fix: back do android saia da sala em vez de fechar o viewer de imagem

O PopScope sozinho nao resolvia: GoRouterDelegate.popRoute() itera
_findCurrentNavigators(), que devolve [root, shell] e retorna
states.reversed, ou seja, o shell navigator e consultado primeiro. O
viewer e um showDialog no root navigator, entao o shell nunca ve o
PopEntry dele, popa a sala e retorna true antes de chegar no dialog.

BackButtonListener registra no BackButtonDispatcher raiz, que e
consultado antes do RouterDelegate.popRoute(), entao o viewer fecha
primeiro. Aplicado tambem no MxcImageViewer (avatar/sticker), que tinha
o mesmo defeito.
This commit is contained in:
2026-09-27 17:30:37 -03:00
parent 9f8877a741
commit d65be8a1ef
2 changed files with 66 additions and 48 deletions

View File

@@ -199,15 +199,25 @@ class ImageViewerController extends State<ImageViewer> {
} }
@override @override
Widget build(BuildContext context) => PopScope<Object?>( Widget build(BuildContext context) => BackButtonListener(
// Claim the system back button (and predictive back gesture) for the // A [PopScope] alone is not enough here: go_router's `popRoute()` asks
// viewer. Without this, the request can reach go_router instead, which // `GoRouterDelegate._findCurrentNavigators()`, which returns the shell
// pops the chat room out of the shell and leaves the image on screen. // navigator BEFORE the root one. The viewer is a dialog on the root
canPop: false, // navigator, so the shell navigator never sees its [PopEntry], pops the
onPopInvokedWithResult: (didPop, _) { // chat room instead and returns `true`, leaving the image on screen.
if (didPop) return; // A [BackButtonListener] is asked before the router gets the request at
// all, so the viewer gets to close itself first.
onBackButtonPressed: () async {
closeAction(); closeAction();
return true;
}, },
child: ImageViewerView(this), child: PopScope<Object?>(
canPop: false,
onPopInvokedWithResult: (didPop, _) {
if (didPop) return;
closeAction();
},
child: ImageViewerView(this),
),
); );
} }

View File

@@ -24,49 +24,57 @@ class MxcImageViewer extends StatelessWidget {
backgroundColor: Colors.black.withAlpha(200), backgroundColor: Colors.black.withAlpha(200),
foregroundColor: Colors.white, foregroundColor: Colors.white,
); );
return PopScope<Object?>( return BackButtonListener(
// Claim the system back button for the viewer so that it is dismissed // Same reason as the room image viewer: this dialog lives on the root
// instead of letting the router navigate away underneath it. // navigator, but go_router asks the shell navigator first, so only a
canPop: false, // [BackButtonListener] (which runs before the router) can keep the back
onPopInvokedWithResult: (didPop, _) { // button from popping the page underneath.
if (didPop) return; onBackButtonPressed: () async {
_close(context); _close(context);
return true;
}, },
child: GestureDetector( child: PopScope<Object?>(
onTap: () => _close(context), canPop: false,
child: Scaffold( onPopInvokedWithResult: (didPop, _) {
backgroundColor: Colors.black.withAlpha(128), if (didPop) return;
extendBodyBehindAppBar: true, _close(context);
appBar: AppBar( },
elevation: 0, child: GestureDetector(
leading: IconButton( onTap: () => _close(context),
style: iconButtonStyle, child: Scaffold(
icon: const Icon(Icons.close), backgroundColor: Colors.black.withAlpha(128),
onPressed: () => _close(context), extendBodyBehindAppBar: true,
color: Colors.white, appBar: AppBar(
tooltip: L10n.of(context).close, elevation: 0,
leading: IconButton(
style: iconButtonStyle,
icon: const Icon(Icons.close),
onPressed: () => _close(context),
color: Colors.white,
tooltip: L10n.of(context).close,
),
backgroundColor: Colors.transparent,
), ),
backgroundColor: Colors.transparent, body: InteractiveViewer(
), minScale: 1.0,
body: InteractiveViewer( maxScale: 10.0,
minScale: 1.0, onInteractionEnd: (endDetails) {
maxScale: 10.0, if (endDetails.velocity.pixelsPerSecond.dy >
onInteractionEnd: (endDetails) { MediaQuery.sizeOf(context).height * 1.5) {
if (endDetails.velocity.pixelsPerSecond.dy > _close(context);
MediaQuery.sizeOf(context).height * 1.5) { }
_close(context); },
} child: Center(
}, child: GestureDetector(
child: Center( // Ignore taps to not go back here:
child: GestureDetector( onTap: () {},
// Ignore taps to not go back here: child: MxcImage(
onTap: () {}, key: ValueKey(mxContent.toString()),
child: MxcImage( uri: mxContent,
key: ValueKey(mxContent.toString()), fit: BoxFit.contain,
uri: mxContent, isThumbnail: false,
fit: BoxFit.contain, animated: true,
isThumbnail: false, ),
animated: true,
), ),
), ),
), ),