From d65be8a1ef050d6097fb7f3d0691038f6f8eb860 Mon Sep 17 00:00:00 2001 From: Welton Moura Date: Sun, 27 Sep 2026 17:30:37 -0300 Subject: [PATCH] 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. --- lib/pages/image_viewer/image_viewer.dart | 26 ++++--- lib/widgets/mxc_image_viewer.dart | 88 +++++++++++++----------- 2 files changed, 66 insertions(+), 48 deletions(-) diff --git a/lib/pages/image_viewer/image_viewer.dart b/lib/pages/image_viewer/image_viewer.dart index 4ad0279..2816741 100644 --- a/lib/pages/image_viewer/image_viewer.dart +++ b/lib/pages/image_viewer/image_viewer.dart @@ -199,15 +199,25 @@ class ImageViewerController extends State { } @override - Widget build(BuildContext context) => PopScope( - // Claim the system back button (and predictive back gesture) for the - // viewer. Without this, the request can reach go_router instead, which - // pops the chat room out of the shell and leaves the image on screen. - canPop: false, - onPopInvokedWithResult: (didPop, _) { - if (didPop) return; + Widget build(BuildContext context) => BackButtonListener( + // A [PopScope] alone is not enough here: go_router's `popRoute()` asks + // `GoRouterDelegate._findCurrentNavigators()`, which returns the shell + // navigator BEFORE the root one. The viewer is a dialog on the root + // navigator, so the shell navigator never sees its [PopEntry], pops the + // chat room instead and returns `true`, leaving the image on screen. + // A [BackButtonListener] is asked before the router gets the request at + // all, so the viewer gets to close itself first. + onBackButtonPressed: () async { closeAction(); + return true; }, - child: ImageViewerView(this), + child: PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, _) { + if (didPop) return; + closeAction(); + }, + child: ImageViewerView(this), + ), ); } diff --git a/lib/widgets/mxc_image_viewer.dart b/lib/widgets/mxc_image_viewer.dart index 7ea6ba8..4dcbb0e 100644 --- a/lib/widgets/mxc_image_viewer.dart +++ b/lib/widgets/mxc_image_viewer.dart @@ -24,49 +24,57 @@ class MxcImageViewer extends StatelessWidget { backgroundColor: Colors.black.withAlpha(200), foregroundColor: Colors.white, ); - return PopScope( - // Claim the system back button for the viewer so that it is dismissed - // instead of letting the router navigate away underneath it. - canPop: false, - onPopInvokedWithResult: (didPop, _) { - if (didPop) return; + return BackButtonListener( + // Same reason as the room image viewer: this dialog lives on the root + // navigator, but go_router asks the shell navigator first, so only a + // [BackButtonListener] (which runs before the router) can keep the back + // button from popping the page underneath. + onBackButtonPressed: () async { _close(context); + return true; }, - child: GestureDetector( - onTap: () => _close(context), - child: Scaffold( - backgroundColor: Colors.black.withAlpha(128), - extendBodyBehindAppBar: true, - appBar: AppBar( - elevation: 0, - leading: IconButton( - style: iconButtonStyle, - icon: const Icon(Icons.close), - onPressed: () => _close(context), - color: Colors.white, - tooltip: L10n.of(context).close, + child: PopScope( + canPop: false, + onPopInvokedWithResult: (didPop, _) { + if (didPop) return; + _close(context); + }, + child: GestureDetector( + onTap: () => _close(context), + child: Scaffold( + backgroundColor: Colors.black.withAlpha(128), + extendBodyBehindAppBar: true, + appBar: AppBar( + 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, - maxScale: 10.0, - onInteractionEnd: (endDetails) { - if (endDetails.velocity.pixelsPerSecond.dy > - MediaQuery.sizeOf(context).height * 1.5) { - _close(context); - } - }, - child: Center( - child: GestureDetector( - // Ignore taps to not go back here: - onTap: () {}, - child: MxcImage( - key: ValueKey(mxContent.toString()), - uri: mxContent, - fit: BoxFit.contain, - isThumbnail: false, - animated: true, + body: InteractiveViewer( + minScale: 1.0, + maxScale: 10.0, + onInteractionEnd: (endDetails) { + if (endDetails.velocity.pixelsPerSecond.dy > + MediaQuery.sizeOf(context).height * 1.5) { + _close(context); + } + }, + child: Center( + child: GestureDetector( + // Ignore taps to not go back here: + onTap: () {}, + child: MxcImage( + key: ValueKey(mxContent.toString()), + uri: mxContent, + fit: BoxFit.contain, + isThumbnail: false, + animated: true, + ), ), ), ),