import 'dart:async'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:quasar_jet/models/storage_model.dart'; import 'package:quasar_jet/quick_tile_dispatcher.dart' show quickTileDispatcher; import 'package:quasar_jet/services/tray_service.dart'; import 'package:quasar_jet/services/vpn_service.dart'; import 'package:quasar_jet/views/main_view.dart'; import 'package:quasar_jet/theme.dart'; import 'package:window_manager/window_manager.dart'; @pragma('vm:entry-point') void _keepQuickTileDispatcher() { quickTileDispatcher; } @pragma('vm:entry-point') Future quickTileMain() async { await quickTileDispatcher(); } Future main() async { WidgetsFlutterBinding.ensureInitialized(); if (_isLinuxDesktop) { await windowManager.ensureInitialized(); await windowManager.waitUntilReadyToShow( const WindowOptions( size: Size(980, 720), center: true, title: 'Quasar Jet', ), () async { await windowManager.setPreventClose(true); await windowManager.show(); await windowManager.focus(); }, ); } final model = StorageModel(); await model.init(); final vpnService = VpnService(); runApp(MyApp(model: model, vpnService: vpnService)); } // ignore: must_be_immutable class MyApp extends StatefulWidget { MyApp({super.key, required this.model, required this.vpnService}); StorageModel model; VpnService vpnService; static _MyAppState of(BuildContext context) { return context.findAncestorStateOfType<_MyAppState>()!; } @override State createState() => _MyAppState(); } class _MyAppState extends State { ThemeMode _themeMode = ThemeMode.system; late final WindowListener _windowListener = _LinuxWindowListener(this); TrayService? _trayService; bool _isQuitting = false; @override void initState() { super.initState(); if (_isLinuxDesktop) { windowManager.addListener(_windowListener); unawaited(_initTray()); } } void changeTheme(ThemeMode mode) { setState(() { _themeMode = mode; }); } Future _initTray() async { _trayService = TrayService( onShow: _showWindow, onHide: _hideWindow, onQuit: _quitFromTray, ); await _trayService!.init(); } Future _showWindow() async { await windowManager.show(); await windowManager.focus(); } Future _hideWindow() async { await windowManager.hide(); } Future _quitFromTray() async { if (_isQuitting) { return; } _isQuitting = true; await widget.vpnService.stop(); await windowManager.setPreventClose(false); await windowManager.close(); } Future _handleWindowClose() async { if (_isQuitting) { await windowManager.setPreventClose(false); return; } await _hideWindow(); } @override void dispose() { if (_isLinuxDesktop) { windowManager.removeListener(_windowListener); unawaited(_trayService?.dispose()); } widget.vpnService.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return MaterialApp( theme: AppTheme.light, darkTheme: AppTheme.dark, themeMode: _themeMode, home: MainView(model: widget.model, vpnService: widget.vpnService), ); } } bool get _isLinuxDesktop { return !kIsWeb && Platform.isLinux; } final class _LinuxWindowListener with WindowListener { _LinuxWindowListener(this._state); final _MyAppState _state; @override void onWindowClose() { unawaited(_state._handleWindowClose()); } }