diff --git a/lib/screens/scanner_screen.dart b/lib/screens/scanner_screen.dart index 3222168..f8eece9 100644 --- a/lib/screens/scanner_screen.dart +++ b/lib/screens/scanner_screen.dart @@ -25,6 +25,11 @@ class ScannerScreen extends StatefulWidget { class _ScannerScreenState extends State { bool _changedNavigation = false; + + /// Re-entrancy guard for routing into the app. Distinct from + /// [_changedNavigation], which records that we entered at least once and + /// gates the disconnect-on-dispose cleanup. + bool _routingIntoApp = false; late final MeshCoreConnector _connector; late final VoidCallback _connectionListener; BluetoothAdapterState _bluetoothState = BluetoothAdapterState.unknown; @@ -42,12 +47,21 @@ class _ScannerScreenState extends State { } else if (_connector.state == MeshCoreConnectionState.connected && _connector.activeTransport == MeshCoreTransportType.bluetooth && isCurrentRoute && - !_changedNavigation) { + !_routingIntoApp) { + // Route in whenever this screen is showing while connected, not just + // on the first connect. Otherwise landing back here with a live + // connection is a dead end: Connect does nothing, because there is + // nothing left to connect. _changedNavigation = true; + _routingIntoApp = true; if (mounted) { - Navigator.of(context).push( - MaterialPageRoute(builder: (context) => const ChannelsScreen()), - ); + Navigator.of(context) + .push( + MaterialPageRoute(builder: (context) => const ChannelsScreen()), + ) + .then((_) { + if (mounted) _routingIntoApp = false; + }); } } }; diff --git a/lib/widgets/app_shell.dart b/lib/widgets/app_shell.dart index 689caa3..b95ba8d 100644 --- a/lib/widgets/app_shell.dart +++ b/lib/widgets/app_shell.dart @@ -68,21 +68,27 @@ class _AppShellState extends State { /// System back, in priority order: /// 1. an open drawer closes, - /// 2. otherwise pop the route (a pushed chat returns to its list), - /// 3. otherwise hand back to the OS, so Android returns to the home - /// screen or the previous app rather than sitting on a dead press. + /// 2. on a detail screen, pop back to the list it came from, + /// 3. on a primary view, hand back to the OS so Android returns to the + /// home screen or the previous app. /// - /// Screens previously did this with `PopScope(canPop: !isConnected)`, which - /// swallowed back entirely while connected: the drawer would not close and - /// the app would never background. + /// Step 3 must NOT pop, even though the route below can be popped. The + /// primary views sit on top of the scanner, so popping would dump a + /// connected user back onto the radio-connect list. Reaching the scanner is + /// what Disconnect is for, not what Back is for. + /// + /// A primary view is one carrying the bottom bar; a detail screen (a channel + /// chat) has no [selectedIndex] and is genuinely pushed. void _handleBack() { final scaffold = _scaffoldKey.currentState; if (scaffold?.isDrawerOpen ?? false) { scaffold!.closeDrawer(); return; } + + final isPrimaryView = widget.selectedIndex != null; final navigator = Navigator.of(context); - if (navigator.canPop()) { + if (!isPrimaryView && navigator.canPop()) { navigator.pop(); return; }