fix(nav): preserve HTPC escape behavior

Use Flutter's supported desktop application-exit API so the second root Back press closes Plezy on Windows. Preserve fullscreen when video player navigation is enabled while keeping fullscreen-first Escape behavior for normal desktop use.

close #1582
This commit is contained in:
edde746
2026-07-16 20:43:10 +02:00
parent a244d249bd
commit 6df8aba097
5 changed files with 94 additions and 10 deletions
+7 -4
View File
@@ -591,10 +591,13 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
isChromePresented: () =>
_isPlayerInitialized && player != null && _hasFirstFrame.value && _chromeController.controlsPresented,
exitFullscreenIfActive: FullscreenStateManager().exitFullscreenIfActive,
// macOS fullscreen belongs to the app window, not the player route.
// Escape stages through chrome/player Back and the root Home screen
// owns leaving native fullscreen.
physicalEscapeExitsFullscreen: !Platform.isMacOS,
// macOS fullscreen belongs to the app window, while HTPC-style player
// navigation treats physical Escape as semantic Back. In both cases the
// player must leave native fullscreen alone.
physicalEscapeExitsFullscreen: () => shouldPhysicalEscapeExitFullscreen(
isMacOS: Platform.isMacOS,
videoPlayerNavigationEnabled: _videoPlayerNavigationEnabled,
),
exitPlayer: () => unawaited(_handleBackButton()),
navigateHome: _handleHomeButton,
isActive: () => mounted,
+12 -1
View File
@@ -1,9 +1,12 @@
import 'dart:io' show Platform;
import 'dart:ui' as ui;
import 'package:flutter/services.dart';
import '../utils/platform_detector.dart';
typedef AppExitApplication = Future<ui.AppExitResponse> Function(ui.AppExitType exitType, int exitCode);
class AppExitService {
static const bool _tvosBuild = bool.fromEnvironment('TVOS_BUILD');
static const MethodChannel _channel = MethodChannel('com.plezy/app_exit');
@@ -12,7 +15,7 @@ class AppExitService {
///
/// tvOS has no public API for force-quitting or going Home, so callers that
/// handle a physical back/Menu key should let the event continue instead.
static Future<bool> requestExit() async {
static Future<bool> requestExit({AppExitApplication? exitApplicationForTesting}) async {
if (_tvosBuild || PlatformDetector.isAppleTV()) return false;
if (Platform.isAndroid) {
@@ -27,6 +30,14 @@ class AppExitService {
}
}
if (PlatformDetector.isDesktopOS()) {
final exitApplication =
exitApplicationForTesting ??
(exitType, exitCode) => ServicesBinding.instance.exitApplication(exitType, exitCode);
final response = await exitApplication(ui.AppExitType.required, 0);
return response == ui.AppExitResponse.exit;
}
await SystemNavigator.pop();
return true;
}
+10 -4
View File
@@ -184,6 +184,10 @@ enum PlayerNavigationKey { none, physicalEscape, back, home }
enum PlayerBackDisposition { closeContentStrip, exitFullscreenIfActive, hideControls, exitPlayer }
bool shouldPhysicalEscapeExitFullscreen({required bool isMacOS, required bool videoPlayerNavigationEnabled}) {
return !isMacOS && !videoPlayerNavigationEnabled;
}
/// Coordinates the player-level stages shared by keyboard, controller, and
/// companion navigation after descendants have handled local overlays.
class PlayerNavigationCoordinator {
@@ -192,7 +196,7 @@ class PlayerNavigationCoordinator {
final VoidCallback dismissPrompt;
final bool Function() isChromePresented;
final Future<bool> Function() exitFullscreenIfActive;
final bool physicalEscapeExitsFullscreen;
final bool Function() _physicalEscapeExitsFullscreen;
final VoidCallback exitPlayer;
final VoidCallback navigateHome;
final bool Function() isActive;
@@ -205,13 +209,15 @@ class PlayerNavigationCoordinator {
required this.dismissPrompt,
required this.isChromePresented,
required this.exitFullscreenIfActive,
this.physicalEscapeExitsFullscreen = true,
bool Function()? physicalEscapeExitsFullscreen,
required this.exitPlayer,
required this.navigateHome,
bool Function()? isActive,
}) : isActive = isActive ?? _alwaysActive;
}) : _physicalEscapeExitsFullscreen = physicalEscapeExitsFullscreen ?? _alwaysTrue,
isActive = isActive ?? _alwaysActive;
static bool _alwaysActive() => true;
static bool _alwaysTrue() => true;
void handle(PlayerNavigationKey navigationKey) {
if (navigationKey == PlayerNavigationKey.home) {
@@ -226,7 +232,7 @@ class PlayerNavigationCoordinator {
navigationKey: navigationKey,
contentStripVisible: chromeController.contentStripVisible,
controlsVisible: isChromePresented(),
physicalEscapeExitsFullscreen: physicalEscapeExitsFullscreen,
physicalEscapeExitsFullscreen: _physicalEscapeExitsFullscreen(),
);
_applyDisposition(disposition);
}