import 'dart:async'; import 'package:desktop_webview_window/desktop_webview_window.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:rhttp/rhttp.dart'; import 'package:venera/foundation/log.dart'; import 'package:venera/pages/auth_page.dart'; import 'package:venera/pages/comic_source_page.dart'; import 'package:venera/pages/main_page.dart'; import 'package:venera/pages/settings/settings_page.dart'; import 'package:venera/utils/app_links.dart'; import 'package:window_manager/window_manager.dart'; import 'components/components.dart'; import 'components/window_frame.dart'; import 'foundation/app.dart'; import 'foundation/appdata.dart'; import 'init.dart'; void main(List args) { if (runWebViewTitleBarWidget(args)) { return; } runZonedGuarded(() async { await Rhttp.init(); WidgetsFlutterBinding.ensureInitialized(); await init(); if (App.isAndroid) { handleLinks(); } FlutterError.onError = (details) { Log.error( "Unhandled Exception", "${details.exception}\n${details.stack}"); }; runApp(const MyApp()); if (App.isDesktop) { await windowManager.ensureInitialized(); windowManager.waitUntilReadyToShow().then((_) async { await windowManager.setTitleBarStyle( TitleBarStyle.hidden, windowButtonVisibility: App.isMacOS, ); if (App.isLinux) { await windowManager.setBackgroundColor(Colors.transparent); } await windowManager.setMinimumSize(const Size(500, 600)); if (!App.isLinux) { // https://github.com/leanflutter/window_manager/issues/460 var placement = await WindowPlacement.loadFromFile(); await placement.applyToWindow(); await windowManager.show(); WindowPlacement.loop(); } }); } }, (error, stack) { Log.error("Unhandled Exception", "$error\n$stack"); }); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override State createState() => _MyAppState(); } class _MyAppState extends State with WidgetsBindingObserver { @override void initState() { checkUpdates(); App.registerForceRebuild(forceRebuild); SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); WidgetsBinding.instance.addObserver(this); super.initState(); } bool isAuthPageActive = false; OverlayEntry? hideContentOverlay; @override void didChangeAppLifecycleState(AppLifecycleState state) { if(!App.isMobile) { return; } if (state == AppLifecycleState.inactive && hideContentOverlay == null) { hideContentOverlay = OverlayEntry( builder: (context) { return Positioned.fill( child: Container( width: double.infinity, height: double.infinity, color: App.rootContext.colorScheme.surface, ), ); }, ); Overlay.of(App.rootContext).insert(hideContentOverlay!); } else if (hideContentOverlay != null && state == AppLifecycleState.resumed) { hideContentOverlay!.remove(); hideContentOverlay = null; } if (state == AppLifecycleState.hidden && appdata.settings['authorizationRequired'] && !isAuthPageActive) { isAuthPageActive = true; App.rootContext.to( () => AuthPage( onSuccessfulAuth: () { App.rootContext.pop(); isAuthPageActive = false; }, ), ); } super.didChangeAppLifecycleState(state); } void forceRebuild() { void rebuild(Element el) { el.markNeedsBuild(); el.visitChildren(rebuild); } (context as Element).visitChildren(rebuild); setState(() {}); } @override Widget build(BuildContext context) { Widget home; if (appdata.settings['authorizationRequired']) { home = AuthPage( onSuccessfulAuth: () { App.rootContext.toReplacement(() => const MainPage()); }, ); } else { home = const MainPage(); } return MaterialApp( home: home, debugShowCheckedModeBanner: false, theme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: App.mainColor, surface: Colors.white, primary: App.mainColor.shade600, // ignore: deprecated_member_use background: Colors.white, ), fontFamily: App.isWindows ? "Microsoft YaHei" : null, ), navigatorKey: App.rootNavigatorKey, darkTheme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: App.mainColor, brightness: Brightness.dark, surface: Colors.black, primary: App.mainColor.shade400, // ignore: deprecated_member_use background: Colors.black, ), fontFamily: App.isWindows ? "Microsoft YaHei" : null, ), themeMode: switch (appdata.settings['theme_mode']) { 'light' => ThemeMode.light, 'dark' => ThemeMode.dark, _ => ThemeMode.system }, localizationsDelegates: const [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], locale: () { var lang = appdata.settings['language']; if (lang == 'system') { return null; } return switch (lang) { 'zh-CN' => const Locale('zh', 'CN'), 'zh-TW' => const Locale('zh', 'TW'), 'en-US' => const Locale('en'), _ => null }; }(), supportedLocales: const [ Locale('en'), Locale('zh', 'CN'), Locale('zh', 'TW'), ], builder: (context, widget) { ErrorWidget.builder = (details) { Log.error( "Unhandled Exception", "${details.exception}\n${details.stack}"); return Material( child: Center( child: Text(details.exception.toString()), ), ); }; if (widget != null) { widget = OverlayWidget(widget); if (App.isDesktop) { widget = Shortcuts( shortcuts: { LogicalKeySet(LogicalKeyboardKey.escape): VoidCallbackIntent( App.pop, ), }, child: MouseBackDetector( onTapDown: App.pop, child: WindowFrame(widget), ), ); } return _SystemUiProvider(Material( child: widget, )); } throw ('widget is null'); }, ); } void checkUpdates() async { if (!appdata.settings['checkUpdateOnStart']) { return; } var lastCheck = appdata.implicitData['lastCheckUpdate'] ?? 0; var now = DateTime.now().millisecondsSinceEpoch; if (now - lastCheck < 24 * 60 * 60 * 1000) { return; } appdata.implicitData['lastCheckUpdate'] = now; appdata.writeImplicitData(); await Future.delayed(const Duration(milliseconds: 300)); await checkUpdateUi(false); await ComicSourcePage.checkComicSourceUpdate(true); } } class _SystemUiProvider extends StatelessWidget { const _SystemUiProvider(this.child); final Widget child; @override Widget build(BuildContext context) { var brightness = Theme.of(context).brightness; SystemUiOverlayStyle systemUiStyle; if (brightness == Brightness.light) { systemUiStyle = SystemUiOverlayStyle.dark.copyWith( statusBarColor: Colors.transparent, systemNavigationBarColor: Colors.transparent, systemNavigationBarIconBrightness: Brightness.dark, ); } else { systemUiStyle = SystemUiOverlayStyle.light.copyWith( statusBarColor: Colors.transparent, systemNavigationBarColor: Colors.transparent, systemNavigationBarIconBrightness: Brightness.light, ); } return AnnotatedRegion( value: systemUiStyle, child: child, ); } }