diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 369a38a..83c1ae2 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -1,4 +1,7 @@ +import 'dart:io' show Platform; + import 'package:flutter/material.dart'; +import 'package:flutter/services.dart' show SystemNavigator; import '../config/responsive.dart'; import '../config/strings.dart'; @@ -169,6 +172,9 @@ class _HomePageState extends State { await _session.logoutGame(userId: widget.userId); } + /// 退登并回到主标题页。供 AppBar 的退出按钮与风险操作完成后的 + /// onExitToTitle 使用——这两处都是「明确要回标题」,与主页上的 + /// 系统返回手势不同(后者见 [_exitApp])。 Future _performLogoutAndExit() async { setState(() => _loggingOut = true); if (_session.gameLogin != null) { @@ -184,6 +190,13 @@ class _HomePageState extends State { } } + /// 主页上的原生返回手势 = 结束应用。 + void _exitApp() { + // iOS 不允许程序化退出,且根路由本来也没有返回手势。 + if (Platform.isIOS) return; + SystemNavigator.pop(); + } + @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -192,11 +205,13 @@ class _HomePageState extends State { listenable: _session, builder: (context, _) { return PopScope( - canPop: !_loggingOut, + // 根路由锁死在主页。之前 canPop 为 !_loggingOut(平时即 true),系统返回 + // 手势会直接把 HomePage 弹掉、落回 Login Page,而且因为 didPop==true, + // 回调里的退登根本不会执行——既回错页面又没退登。 + canPop: false, onPopInvokedWithResult: (didPop, _) { - if (!didPop && !_loggingOut) { - _performLogoutAndExit(); - } + if (didPop || _loggingOut) return; + _exitApp(); }, child: Scaffold( appBar: AppBar( diff --git a/test/home_back_navigation_test.dart b/test/home_back_navigation_test.dart new file mode 100644 index 0000000..02b3c6f --- /dev/null +++ b/test/home_back_navigation_test.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:ichino/pages/home_page.dart'; + +/// 主页上的系统返回手势不能把 HomePage 弹掉(那会落回 Login Page, +/// 而且退登逻辑因为 didPop==true 根本不会跑)。 +void main() { + testWidgets('主页收到系统返回时结束应用而不是退回上一页', (tester) async { + final calls = []; + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(SystemChannels.platform, (call) async { + calls.add(call.method); + return null; + }); + addTearDown(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(SystemChannels.platform, null); + }); + + await tester.pumpWidget( + const MaterialApp(home: Text('login-root')), + ); + + final navigator = tester.state(find.byType(Navigator)); + navigator.push( + MaterialPageRoute(builder: (_) => const HomePage(userId: 1, token: 't')), + ); + await tester.pumpAndSettle(); + + expect(find.text('login-root'), findsNothing); + expect(find.byType(HomePage), findsOneWidget); + + await tester.binding.handlePopRoute(); + await tester.pumpAndSettle(); + + // HomePage 仍在栈上:根路由被锁死。 + expect(find.byType(HomePage), findsOneWidget); + expect(find.text('login-root'), findsNothing); + + // 非 iOS 上应当走 SystemNavigator.pop 结束应用。 + expect(calls, contains('SystemNavigator.pop')); + }); +}