import 'dart:io' show Platform; import 'package:flutter/foundation.dart'; import 'package:window_manager/window_manager.dart'; import '../utils/platform_detector.dart'; import 'macos_window_service.dart'; import 'native_window_service.dart'; class FullscreenStateManager extends ChangeNotifier with WindowListener { static final FullscreenStateManager _instance = FullscreenStateManager._internal(); factory FullscreenStateManager() => _instance; FullscreenStateManager._internal(); bool _isFullscreen = false; bool _isListening = false; bool _wasMaximized = false; bool get isFullscreen => _isFullscreen; /// Manually set fullscreen state (called by NSWindowDelegate callbacks on macOS) void setFullscreen(bool value) { if (_isFullscreen != value) { _isFullscreen = value; notifyListeners(); } } /// Toggle fullscreen state, handling maximized-to-fullscreen transition on Windows/Linux Future toggleFullscreen() async { if (!PlatformDetector.isDesktopOS()) return; if (Platform.isMacOS) { final isCurrentlyFullscreen = await MacOSWindowService.isFullscreen(); if (isCurrentlyFullscreen) { await MacOSWindowService.exitFullscreen(); } else { await MacOSWindowService.enterFullscreen(); } } else if (Platform.isWindows) { // Route through the native Win32 runner, which restores to the monitor // the window is currently on (window_manager 0.5.1 picks the wrong one // on multi-monitor setups — see issue #880). The native code also // preserves maximized state internally, so no unmaximize dance here. final isCurrentlyFullscreen = await NativeWindowService.isFullScreen(); await NativeWindowService.setFullScreen(!isCurrentlyFullscreen); } else { final isCurrentlyFullscreen = await windowManager.isFullScreen(); if (isCurrentlyFullscreen) { await windowManager.setFullScreen(false); if (_wasMaximized) { await windowManager.maximize(); _wasMaximized = false; } } else { _wasMaximized = await windowManager.isMaximized(); if (_wasMaximized) { await windowManager.unmaximize(); } await windowManager.setFullScreen(true); } } } /// Enter fullscreen, preserving maximized state on Windows/Linux for restoration on exit. Future enterFullscreen() async { if (!PlatformDetector.isDesktopOS()) return; if (Platform.isMacOS) { await MacOSWindowService.enterFullscreen(); } else if (Platform.isWindows) { await NativeWindowService.setFullScreen(true); } else { _wasMaximized = await windowManager.isMaximized(); if (_wasMaximized) { await windowManager.unmaximize(); } await windowManager.setFullScreen(true); } } /// Exit fullscreen, restoring maximized state if needed Future exitFullscreen() async { if (!PlatformDetector.isDesktopOS()) return; if (Platform.isMacOS) { await MacOSWindowService.exitFullscreen(); } else if (Platform.isWindows) { await NativeWindowService.setFullScreen(false); } else { await windowManager.setFullScreen(false); if (_wasMaximized) { await windowManager.maximize(); _wasMaximized = false; } } } /// Exits fullscreen when the platform window is currently fullscreen. /// /// Returns whether fullscreen consumed the request. Querying the native /// source avoids relying on listener state that may still be catching up. Future exitFullscreenIfActive() async { if (!PlatformDetector.isDesktopOS()) return false; final isActive = Platform.isMacOS ? await MacOSWindowService.isFullscreen() : Platform.isWindows ? await NativeWindowService.isFullScreen() : await windowManager.isFullScreen(); if (!isActive) return false; await exitFullscreen(); return true; } void startMonitoring() { if (!_shouldMonitor() || _isListening) return; // Use window_manager listener for Windows/Linux // macOS uses NSWindowDelegate callbacks instead (see FullscreenWindowDelegate) if (!Platform.isMacOS) { windowManager.addListener(this); _isListening = true; } } void stopMonitoring() { if (_isListening) { windowManager.removeListener(this); _isListening = false; } } bool _shouldMonitor() { return PlatformDetector.isDesktopOS(); } @override void onWindowEnterFullScreen() { setFullscreen(true); } @override void onWindowLeaveFullScreen() { setFullscreen(false); } @override void dispose() { stopMonitoring(); super.dispose(); } }