diff --git a/lib/main.dart b/lib/main.dart index 168af9cb..e9cd53b7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -57,6 +57,8 @@ void _absorbZeroOffsetPointerEvent(PointerEvent event) { } } +final ValueNotifier appClosing = ValueNotifier(false); + void main() async { WidgetsFlutterBinding.ensureInitialized(); _installZeroOffsetPointerGuard(); // Workaround for iPadOS 26.1+ modal dismissal bug @@ -255,6 +257,17 @@ class _MainAppState extends State with WidgetsBindingObserver { darkTheme: themeProvider.darkTheme, themeMode: themeProvider.materialThemeMode, navigatorObservers: [routeObserver], + builder: (context, child) { + return ValueListenableBuilder( + valueListenable: appClosing, + builder: (context, closing, _) { + if (!closing) { + return child ?? const SizedBox.shrink(); + } + return const SizedBox.expand(child: ColoredBox(color: Colors.black)); + }, + ); + }, home: const OrientationAwareSetup(), ), ), diff --git a/lib/screens/main_screen.dart b/lib/screens/main_screen.dart index 489ba8cc..19735958 100644 --- a/lib/screens/main_screen.dart +++ b/lib/screens/main_screen.dart @@ -1,7 +1,10 @@ +import 'dart:io' show Platform; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:window_manager/window_manager.dart'; import '../../services/plex_client.dart'; import '../i18n/strings.g.dart'; import '../services/update_service.dart'; @@ -64,7 +67,8 @@ class MainScreen extends StatefulWidget { State createState() => _MainScreenState(); } -class _MainScreenState extends State with RouteAware { +class _MainScreenState extends State with RouteAware, WindowListener { + bool _isClosing = false; late int _currentIndex; String? _selectedLibraryGlobalKey; @@ -97,6 +101,11 @@ class _MainScreenState extends State with RouteAware { super.initState(); _isOffline = widget.isOfflineMode; + if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) { + windowManager.addListener(this); + windowManager.setPreventClose(true); + } + // Start on Downloads tab when in offline mode // In offline mode: visual index 0 = Downloads (screen 3), 1 = Settings (screen 4) // In online mode: indices match directly @@ -277,12 +286,30 @@ class _MainScreenState extends State with RouteAware { @override void dispose() { routeObserver.unsubscribe(this); + if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) { + windowManager.removeListener(this); + windowManager.setPreventClose(false); + } _offlineModeProvider?.removeListener(_handleOfflineStatusChanged); _sidebarFocusScope.dispose(); _contentFocusScope.dispose(); super.dispose(); } + @override + void onWindowClose() { + if (_isClosing) return; + _isClosing = true; + + appClosing.value = true; + + // Give the UI a brief moment to settle before tearing down the window. + Future.delayed(const Duration(milliseconds: 150), () async { + await windowManager.setPreventClose(false); + await windowManager.destroy(); + }); + } + List _buildScreens(bool offline) { // In offline mode, only show Downloads and Settings // In online mode, show all 5 screens