mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-14 04:13:43 +00:00
fix(android): intercept back during cold startup and move exit toast down
This commit is contained in:
1 parent
ee6b073bc1
commit
bd03d3b31c
8 files changed
+195
-48
No files matched your search
@@ -0,0 +1,65 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:gopeed/app/application/app_runtime_controller.dart';
|
||||
import 'package:gopeed/app/router/app_router.dart';
|
||||
import 'package:gopeed/features/auth/application/web_auth_controller.dart';
|
||||
import 'package:gopeed/shared/theme/app_theme.dart';
|
||||
import 'package:gopeed/shared/widgets/app_toast.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart' as shad;
|
||||
|
||||
class _StartingRuntime extends AppRuntimeController {
|
||||
final pending = Completer<AppRuntimeState>();
|
||||
|
||||
@override
|
||||
Future<AppRuntimeState> build() => pending.future;
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets(
|
||||
'cold startup registers Android back and confirms exit before runtime is ready',
|
||||
(tester) async {
|
||||
final platformCalls = <MethodCall>[];
|
||||
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (call) async {
|
||||
platformCalls.add(call);
|
||||
return null;
|
||||
});
|
||||
addTearDown(() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, null));
|
||||
final runtime = _StartingRuntime();
|
||||
final auth = WebAuthController();
|
||||
final router = AppRouter.build(auth);
|
||||
addTearDown(router.dispose);
|
||||
addTearDown(auth.dispose);
|
||||
tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed);
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [appRuntimeControllerProvider.overrideWith(() => runtime)],
|
||||
child: shad.ShadcnApp.router(
|
||||
theme: AppTheme.dark(),
|
||||
materialTheme: AppTheme.materialDark(),
|
||||
routerConfig: router,
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
expect(find.byType(shad.CircularProgressIndicator), findsOneWidget);
|
||||
expect(
|
||||
platformCalls.where((call) => call.method == 'SystemNavigator.setFrameworkHandlesBack').last.arguments,
|
||||
isTrue,
|
||||
);
|
||||
await tester.binding.handlePopRoute();
|
||||
await tester.pump();
|
||||
expect(platformCalls.where((call) => call.method == 'SystemNavigator.pop'), isEmpty);
|
||||
expect(find.byType(AppToastContent), findsOneWidget);
|
||||
await tester.binding.handlePopRoute();
|
||||
await tester.pump();
|
||||
expect(platformCalls.where((call) => call.method == 'SystemNavigator.pop'), hasLength(1));
|
||||
await tester.pumpWidget(const SizedBox());
|
||||
},
|
||||
variant: TargetPlatformVariant.only(TargetPlatform.android),
|
||||
);
|
||||
}
|
||||
@@ -24,11 +24,11 @@ void main() {
|
||||
router = GoRouter(
|
||||
routes: [
|
||||
ShellRoute(
|
||||
builder: (_, _, child) => child,
|
||||
builder: (_, _, child) => MobileExitGuard(child: child),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (_, _) => const MobileExitGuard(child: SizedBox.expand()),
|
||||
builder: (_, _) => const SizedBox.expand(),
|
||||
routes: [
|
||||
GoRoute(path: 'details', builder: (_, _) => const SizedBox.expand()),
|
||||
for (final path in ['extensions', 'settings'])
|
||||
@@ -56,13 +56,14 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
testWidgets('root back shows centered prompt, second back exits', (tester) async {
|
||||
testWidgets('root back shows bottom prompt, second back exits', (tester) async {
|
||||
await mount(tester);
|
||||
await tester.binding.handlePopRoute();
|
||||
await tester.pump();
|
||||
expect(exits, isEmpty);
|
||||
expect(find.byType(AppToastContent), findsOneWidget);
|
||||
expect(tester.getCenter(find.byType(AppToastContent)), const Offset(400, 300));
|
||||
expect(tester.getCenter(find.byType(AppToastContent)).dx, 400);
|
||||
expect(tester.getBottomLeft(find.byType(AppToastContent)).dy, 576);
|
||||
await tester.pump(const Duration(milliseconds: 1900));
|
||||
await tester.binding.handlePopRoute();
|
||||
await tester.pump();
|
||||
@@ -127,4 +128,34 @@ void main() {
|
||||
expect(exits, hasLength(1));
|
||||
}, variant: TargetPlatformVariant.only(TargetPlatform.android));
|
||||
}
|
||||
testWidgets(
|
||||
'dialog back dismisses the dialog before exit confirmation',
|
||||
(tester) async {
|
||||
await mount(tester);
|
||||
final context = router.routerDelegate.navigatorKey.currentContext!;
|
||||
showGeneralDialog<void>(
|
||||
context: context,
|
||||
pageBuilder: (_, _, _) => const Center(child: Text('Dialog content')),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.binding.handlePopRoute();
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Dialog content'), findsNothing);
|
||||
expect(find.byType(AppToastContent), findsNothing);
|
||||
expect(exits, isEmpty);
|
||||
},
|
||||
variant: TargetPlatformVariant.only(TargetPlatform.android),
|
||||
);
|
||||
|
||||
testWidgets('bottom prompt stays above the Android safe area', (tester) async {
|
||||
tester.view.padding = const FakeViewPadding(bottom: 48);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPadding);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
await mount(tester);
|
||||
await tester.binding.handlePopRoute();
|
||||
await tester.pump();
|
||||
expect(tester.getBottomLeft(find.byType(AppToastContent)).dy, tester.view.physicalSize.height - 48 - 24);
|
||||
await tester.pumpWidget(const SizedBox());
|
||||
}, variant: TargetPlatformVariant.only(TargetPlatform.android));
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart' as shad;
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:gopeed/features/home/presentation/pages/home_page.dart';
|
||||
import 'package:gopeed/app/app.dart';
|
||||
import 'package:gopeed/app/router/app_router.dart';
|
||||
import 'package:gopeed/core/capabilities/app_capabilities.dart';
|
||||
@@ -2254,15 +2255,16 @@ void main() {
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
testWidgets('settings mobile back returns one level before requiring a second back to exit', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
testWidgets('settings mobile back returns through home before confirming exit', (WidgetTester tester) async {
|
||||
await _setTestSize(tester, const Size(390, 760));
|
||||
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
||||
addTearDown(() => debugDefaultTargetPlatformOverride = null);
|
||||
var systemPopCalls = 0;
|
||||
bool? frameworkHandlesBack;
|
||||
tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed);
|
||||
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (call) async {
|
||||
if (call.method == 'SystemNavigator.pop') systemPopCalls++;
|
||||
if (call.method == 'SystemNavigator.setFrameworkHandlesBack') frameworkHandlesBack = call.arguments as bool;
|
||||
return null;
|
||||
});
|
||||
addTearDown(() {
|
||||
@@ -2281,6 +2283,7 @@ void main() {
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(frameworkHandlesBack, isTrue);
|
||||
await tester.tap(find.text('Settings'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Basic'));
|
||||
@@ -2294,6 +2297,13 @@ void main() {
|
||||
expect(find.byKey(const ValueKey('theme-mode-system')), findsNothing);
|
||||
expect(systemPopCalls, 0);
|
||||
|
||||
await tester.binding.handlePopRoute();
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(HomePage), findsOneWidget);
|
||||
expect(find.text('Press back again to exit'), findsNothing);
|
||||
expect(systemPopCalls, 0);
|
||||
expect(frameworkHandlesBack, isTrue);
|
||||
|
||||
await tester.binding.handlePopRoute();
|
||||
await tester.pump();
|
||||
expect(find.text('Press back again to exit'), findsOneWidget);
|
||||
|
||||
Reference in new issue
Block a user