From a868e8f510777d7d05ec3ee1a11a6576867b4544 Mon Sep 17 00:00:00 2001 From: CMonk Date: Thu, 26 Mar 2026 05:24:51 +0800 Subject: [PATCH 1/3] add keyboard escape and mouse back button to navigate back --- lib/app_widget.dart | 47 ++++++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/lib/app_widget.dart b/lib/app_widget.dart index 78ad8aeca..382d5ef77 100644 --- a/lib/app_widget.dart +++ b/lib/app_widget.dart @@ -1,5 +1,7 @@ import 'dart:io'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_modular/flutter_modular.dart'; import 'package:dynamic_color/dynamic_color.dart'; @@ -269,19 +271,38 @@ class _AppWidgetState extends State notify: false, ); } - return MaterialApp.router( - title: "Kazumi", - localizationsDelegates: GlobalMaterialLocalizations.delegates, - supportedLocales: const [ - Locale.fromSubtags( - languageCode: 'zh', scriptCode: 'Hans', countryCode: "CN") - ], - locale: const Locale.fromSubtags( - languageCode: 'zh', scriptCode: 'Hans', countryCode: "CN"), - theme: themeProvider.light, - darkTheme: themeProvider.dark, - themeMode: themeProvider.themeMode, - routerConfig: Modular.routerConfig, + return Shortcuts( + shortcuts: { + LogicalKeySet(LogicalKeyboardKey.escape): const ActivateIntent(), + }, + child: Actions( + actions: >{ + ActivateIntent: CallbackAction( + onInvoke: (intent) => Modular.to.maybePop(), + ), + }, + child: Listener( + onPointerDown: (event) { + if (event.buttons == kBackMouseButton) { + Modular.to.maybePop(); + } + }, + child: MaterialApp.router( + title: "Kazumi", + localizationsDelegates: GlobalMaterialLocalizations.delegates, + supportedLocales: const [ + Locale.fromSubtags( + languageCode: 'zh', scriptCode: 'Hans', countryCode: "CN") + ], + locale: const Locale.fromSubtags( + languageCode: 'zh', scriptCode: 'Hans', countryCode: "CN"), + theme: themeProvider.light, + darkTheme: themeProvider.dark, + themeMode: themeProvider.themeMode, + routerConfig: Modular.routerConfig, + ), + ), + ), ); }, ); From e3c716dd233e3d80effa1754c84e0718b58458a0 Mon Sep 17 00:00:00 2001 From: CMonk Date: Thu, 26 Mar 2026 16:44:14 +0800 Subject: [PATCH 2/3] Create shortcuts_test.dart --- test/shortcuts_test.dart | 116 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 test/shortcuts_test.dart diff --git a/test/shortcuts_test.dart b/test/shortcuts_test.dart new file mode 100644 index 000000000..028bb63bb --- /dev/null +++ b/test/shortcuts_test.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_modular/flutter_modular.dart'; +import 'package:kazumi/app_widget.dart'; +import 'package:kazumi/bean/settings/theme_provider.dart'; +import 'package:provider/provider.dart'; +import 'package:hive_ce/hive.dart'; +import 'package:kazumi/utils/storage.dart'; +import 'dart:io'; + +class MockGStorage { + static Future init() async { + final tempDir = Directory.systemTemp.createTempSync(); + Hive.init(tempDir.path); + GStorage.favorites = await Hive.openBox('favorites'); + GStorage.setting = await Hive.openBox('setting'); + GStorage.collectibles = await Hive.openBox('collectibles'); + GStorage.histories = await Hive.openBox('histories'); + GStorage.collectChanges = await Hive.openBox('collectChanges'); + GStorage.shieldList = await Hive.openBox('shieldList'); + GStorage.searchHistory = await Hive.openBox('searchHistory'); + GStorage.downloads = await Hive.openBox('downloads'); + } +} + +// A simple module for testing navigation without heavy page dependencies +class TestPage extends StatelessWidget { + final String title; + const TestPage({super.key, required this.title}); + @override + Widget build(BuildContext context) => Scaffold(appBar: AppBar(title: Text(title))); +} + +class TestModule extends Module { + @override + void routes(r) { + r.child('/', child: (_) => const TestPage(title: 'Root')); + r.child('/sub', child: (_) => const TestPage(title: 'Sub')); + } +} + +void main() { + setUpAll(() async { + await MockGStorage.init(); + await GStorage.setting.put(SettingBoxKey.themeColor, 'default'); + await GStorage.setting.put(SettingBoxKey.themeMode, 'system'); + await GStorage.setting.put(SettingBoxKey.oledEnhance, false); + await GStorage.setting.put(SettingBoxKey.useSystemFont, false); + }); + + testWidgets('AppWidget shortcuts correctly trigger maybePop upon navigation', (WidgetTester tester) async { + // We use a simplified AppModule setup to avoid heavy page dependencies (like CollectPage) + // while still testing the AppWidget's shortcut logic. + + await tester.pumpWidget( + ChangeNotifierProvider( + create: (_) => ThemeProvider(), + child: ModularApp( + module: TestModule(), + child: const AppWidget(), + ), + ), + ); + + await tester.pumpAndSettle(); + + // 1. Navigate to a sub-page + Modular.to.pushNamed('/sub'); + await tester.pumpAndSettle(); + expect(Modular.to.path, '/sub'); + + // 2. Simulate Escape key to trigger maybePop + await tester.sendKeyEvent(LogicalKeyboardKey.escape); + await tester.pumpAndSettle(); + + // 3. Verify that we popped back to root + expect(Modular.to.path, '/'); + }); + + testWidgets('AppWidget handles Back mouse button for navigation', (WidgetTester tester) async { + await tester.pumpWidget( + ChangeNotifierProvider( + create: (_) => ThemeProvider(), + child: ModularApp( + module: TestModule(), + child: const AppWidget(), + ), + ), + ); + + await tester.pumpAndSettle(); + + // 1. Navigate to a sub-page + Modular.to.pushNamed('/sub'); + await tester.pumpAndSettle(); + expect(Modular.to.path, '/sub'); + + // 2. Simulate back mouse button + const int kBackMouseButton = 8; + final Offset center = tester.getCenter(find.byType(AppWidget).first); + final TestPointer pointer = TestPointer(1, PointerDeviceKind.mouse); + await tester.sendEventToBinding(pointer.hover(center)); + await tester.sendEventToBinding(pointer.down(center, buttons: kBackMouseButton)); + await tester.sendEventToBinding(pointer.up()); + await tester.pumpAndSettle(); + + // 3. Verify pop + expect(Modular.to.path, '/'); + }); +} + + + + From 16d449363abff34266adbe26102b1d5a04ede559 Mon Sep 17 00:00:00 2001 From: CMonk Date: Thu, 26 Mar 2026 19:11:43 +0800 Subject: [PATCH 3/3] Revert "Create shortcuts_test.dart" This reverts commit e3c716dd233e3d80effa1754c84e0718b58458a0. --- test/shortcuts_test.dart | 116 --------------------------------------- 1 file changed, 116 deletions(-) delete mode 100644 test/shortcuts_test.dart diff --git a/test/shortcuts_test.dart b/test/shortcuts_test.dart deleted file mode 100644 index 028bb63bb..000000000 --- a/test/shortcuts_test.dart +++ /dev/null @@ -1,116 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:flutter_modular/flutter_modular.dart'; -import 'package:kazumi/app_widget.dart'; -import 'package:kazumi/bean/settings/theme_provider.dart'; -import 'package:provider/provider.dart'; -import 'package:hive_ce/hive.dart'; -import 'package:kazumi/utils/storage.dart'; -import 'dart:io'; - -class MockGStorage { - static Future init() async { - final tempDir = Directory.systemTemp.createTempSync(); - Hive.init(tempDir.path); - GStorage.favorites = await Hive.openBox('favorites'); - GStorage.setting = await Hive.openBox('setting'); - GStorage.collectibles = await Hive.openBox('collectibles'); - GStorage.histories = await Hive.openBox('histories'); - GStorage.collectChanges = await Hive.openBox('collectChanges'); - GStorage.shieldList = await Hive.openBox('shieldList'); - GStorage.searchHistory = await Hive.openBox('searchHistory'); - GStorage.downloads = await Hive.openBox('downloads'); - } -} - -// A simple module for testing navigation without heavy page dependencies -class TestPage extends StatelessWidget { - final String title; - const TestPage({super.key, required this.title}); - @override - Widget build(BuildContext context) => Scaffold(appBar: AppBar(title: Text(title))); -} - -class TestModule extends Module { - @override - void routes(r) { - r.child('/', child: (_) => const TestPage(title: 'Root')); - r.child('/sub', child: (_) => const TestPage(title: 'Sub')); - } -} - -void main() { - setUpAll(() async { - await MockGStorage.init(); - await GStorage.setting.put(SettingBoxKey.themeColor, 'default'); - await GStorage.setting.put(SettingBoxKey.themeMode, 'system'); - await GStorage.setting.put(SettingBoxKey.oledEnhance, false); - await GStorage.setting.put(SettingBoxKey.useSystemFont, false); - }); - - testWidgets('AppWidget shortcuts correctly trigger maybePop upon navigation', (WidgetTester tester) async { - // We use a simplified AppModule setup to avoid heavy page dependencies (like CollectPage) - // while still testing the AppWidget's shortcut logic. - - await tester.pumpWidget( - ChangeNotifierProvider( - create: (_) => ThemeProvider(), - child: ModularApp( - module: TestModule(), - child: const AppWidget(), - ), - ), - ); - - await tester.pumpAndSettle(); - - // 1. Navigate to a sub-page - Modular.to.pushNamed('/sub'); - await tester.pumpAndSettle(); - expect(Modular.to.path, '/sub'); - - // 2. Simulate Escape key to trigger maybePop - await tester.sendKeyEvent(LogicalKeyboardKey.escape); - await tester.pumpAndSettle(); - - // 3. Verify that we popped back to root - expect(Modular.to.path, '/'); - }); - - testWidgets('AppWidget handles Back mouse button for navigation', (WidgetTester tester) async { - await tester.pumpWidget( - ChangeNotifierProvider( - create: (_) => ThemeProvider(), - child: ModularApp( - module: TestModule(), - child: const AppWidget(), - ), - ), - ); - - await tester.pumpAndSettle(); - - // 1. Navigate to a sub-page - Modular.to.pushNamed('/sub'); - await tester.pumpAndSettle(); - expect(Modular.to.path, '/sub'); - - // 2. Simulate back mouse button - const int kBackMouseButton = 8; - final Offset center = tester.getCenter(find.byType(AppWidget).first); - final TestPointer pointer = TestPointer(1, PointerDeviceKind.mouse); - await tester.sendEventToBinding(pointer.hover(center)); - await tester.sendEventToBinding(pointer.down(center, buttons: kBackMouseButton)); - await tester.sendEventToBinding(pointer.up()); - await tester.pumpAndSettle(); - - // 3. Verify pop - expect(Modular.to.path, '/'); - }); -} - - - -