diff --git a/README.md b/README.md index c2158af..9077a28 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,8 @@ Anywhere in a server (unless you are typing): Space = ready / unready, Tab = chat & activity (Tab never moves between buttons). Keys while the player fills the window: Left/Right = seek 10 s, Up/Down = volume, Shift+Right = skip 1:30, PgUp/PgDn = previous / next chapter (as in mpv), F = fullscreen, Esc = -close the chat or fullscreen, else ask to leave, Ctrl+R = reload video, +close the chat, then fullscreen, then shrink the player to mini; from the +mini it asks to leave, Ctrl+R = reload video, `<` / `>` or Shift+P / Shift+N = previous / next Jellyfin episode, Ctrl+1..6 = Anime4K mode A, B, C, A+A, B+B, C+A, Ctrl+0 = shaders off. Every seek, skip and chapter jump moves everyone. diff --git a/apps/desktop/lib/src/screens/home/settings_page.dart b/apps/desktop/lib/src/screens/home/settings_page.dart index caaedbc..655081f 100644 --- a/apps/desktop/lib/src/screens/home/settings_page.dart +++ b/apps/desktop/lib/src/screens/home/settings_page.dart @@ -22,7 +22,7 @@ const sessionShortcuts = [ ('< or Shift+P', 'Put up the previous Jellyfin episode'), ('Ctrl+1…6', 'Shaders: Anime4K mode A, B, C, A+A, B+B, C+A'), ('Ctrl+0', 'Shaders off'), - ('Esc', 'Close the panel or fullscreen, else ask to leave'), + ('Esc', 'Close the panel or fullscreen, shrink the player, then ask to leave'), ]; /// One titled card of the Settings tab (rounded Material, so list tiles diff --git a/apps/desktop/lib/src/shell/session_player.dart b/apps/desktop/lib/src/shell/session_player.dart index 2c67333..e96dd6f 100644 --- a/apps/desktop/lib/src/shell/session_player.dart +++ b/apps/desktop/lib/src/shell/session_player.dart @@ -164,8 +164,15 @@ class _SessionPlayerState extends State { /// dialog, a card, a text field) is in front of it. Space and Tab are /// app-wide ([AppKeys]). bool _globalKey(KeyEvent event) { - if (event is! KeyDownEvent || !mounted || !_full) return false; + if (event is! KeyDownEvent || !mounted) return false; if (SessionState.instance.route.peek() != AppRoute.session) return false; + // Mini: Esc is the last step out, asking to leave the server. + if (!_full) { + if (event.logicalKey != LogicalKeyboardKey.escape) return false; + if (ModalRoute.of(context)?.isCurrent == false || textInputFocused()) return false; + widget.onLeave(); + return true; + } final route = ModalRoute.of(context); if (route != null && !route.isCurrent) { // A menu of ours is open: Esc closes it. @@ -240,7 +247,8 @@ class _SessionPlayerState extends State { } else if (widget.fullscreen) { widget.onToggleFullscreen(); } else { - widget.onLeave(); + // One step at a time: big player → mini, then (from mini) leave. + widget.onCollapse(); } return true; } diff --git a/apps/desktop/test/shell_test.dart b/apps/desktop/test/shell_test.dart index 8292555..84782af 100644 --- a/apps/desktop/test/shell_test.dart +++ b/apps/desktop/test/shell_test.dart @@ -306,6 +306,17 @@ void main() { await stepThrough(); expect(shell().fullscreen, isFalse); expect(find.byType(PartyStrip), findsOneWidget); + // Esc again: big → mini, not leave; from mini it asks to leave. + await tester.runAsync(() => Future.delayed(const Duration(milliseconds: 400))); + await tester.sendKeyEvent(LogicalKeyboardKey.escape); + await stepThrough(); + expect(shell().mode, PlayerMode.mini); + expect(find.text('Leave server?'), findsNothing); + await tester.sendKeyEvent(LogicalKeyboardKey.escape); + await tester.pumpAndSettle(); + expect(find.text('Leave server?'), findsOneWidget); + await tester.tap(find.text('Cancel')); + await tester.pumpAndSettle(); // Never rebuilt from scratch on the way. expect(FakeVideo.created, 1); // Offline: no player at all.