import 'dart:io'; import 'package:PiliPlus/build_config.dart'; import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/back_detector.dart'; import 'package:PiliPlus/common/widgets/custom_toast.dart'; import 'package:PiliPlus/common/widgets/route_aware_mixin.dart'; import 'package:PiliPlus/common/widgets/scale_app.dart'; import 'package:PiliPlus/common/widgets/scroll_behavior.dart'; import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/models/common/theme/theme_color_type.dart'; import 'package:PiliPlus/plugin/pl_player/utils/fullscreen.dart'; import 'package:PiliPlus/router/app_pages.dart'; import 'package:PiliPlus/services/account_service.dart'; import 'package:PiliPlus/services/download/download_service.dart'; import 'package:PiliPlus/services/service_locator.dart'; import 'package:PiliPlus/utils/cache_manager.dart'; import 'package:PiliPlus/utils/calc_window_position.dart'; import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/extension/iterable_ext.dart'; import 'package:PiliPlus/utils/extension/theme_ext.dart'; import 'package:PiliPlus/utils/json_file_handler.dart'; import 'package:PiliPlus/utils/path_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/request_utils.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage_key.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:PiliPlus/utils/theme_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:catcher_2/catcher_2.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:media_kit/media_kit.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; import 'package:window_manager/window_manager.dart' hide calcWindowPosition; WebViewEnvironment? webViewEnvironment; Future _initDownPath() async { if (PlatformUtils.isDesktop) { final customDownPath = Pref.downloadPath; if (customDownPath != null && customDownPath.isNotEmpty) { try { final dir = Directory(customDownPath); if (!dir.existsSync()) { await dir.create(recursive: true); } downloadPath = customDownPath; } catch (e) { downloadPath = defDownloadPath; await GStorage.setting.delete(SettingBoxKey.downloadPath); if (kDebugMode) { debugPrint('download path error: $e'); } } } else { downloadPath = defDownloadPath; } } else if (Platform.isAndroid) { final externalStorageDirPath = (await getExternalStorageDirectory())?.path; downloadPath = externalStorageDirPath != null ? path.join(externalStorageDirPath, PathUtils.downloadDir) : defDownloadPath; } else { downloadPath = defDownloadPath; } } Future _initTmpPath() async { tmpDirPath = (await getTemporaryDirectory()).path; } Future _initAppPath() async { appSupportDirPath = (await getApplicationSupportDirectory()).path; } Future _initSdkInt() async { Utils.sdkInt = (await DeviceInfoPlugin().androidInfo).version.sdkInt; } void main() async { ScaledWidgetsFlutterBinding.ensureInitialized(); MediaKit.ensureInitialized(); await _initAppPath(); try { await GStorage.init(); } catch (e) { await Utils.copyText(e.toString()); if (kDebugMode) debugPrint('GStorage init error: $e'); exit(0); } ScaledWidgetsFlutterBinding.instance.scaleFactor = Pref.uiScale; await Future.wait([_initDownPath(), _initTmpPath()]); Get ..lazyPut(AccountService.new) ..lazyPut(DownloadService.new); HttpOverrides.global = _CustomHttpOverrides(); CacheManager.autoClearCache(); if (PlatformUtils.isMobile) { await Future.wait([ if (Platform.isAndroid) _initSdkInt(), if (Pref.horizontalScreen) ?fullMode() else ?portraitUpMode(), setupServiceLocator(), ]); } else if (Platform.isWindows) { if (await WebViewEnvironment.getAvailableVersion() != null) { webViewEnvironment = await WebViewEnvironment.create( settings: WebViewEnvironmentSettings( userDataFolder: path.join(appSupportDirPath, 'flutter_inappwebview'), ), ); } } else if (Platform.isMacOS) { await setupServiceLocator(); } Request(); Request.setCookie(); RequestUtils.syncHistoryStatus(); SmartDialog.config.toast = SmartConfigToast(displayType: .onlyRefresh); if (PlatformUtils.isMobile) { SystemChrome.setEnabledSystemUIMode(.edgeToEdge); SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( systemNavigationBarColor: Colors.transparent, systemNavigationBarDividerColor: Colors.transparent, statusBarColor: Colors.transparent, systemNavigationBarContrastEnforced: false, ), ); if (Platform.isAndroid) { FlutterDisplayMode.supported.then((mode) { final String? storageDisplay = GStorage.setting.get( SettingBoxKey.displayMode, ); DisplayMode? displayMode; if (storageDisplay != null) { displayMode = mode.firstWhereOrNull( (e) => e.toString() == storageDisplay, ); } FlutterDisplayMode.setPreferredMode(displayMode ?? DisplayMode.auto); }); } } else if (PlatformUtils.isDesktop) { await windowManager.ensureInitialized(); final windowOptions = WindowOptions( minimumSize: const Size(400, 720), skipTaskbar: false, titleBarStyle: Pref.showWindowTitleBar ? TitleBarStyle.normal : TitleBarStyle.hidden, title: Constants.appName, ); windowManager.waitUntilReadyToShow(windowOptions, () async { final windowSize = Pref.windowSize; await windowManager.setBounds( await calcWindowPosition(windowSize) & windowSize, ); if (Pref.isWindowMaximized) await windowManager.maximize(); await windowManager.show(); await windowManager.focus(); }); } if (Pref.dynamicColor) { await MyApp.initPlatformState(); } if (Pref.enableLog) { // 异常捕获 logo记录 final customParameters = { 'BuildConfig': '\nBuild Time: ${DateFormatUtils.format(BuildConfig.buildTime, format: DateFormatUtils.longFormatDs)}\n' 'Commit Hash: ${BuildConfig.commitHash}', }; final fileHandler = await JsonFileHandler.init(); final Catcher2Options debugConfig = Catcher2Options( SilentReportMode(), [ ?fileHandler, ConsoleHandler( enableDeviceParameters: false, enableApplicationParameters: false, enableCustomParameters: true, ), ], customParameters: customParameters, ); final Catcher2Options releaseConfig = Catcher2Options( SilentReportMode(), [ ?fileHandler, ConsoleHandler(enableCustomParameters: true), ], customParameters: customParameters, ); Catcher2( debugConfig: debugConfig, releaseConfig: releaseConfig, rootWidget: const MyApp(), ); } else { runApp(const MyApp()); } } class MyApp extends StatelessWidget { const MyApp({super.key}); static ColorScheme? _light, _dark; static ThemeData? darkThemeData; static void _onBack() { if (SmartDialog.checkExist()) { SmartDialog.dismiss(); return; } final route = Get.routing.route; if (route is GetPageRoute) { if (route.popDisposition == .doNotPop) { route.onPopInvokedWithResult(false, null); return; } } final navigator = Get.key.currentState; if (navigator?.canPop() ?? false) { navigator!.pop(); } } static (ThemeData, ThemeData) getAllTheme() { final dynamicColor = _light != null && _dark != null && Pref.dynamicColor; late final brandColor = colorThemeTypes[Pref.customColor].color; late final variant = Pref.schemeVariant; return ( ThemeUtils.getThemeData( colorScheme: dynamicColor ? _light! : brandColor.asColorSchemeSeed(variant, .light), isDynamic: dynamicColor, ), ThemeUtils.getThemeData( isDark: true, colorScheme: dynamicColor ? _dark! : brandColor.asColorSchemeSeed(variant, .dark), isDynamic: dynamicColor, ), ); } @override Widget build(BuildContext context) { final (light, dark) = getAllTheme(); return GetMaterialApp( title: Constants.appName, theme: light, darkTheme: dark, themeMode: Pref.themeMode, localizationsDelegates: const [ GlobalCupertinoLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ], locale: const Locale("zh", "CN"), fallbackLocale: const Locale("zh", "CN"), supportedLocales: const [Locale("zh", "CN"), Locale("en", "US")], initialRoute: '/', getPages: Routes.getPages, defaultTransition: Pref.pageTransition, builder: FlutterSmartDialog.init( toastBuilder: (msg) => CustomToast(msg: msg), loadingBuilder: (msg) => LoadingWidget(msg: msg), builder: _builder, ), navigatorObservers: [ routeObserver, FlutterSmartDialog.observer, ], scrollBehavior: PlatformUtils.isDesktop ? const CustomScrollBehavior(desktopDragDevices) : null, ); } static Widget _builder(BuildContext context, Widget? child) { final uiScale = Pref.uiScale; final mediaQuery = MediaQuery.of(context); final textScaler = TextScaler.linear(Pref.defaultTextScale); if (uiScale != 1.0) { child = MediaQuery( data: mediaQuery.copyWith( textScaler: textScaler, size: mediaQuery.size / uiScale, padding: mediaQuery.padding / uiScale, viewInsets: mediaQuery.viewInsets / uiScale, viewPadding: mediaQuery.viewPadding / uiScale, devicePixelRatio: mediaQuery.devicePixelRatio * uiScale, ), child: child!, ); } else { child = MediaQuery( data: mediaQuery.copyWith(textScaler: textScaler), child: child!, ); } if (PlatformUtils.isDesktop) { return BackDetector( onBack: _onBack, child: child, ); } return child; } /// from [DynamicColorBuilderState.initPlatformState] static Future initPlatformState() async { if (_light != null || _dark != null) return true; // Platform messages may fail, so we use a try/catch PlatformException. try { final corePalette = await DynamicColorPlugin.getCorePalette(); if (corePalette != null) { if (kDebugMode) { debugPrint('dynamic_color: Core palette detected.'); } _light = corePalette.toColorScheme(); _dark = corePalette.toColorScheme(brightness: Brightness.dark); return true; } } on PlatformException { if (kDebugMode) { debugPrint('dynamic_color: Failed to obtain core palette.'); } } try { final Color? accentColor = await DynamicColorPlugin.getAccentColor(); if (accentColor != null) { if (kDebugMode) { debugPrint('dynamic_color: Accent color detected.'); } final variant = Pref.schemeVariant; _light = accentColor.asColorSchemeSeed(variant, .light); _dark = accentColor.asColorSchemeSeed(variant, .dark); return true; } } on PlatformException { if (kDebugMode) { debugPrint('dynamic_color: Failed to obtain accent color.'); } } if (kDebugMode) { debugPrint('dynamic_color: Dynamic color not detected on this device.'); } GStorage.setting.put(SettingBoxKey.dynamicColor, false); return false; } } class _CustomHttpOverrides extends HttpOverrides { @override HttpClient createHttpClient(SecurityContext? context) { final client = super.createHttpClient(context); // ..maxConnectionsPerHost = 32 /// The default value is 15 seconds. // ..idleTimeout = const Duration(seconds: 15); if (kDebugMode || Pref.badCertificateCallback) { client.badCertificateCallback = (cert, host, port) => true; } return client; } }