From f5f3e4753cbf27033ce68441763f09bf49e6d351 Mon Sep 17 00:00:00 2001 From: xkeyC <3334969096@qq.com> Date: Sun, 16 Mar 2025 17:14:45 +0800 Subject: [PATCH] feat: desktop_multi_window Support --- lib/app.dart | 75 ++---- lib/common/utils/base_utils.dart | 83 +++--- lib/common/utils/multi_window_manager.dart | 108 ++++++++ .../utils/multi_window_manager.freezed.dart | 255 ++++++++++++++++++ lib/common/utils/multi_window_manager.g.dart | 27 ++ lib/main.dart | 5 + .../log_analyze_ui/log_analyze_provider.dart | 12 + .../log_analyze_provider.g.dart | 26 ++ .../tools/log_analyze_ui/log_analyze_ui.dart | 18 ++ lib/ui/tools/tools_ui_model.dart | 128 ++++----- lib/ui/tools/tools_ui_model.g.dart | 2 +- linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.yaml | 1 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 17 files changed, 588 insertions(+), 163 deletions(-) create mode 100644 lib/common/utils/multi_window_manager.dart create mode 100644 lib/common/utils/multi_window_manager.freezed.dart create mode 100644 lib/common/utils/multi_window_manager.g.dart create mode 100644 lib/ui/tools/log_analyze_ui/log_analyze_provider.dart create mode 100644 lib/ui/tools/log_analyze_ui/log_analyze_provider.g.dart create mode 100644 lib/ui/tools/log_analyze_ui/log_analyze_ui.dart diff --git a/lib/app.dart b/lib/app.dart index 9551c39..dd484f4 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -58,45 +58,35 @@ GoRouter router(Ref ref) { routes: [ GoRoute( path: '/', - pageBuilder: (context, state) => - myPageBuilder(context, state, const SplashUI()), + pageBuilder: (context, state) => myPageBuilder(context, state, const SplashUI()), ), GoRoute( path: '/index', - pageBuilder: (context, state) => - myPageBuilder(context, state, const IndexUI()), + pageBuilder: (context, state) => myPageBuilder(context, state, const IndexUI()), routes: [ GoRoute( path: "downloader", - pageBuilder: (context, state) => - myPageBuilder(context, state, const HomeDownloaderUI())), + pageBuilder: (context, state) => myPageBuilder(context, state, const HomeDownloaderUI())), GoRoute( path: 'game_doctor', - pageBuilder: (context, state) => - myPageBuilder(context, state, const HomeGameDoctorUI()), + pageBuilder: (context, state) => myPageBuilder(context, state, const HomeGameDoctorUI()), ), GoRoute( path: 'performance', - pageBuilder: (context, state) => - myPageBuilder(context, state, const HomePerformanceUI()), + pageBuilder: (context, state) => myPageBuilder(context, state, const HomePerformanceUI()), ), GoRoute( path: 'advanced_localization', - pageBuilder: (context, state) => - myPageBuilder(context, state, const AdvancedLocalizationUI())) + pageBuilder: (context, state) => myPageBuilder(context, state, const AdvancedLocalizationUI())) ], ), GoRoute(path: '/tools', builder: (_, __) => const SizedBox(), routes: [ GoRoute( path: 'unp4kc', - pageBuilder: (context, state) => - myPageBuilder(context, state, const UnP4kcUI()), + pageBuilder: (context, state) => myPageBuilder(context, state, const UnP4kcUI()), ), ]), - GoRoute( - path: '/guide', - pageBuilder: (context, state) => - myPageBuilder(context, state, const GuideUI())) + GoRoute(path: '/guide', pageBuilder: (context, state) => myPageBuilder(context, state, const GuideUI())) ], ); } @@ -218,11 +208,9 @@ class AppGlobalModel extends _$AppGlobalModel { AppConf.setNetworkChannels(networkVersionData.gameChannels); checkActivityThemeColor(networkVersionData); if (ConstConf.isMSE) { - dPrint( - "lastVersion=${networkVersionData.mSELastVersion} ${networkVersionData.mSELastVersionCode}"); + dPrint("lastVersion=${networkVersionData.mSELastVersion} ${networkVersionData.mSELastVersionCode}"); } else { - dPrint( - "lastVersion=${networkVersionData.lastVersion} ${networkVersionData.lastVersionCode}"); + dPrint("lastVersion=${networkVersionData.lastVersion} ${networkVersionData.lastVersionCode}"); } state = state.copyWith(networkVersionData: networkVersionData); } catch (e) { @@ -234,23 +222,18 @@ class AppGlobalModel extends _$AppGlobalModel { if (state.networkVersionData == null) { if (!context.mounted) return false; await showToast( - context, - S.current.app_common_network_error( - ConstConf.appVersionDate, checkUpdateError.toString())); + context, S.current.app_common_network_error(ConstConf.appVersionDate, checkUpdateError.toString())); return false; } if (!Platform.isWindows) return false; - final lastVersion = ConstConf.isMSE - ? state.networkVersionData?.mSELastVersionCode - : state.networkVersionData?.lastVersionCode; + final lastVersion = + ConstConf.isMSE ? state.networkVersionData?.mSELastVersionCode : state.networkVersionData?.lastVersionCode; if ((lastVersion ?? 0) > ConstConf.appVersionCode) { // need update if (!context.mounted) return false; - final r = await showDialog( - dismissWithEsc: false, - context: context, - builder: (context) => const UpgradeDialogUI()); + final r = + await showDialog(dismissWithEsc: false, context: context, builder: (context) => const UpgradeDialogUI()); if (r != true) { if (!context.mounted) return false; @@ -277,8 +260,8 @@ class AppGlobalModel extends _$AppGlobalModel { dPrint("now == $now start == $startTime end == $endTime"); if (now < startTime) { - _activityThemeColorTimer = Timer(Duration(milliseconds: startTime - now), - () => checkActivityThemeColor(networkVersionData)); + _activityThemeColorTimer = + Timer(Duration(milliseconds: startTime - now), () => checkActivityThemeColor(networkVersionData)); dPrint("start Timer ...."); } else if (now >= startTime && now <= endTime) { dPrint("update Color ...."); @@ -286,17 +269,15 @@ class AppGlobalModel extends _$AppGlobalModel { final colorCfg = networkVersionData.activityColors; state = state.copyWith( themeConf: ThemeConf( - backgroundColor: HexColor(colorCfg?.background ?? "#132431") - .withValues(alpha: .75), - menuColor: - HexColor(colorCfg?.menu ?? "#132431").withValues(alpha: .95), + backgroundColor: HexColor(colorCfg?.background ?? "#132431").withValues(alpha: .75), + menuColor: HexColor(colorCfg?.menu ?? "#132431").withValues(alpha: .95), micaColor: HexColor(colorCfg?.mica ?? "#0A3142"), ), ); // wait for end - _activityThemeColorTimer = Timer(Duration(milliseconds: endTime - now), - () => checkActivityThemeColor(networkVersionData)); + _activityThemeColorTimer = + Timer(Duration(milliseconds: endTime - now), () => checkActivityThemeColor(networkVersionData)); } else { dPrint("reset Color ...."); state = state.copyWith( @@ -317,9 +298,8 @@ class AppGlobalModel extends _$AppGlobalModel { await appConfBox.put("app_locale", null); return; } - final localeCode = value.countryCode != null - ? "${value.languageCode}_${value.countryCode ?? ""}" - : value.languageCode; + final localeCode = + value.countryCode != null ? "${value.languageCode}_${value.countryCode ?? ""}" : value.languageCode; dPrint("changeLocale == $value localeCode=== $localeCode"); await appConfBox.put("app_locale", localeCode); state = state.copyWith(appLocale: value); @@ -329,8 +309,7 @@ class AppGlobalModel extends _$AppGlobalModel { Future _initAppDir() async { if (Platform.isWindows) { final userProfileDir = Platform.environment["USERPROFILE"]; - final applicationSupportDir = - (await getApplicationSupportDirectory()).absolute.path; + final applicationSupportDir = (await getApplicationSupportDirectory()).absolute.path; String? applicationBinaryModuleDir; try { await initDPrintFile(applicationSupportDir); @@ -338,8 +317,7 @@ class AppGlobalModel extends _$AppGlobalModel { dPrint("initDPrintFile Error: $e"); } if (ConstConf.isMSE && userProfileDir != null) { - applicationBinaryModuleDir = - "$userProfileDir\\AppData\\Local\\Temp\\SCToolbox\\modules"; + applicationBinaryModuleDir = "$userProfileDir\\AppData\\Local\\Temp\\SCToolbox\\modules"; } else { applicationBinaryModuleDir = "$applicationSupportDir\\modules"; } @@ -351,8 +329,7 @@ class AppGlobalModel extends _$AppGlobalModel { ); return applicationSupportDir; } else { - final applicationSupportDir = - (await getApplicationSupportDirectory()).absolute.path; + final applicationSupportDir = (await getApplicationSupportDirectory()).absolute.path; final applicationBinaryModuleDir = "$applicationSupportDir/modules"; dPrint("applicationSupportDir == $applicationSupportDir"); dPrint("applicationBinaryModuleDir == $applicationBinaryModuleDir"); diff --git a/lib/common/utils/base_utils.dart b/lib/common/utils/base_utils.dart index e180355..7a60871 100644 --- a/lib/common/utils/base_utils.dart +++ b/lib/common/utils/base_utils.dart @@ -7,16 +7,14 @@ import 'dart:ui' as ui; import 'package:flutter/services.dart'; import 'package:starcitizen_doctor/generated/l10n.dart'; -Future showToast(BuildContext context, String msg, - {BoxConstraints? constraints, String? title}) async { +Future showToast(BuildContext context, String msg, {BoxConstraints? constraints, String? title}) async { return showBaseDialog(context, title: title ?? S.current.app_common_tip, content: Text(msg), actions: [ FilledButton( child: Padding( - padding: - const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8), + padding: const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8), child: Text(S.current.app_common_tip_i_know), ), onPressed: () => Navigator.pop(context), @@ -25,11 +23,8 @@ Future showToast(BuildContext context, String msg, constraints: constraints); } -Future showConfirmDialogs( - BuildContext context, String title, Widget content, - {String confirm = "", - String cancel = "", - BoxConstraints? constraints}) async { +Future showConfirmDialogs(BuildContext context, String title, Widget content, + {String confirm = "", String cancel = "", BoxConstraints? constraints}) async { if (confirm.isEmpty) confirm = S.current.app_common_tip_confirm; if (cancel.isEmpty) cancel = S.current.app_common_tip_cancel; @@ -40,8 +35,7 @@ Future showConfirmDialogs( if (confirm.isNotEmpty) FilledButton( child: Padding( - padding: - const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8), + padding: const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8), child: Text(confirm), ), onPressed: () => Navigator.pop(context, true), @@ -49,8 +43,7 @@ Future showConfirmDialogs( if (cancel.isNotEmpty) Button( child: Padding( - padding: - const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8), + padding: const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8), child: Text(cancel), ), onPressed: () => Navigator.pop(context, false), @@ -62,13 +55,15 @@ Future showConfirmDialogs( Future showInputDialogs(BuildContext context, {required String title, - required String content, - BoxConstraints? constraints, - String? initialValue, - List? inputFormatters}) async { + required String content, + BoxConstraints? constraints, + String? initialValue, + List? inputFormatters}) async { String? userInput; - constraints ??= - BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .38); + constraints ??= BoxConstraints(maxWidth: MediaQuery + .of(context) + .size + .width * .38); final ok = await showConfirmDialogs( context, title, @@ -97,22 +92,20 @@ Future showInputDialogs(BuildContext context, } Future showBaseDialog(BuildContext context, - {required String title, - required Widget content, - List? actions, - BoxConstraints? constraints}) async { + {required String title, required Widget content, List? actions, BoxConstraints? constraints}) async { return await showDialog( context: context, - builder: (context) => ContentDialog( - title: Text(title), - content: content, - constraints: constraints ?? - const BoxConstraints( - maxWidth: 512, - maxHeight: 756.0, - ), - actions: actions, - ), + builder: (context) => + ContentDialog( + title: Text(title), + content: content, + constraints: constraints ?? + const BoxConstraints( + maxWidth: 512, + maxHeight: 756.0, + ), + actions: actions, + ), ); } @@ -120,10 +113,8 @@ bool stringIsNotEmpty(String? s) { return s != null && (s.isNotEmpty); } -Future widgetToPngImage(GlobalKey repaintBoundaryKey, - {double pixelRatio = 3.0}) async { - RenderRepaintBoundary? boundary = repaintBoundaryKey.currentContext - ?.findRenderObject() as RenderRepaintBoundary?; +Future widgetToPngImage(GlobalKey repaintBoundaryKey, {double pixelRatio = 3.0}) async { + RenderRepaintBoundary? boundary = repaintBoundaryKey.currentContext?.findRenderObject() as RenderRepaintBoundary?; if (boundary == null) return null; ui.Image image = await boundary.toImage(pixelRatio: pixelRatio); @@ -133,11 +124,25 @@ Future widgetToPngImage(GlobalKey repaintBoundaryKey, return pngBytes; } -double roundDoubleTo(double value, double precision) => - (value * precision).round() / precision; +double roundDoubleTo(double value, double precision) => (value * precision).round() / precision; int getMinNumber(List list) { if (list.isEmpty) return 0; list.sort((a, b) => a.compareTo(b)); return list.first; } + +String colorToHexCode(Color color, {ignoreTransparency = false}) { + final colorValue = color.toARGB32(); + final colorAlpha = ((0xff000000 & colorValue) >> 24); + final r = ((0x00ff0000 & colorValue) >> 16).toRadixString(16).padLeft(2, '0'); + final g = ((0x0000ff00 & colorValue) >> 8).toRadixString(16).padLeft(2, '0'); + final b = ((0x000000ff & colorValue) >> 0).toRadixString(16).padLeft(2, '0'); + final a = colorAlpha.toRadixString(16).padLeft(2, '0'); + + if (ignoreTransparency || colorAlpha == 255) { + return '#$r$g$b'; + } else { + return '#$a$r$g$b'; + } +} diff --git a/lib/common/utils/multi_window_manager.dart b/lib/common/utils/multi_window_manager.dart new file mode 100644 index 0000000..ed01cd2 --- /dev/null +++ b/lib/common/utils/multi_window_manager.dart @@ -0,0 +1,108 @@ +import 'dart:convert'; + +import 'package:desktop_multi_window/desktop_multi_window.dart'; +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:hexcolor/hexcolor.dart'; +import 'package:starcitizen_doctor/app.dart'; +import 'package:starcitizen_doctor/ui/tools/log_analyze_ui/log_analyze_ui.dart'; + +import 'base_utils.dart'; + +part 'multi_window_manager.freezed.dart'; + +part 'multi_window_manager.g.dart'; + +@freezed +class MultiWindowAppState with _$MultiWindowAppState { + const factory MultiWindowAppState({ + required String backgroundColor, + required String menuColor, + required String micaColor, + String? languageCode, + String? countryCode, + }) = _MultiWindowAppState; + + factory MultiWindowAppState.fromJson(Map json) => _$MultiWindowAppStateFromJson(json); +} + +class MultiWindowManager { + static Future launchSubWindow(String type, AppGlobalState appGlobalState) async { + final window = await DesktopMultiWindow.createWindow(jsonEncode({ + 'window_type': type, + 'app_state': _appStateToWindowState(appGlobalState).toJson(), + })); + window.setTitle("Log 分析器"); + await window.center(); + await window.show(); + // sendAppStateBroadcast(appGlobalState); + } + + static sendAppStateBroadcast(AppGlobalState appGlobalState) { + DesktopMultiWindow.invokeMethod( + 0, + 'app_state_broadcast', + _appStateToWindowState(appGlobalState).toJson(), + ); + } + + static MultiWindowAppState _appStateToWindowState(AppGlobalState appGlobalState) { + return MultiWindowAppState( + backgroundColor: colorToHexCode(appGlobalState.themeConf.backgroundColor), + menuColor: colorToHexCode(appGlobalState.themeConf.menuColor), + micaColor: colorToHexCode(appGlobalState.themeConf.micaColor), + languageCode: appGlobalState.appLocale?.languageCode, + countryCode: appGlobalState.appLocale?.countryCode, + ); + } + + static void runSubWindowApp(List args) { + final argument = args[2].isEmpty ? const {} : jsonDecode(args[2]) as Map; + Widget? windowWidget; + switch (argument["window_type"]) { + case "log_analyze": + windowWidget = const ToolsLogAnalyzeDialogUI(); + break; + default: + throw Exception('Unknown window type'); + } + + final windowAppState = MultiWindowAppState.fromJson(argument['app_state'] ?? {}); + + return runApp(ProviderScope( + child: FluentApp( + title: "StarCitizenToolBox", + restorationScopeId: "StarCitizenToolBox", + themeMode: ThemeMode.dark, + localizationsDelegates: const [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + FluentLocalizations.delegate, + ], + supportedLocales: const [Locale('en', 'US')], + home: windowWidget, + theme: FluentThemeData( + brightness: Brightness.dark, + fontFamily: "SourceHanSansCN-Regular", + navigationPaneTheme: NavigationPaneThemeData( + backgroundColor: HexColor(windowAppState.backgroundColor), + ), + menuColor: HexColor(windowAppState.menuColor), + micaBackgroundColor: HexColor(windowAppState.micaColor), + buttonTheme: ButtonThemeData( + defaultButtonStyle: ButtonStyle( + shape: WidgetStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), + side: BorderSide(color: Colors.white.withValues(alpha: .01)))), + ))), + locale: windowAppState.languageCode != null + ? Locale(windowAppState.languageCode!, windowAppState.countryCode) + : null, + debugShowCheckedModeBanner: false, + ), + )); + } +} diff --git a/lib/common/utils/multi_window_manager.freezed.dart b/lib/common/utils/multi_window_manager.freezed.dart new file mode 100644 index 0000000..58256d4 --- /dev/null +++ b/lib/common/utils/multi_window_manager.freezed.dart @@ -0,0 +1,255 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'multi_window_manager.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +MultiWindowAppState _$MultiWindowAppStateFromJson(Map json) { + return _MultiWindowAppState.fromJson(json); +} + +/// @nodoc +mixin _$MultiWindowAppState { + String get backgroundColor => throw _privateConstructorUsedError; + String get menuColor => throw _privateConstructorUsedError; + String get micaColor => throw _privateConstructorUsedError; + String? get languageCode => throw _privateConstructorUsedError; + String? get countryCode => throw _privateConstructorUsedError; + + /// Serializes this MultiWindowAppState to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of MultiWindowAppState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $MultiWindowAppStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $MultiWindowAppStateCopyWith<$Res> { + factory $MultiWindowAppStateCopyWith( + MultiWindowAppState value, $Res Function(MultiWindowAppState) then) = + _$MultiWindowAppStateCopyWithImpl<$Res, MultiWindowAppState>; + @useResult + $Res call( + {String backgroundColor, + String menuColor, + String micaColor, + String? languageCode, + String? countryCode}); +} + +/// @nodoc +class _$MultiWindowAppStateCopyWithImpl<$Res, $Val extends MultiWindowAppState> + implements $MultiWindowAppStateCopyWith<$Res> { + _$MultiWindowAppStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of MultiWindowAppState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? backgroundColor = null, + Object? menuColor = null, + Object? micaColor = null, + Object? languageCode = freezed, + Object? countryCode = freezed, + }) { + return _then(_value.copyWith( + backgroundColor: null == backgroundColor + ? _value.backgroundColor + : backgroundColor // ignore: cast_nullable_to_non_nullable + as String, + menuColor: null == menuColor + ? _value.menuColor + : menuColor // ignore: cast_nullable_to_non_nullable + as String, + micaColor: null == micaColor + ? _value.micaColor + : micaColor // ignore: cast_nullable_to_non_nullable + as String, + languageCode: freezed == languageCode + ? _value.languageCode + : languageCode // ignore: cast_nullable_to_non_nullable + as String?, + countryCode: freezed == countryCode + ? _value.countryCode + : countryCode // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$MultiWindowAppStateImplCopyWith<$Res> + implements $MultiWindowAppStateCopyWith<$Res> { + factory _$$MultiWindowAppStateImplCopyWith(_$MultiWindowAppStateImpl value, + $Res Function(_$MultiWindowAppStateImpl) then) = + __$$MultiWindowAppStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String backgroundColor, + String menuColor, + String micaColor, + String? languageCode, + String? countryCode}); +} + +/// @nodoc +class __$$MultiWindowAppStateImplCopyWithImpl<$Res> + extends _$MultiWindowAppStateCopyWithImpl<$Res, _$MultiWindowAppStateImpl> + implements _$$MultiWindowAppStateImplCopyWith<$Res> { + __$$MultiWindowAppStateImplCopyWithImpl(_$MultiWindowAppStateImpl _value, + $Res Function(_$MultiWindowAppStateImpl) _then) + : super(_value, _then); + + /// Create a copy of MultiWindowAppState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? backgroundColor = null, + Object? menuColor = null, + Object? micaColor = null, + Object? languageCode = freezed, + Object? countryCode = freezed, + }) { + return _then(_$MultiWindowAppStateImpl( + backgroundColor: null == backgroundColor + ? _value.backgroundColor + : backgroundColor // ignore: cast_nullable_to_non_nullable + as String, + menuColor: null == menuColor + ? _value.menuColor + : menuColor // ignore: cast_nullable_to_non_nullable + as String, + micaColor: null == micaColor + ? _value.micaColor + : micaColor // ignore: cast_nullable_to_non_nullable + as String, + languageCode: freezed == languageCode + ? _value.languageCode + : languageCode // ignore: cast_nullable_to_non_nullable + as String?, + countryCode: freezed == countryCode + ? _value.countryCode + : countryCode // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$MultiWindowAppStateImpl implements _MultiWindowAppState { + const _$MultiWindowAppStateImpl( + {required this.backgroundColor, + required this.menuColor, + required this.micaColor, + this.languageCode, + this.countryCode}); + + factory _$MultiWindowAppStateImpl.fromJson(Map json) => + _$$MultiWindowAppStateImplFromJson(json); + + @override + final String backgroundColor; + @override + final String menuColor; + @override + final String micaColor; + @override + final String? languageCode; + @override + final String? countryCode; + + @override + String toString() { + return 'MultiWindowAppState(backgroundColor: $backgroundColor, menuColor: $menuColor, micaColor: $micaColor, languageCode: $languageCode, countryCode: $countryCode)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$MultiWindowAppStateImpl && + (identical(other.backgroundColor, backgroundColor) || + other.backgroundColor == backgroundColor) && + (identical(other.menuColor, menuColor) || + other.menuColor == menuColor) && + (identical(other.micaColor, micaColor) || + other.micaColor == micaColor) && + (identical(other.languageCode, languageCode) || + other.languageCode == languageCode) && + (identical(other.countryCode, countryCode) || + other.countryCode == countryCode)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, backgroundColor, menuColor, + micaColor, languageCode, countryCode); + + /// Create a copy of MultiWindowAppState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$MultiWindowAppStateImplCopyWith<_$MultiWindowAppStateImpl> get copyWith => + __$$MultiWindowAppStateImplCopyWithImpl<_$MultiWindowAppStateImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$MultiWindowAppStateImplToJson( + this, + ); + } +} + +abstract class _MultiWindowAppState implements MultiWindowAppState { + const factory _MultiWindowAppState( + {required final String backgroundColor, + required final String menuColor, + required final String micaColor, + final String? languageCode, + final String? countryCode}) = _$MultiWindowAppStateImpl; + + factory _MultiWindowAppState.fromJson(Map json) = + _$MultiWindowAppStateImpl.fromJson; + + @override + String get backgroundColor; + @override + String get menuColor; + @override + String get micaColor; + @override + String? get languageCode; + @override + String? get countryCode; + + /// Create a copy of MultiWindowAppState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$MultiWindowAppStateImplCopyWith<_$MultiWindowAppStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/common/utils/multi_window_manager.g.dart b/lib/common/utils/multi_window_manager.g.dart new file mode 100644 index 0000000..a0ca374 --- /dev/null +++ b/lib/common/utils/multi_window_manager.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'multi_window_manager.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$MultiWindowAppStateImpl _$$MultiWindowAppStateImplFromJson( + Map json) => + _$MultiWindowAppStateImpl( + backgroundColor: json['backgroundColor'] as String, + menuColor: json['menuColor'] as String, + micaColor: json['micaColor'] as String, + languageCode: json['languageCode'] as String?, + countryCode: json['countryCode'] as String?, + ); + +Map _$$MultiWindowAppStateImplToJson( + _$MultiWindowAppStateImpl instance) => + { + 'backgroundColor': instance.backgroundColor, + 'menuColor': instance.menuColor, + 'micaColor': instance.micaColor, + 'languageCode': instance.languageCode, + 'countryCode': instance.countryCode, + }; diff --git a/lib/main.dart b/lib/main.dart index b3ffea4..0bdad4b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,6 +7,7 @@ import 'package:window_manager/window_manager.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'app.dart'; +import 'common/utils/multi_window_manager.dart'; void main(List args) async { // webview window @@ -14,6 +15,10 @@ void main(List args) async { backgroundColor: const Color.fromRGBO(19, 36, 49, 1), builder: _defaultWebviewTitleBar)) { return; } + if (args.firstOrNull == 'multi_window') { + MultiWindowManager.runSubWindowApp(args); + return; + } WidgetsFlutterBinding.ensureInitialized(); await _initWindow(); // run app diff --git a/lib/ui/tools/log_analyze_ui/log_analyze_provider.dart b/lib/ui/tools/log_analyze_ui/log_analyze_provider.dart new file mode 100644 index 0000000..2a2d1cc --- /dev/null +++ b/lib/ui/tools/log_analyze_ui/log_analyze_provider.dart @@ -0,0 +1,12 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'log_analyze_provider.g.dart'; + + +@riverpod +class ToolsLogAnalyze extends _$ToolsLogAnalyze { + @override + void build() async { + return; + } +} diff --git a/lib/ui/tools/log_analyze_ui/log_analyze_provider.g.dart b/lib/ui/tools/log_analyze_ui/log_analyze_provider.g.dart new file mode 100644 index 0000000..698374c --- /dev/null +++ b/lib/ui/tools/log_analyze_ui/log_analyze_provider.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'log_analyze_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$toolsLogAnalyzeHash() => r'a31922fe5ee020b06e8d494486c39bdd261af34c'; + +/// See also [ToolsLogAnalyze]. +@ProviderFor(ToolsLogAnalyze) +final toolsLogAnalyzeProvider = + AutoDisposeNotifierProvider.internal( + ToolsLogAnalyze.new, + name: r'toolsLogAnalyzeProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$toolsLogAnalyzeHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$ToolsLogAnalyze = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/ui/tools/log_analyze_ui/log_analyze_ui.dart b/lib/ui/tools/log_analyze_ui/log_analyze_ui.dart new file mode 100644 index 0000000..0512801 --- /dev/null +++ b/lib/ui/tools/log_analyze_ui/log_analyze_ui.dart @@ -0,0 +1,18 @@ +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class ToolsLogAnalyzeDialogUI extends HookConsumerWidget { + const ToolsLogAnalyzeDialogUI({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return ScaffoldPage( + header: const PageHeader( + title: Text("Log 分析器"), + ), + content: Column( + children: [], + ), + ); + } +} diff --git a/lib/ui/tools/tools_ui_model.dart b/lib/ui/tools/tools_ui_model.dart index 748d0b2..1ab42ed 100644 --- a/lib/ui/tools/tools_ui_model.dart +++ b/lib/ui/tools/tools_ui_model.dart @@ -16,6 +16,7 @@ import 'package:starcitizen_doctor/common/helper/log_helper.dart'; import 'package:starcitizen_doctor/common/helper/system_helper.dart'; import 'package:starcitizen_doctor/common/io/rs_http.dart'; import 'package:starcitizen_doctor/common/utils/log.dart'; +import 'package:starcitizen_doctor/common/utils/multi_window_manager.dart'; import 'package:starcitizen_doctor/common/utils/provider.dart'; import 'package:starcitizen_doctor/provider/aria2c.dart'; import 'package:starcitizen_doctor/ui/home/downloader/home_downloader_ui_model.dart'; @@ -90,6 +91,13 @@ class ToolsUIModel extends _$ToolsUIModel { const Icon(FluentIcons.virtual_network, size: 24), onTap: () => _doHostsBooster(context), ), + ToolsItemData( + "log_analyze", + "log 分析器", + "分析您的游玩记录 (登录、死亡、击杀 等信息)", + Icon(FluentIcons.analytics_logo), + onTap: () => _showLogAnalyze(context), + ), ToolsItemData( "rsilauncher_enhance_mod", S.current.tools_rsi_launcher_enhance_title, @@ -167,9 +175,8 @@ class ToolsUIModel extends _$ToolsUIModel { ToolsItemData( "remove_nvme_settings", S.current.tools_action_remove_nvme_registry_patch, - S.current.tools_action_info_nvme_patch_issue(nvmePatchStatus - ? S.current.localization_info_installed - : S.current.tools_action_info_not_installed), + S.current.tools_action_info_nvme_patch_issue( + nvmePatchStatus ? S.current.localization_info_installed : S.current.tools_action_info_not_installed), const Icon(FluentIcons.hard_drive, size: 24), onTap: nvmePatchStatus ? () async { @@ -177,8 +184,7 @@ class ToolsUIModel extends _$ToolsUIModel { await SystemHelper.doRemoveNvmePath(); state = state.copyWith(working: false); if (!context.mounted) return; - showToast(context, - S.current.tools_action_info_removed_restart_effective); + showToast(context, S.current.tools_action_info_removed_restart_effective); loadToolsCard(context, skipPathScan: true); } : null, @@ -194,8 +200,7 @@ class ToolsUIModel extends _$ToolsUIModel { final r = await SystemHelper.addNvmePatch(); if (r == "") { if (!context.mounted) return; - showToast( - context, S.current.tools_action_info_fix_success_restart); + showToast(context, S.current.tools_action_info_fix_success_restart); } else { if (!context.mounted) return; showToast(context, S.current.doctor_action_result_fix_fail(r)); @@ -209,11 +214,11 @@ class ToolsUIModel extends _$ToolsUIModel { Future _addShaderCard(BuildContext context) async { final gameShaderCachePath = await SCLoggerHelper.getShaderCachePath(); - final shaderSize = ((await SystemHelper.getDirLen(gameShaderCachePath ?? "", - skipPath: ["$gameShaderCachePath\\Crashes"])) / - 1024 / - 1024) - .toStringAsFixed(4); + final shaderSize = + ((await SystemHelper.getDirLen(gameShaderCachePath ?? "", skipPath: ["$gameShaderCachePath\\Crashes"])) / + 1024 / + 1024) + .toStringAsFixed(4); return ToolsItemData( "clean_shaders", S.current.tools_action_clear_shader_cache, @@ -229,12 +234,8 @@ class ToolsUIModel extends _$ToolsUIModel { return ToolsItemData( "photography_mode", - isEnable - ? S.current.tools_action_close_photography_mode - : S.current.tools_action_open_photography_mode, - isEnable - ? S.current.tools_action_info_restore_lens_shake - : S.current.tools_action_info_one_key_close_lens_shake, + isEnable ? S.current.tools_action_close_photography_mode : S.current.tools_action_open_photography_mode, + isEnable ? S.current.tools_action_info_restore_lens_shake : S.current.tools_action_info_one_key_close_lens_shake, const Icon(FontAwesomeIcons.camera, size: 24), onTap: () => _onChangePhotographyMode(context, isEnable), ); @@ -245,8 +246,7 @@ class ToolsUIModel extends _$ToolsUIModel { /// ----------------------------------------------------------------------------------------- /// ----------------------------------------------------------------------------------------- - Future reScanPath(BuildContext context, - {bool checkActive = false, bool skipToast = false}) async { + Future reScanPath(BuildContext context, {bool checkActive = false, bool skipToast = false}) async { var scInstallPaths = []; var rsiLauncherInstallPaths = []; var scInstalledPath = ""; @@ -298,8 +298,7 @@ class ToolsUIModel extends _$ToolsUIModel { /// 重装EAC Future _reinstallEAC(BuildContext context) async { if (state.scInstalledPath.isEmpty) { - showToast( - context, S.current.tools_action_info_valid_game_directory_needed); + showToast(context, S.current.tools_action_info_valid_game_directory_needed); return; } state = state.copyWith(working: true); @@ -312,8 +311,7 @@ class ToolsUIModel extends _$ToolsUIModel { final Map eacJson = json.decode(eacJsonData); final eacID = eacJson["productid"]; if (eacID != null) { - final eacCacheDir = - Directory("${envVars["appdata"]}\\EasyAntiCheat\\$eacID"); + final eacCacheDir = Directory("${envVars["appdata"]}\\EasyAntiCheat\\$eacID"); if (await eacCacheDir.exists()) { await eacCacheDir.delete(recursive: true); } @@ -323,8 +321,7 @@ class ToolsUIModel extends _$ToolsUIModel { if (await dir.exists()) { await dir.delete(recursive: true); } - final eacLauncher = - File("${state.scInstalledPath}\\StarCitizen_Launcher.exe"); + final eacLauncher = File("${state.scInstalledPath}\\StarCitizen_Launcher.exe"); if (await eacLauncher.exists()) { await eacLauncher.delete(recursive: true); } @@ -350,8 +347,7 @@ class ToolsUIModel extends _$ToolsUIModel { /// 管理员模式运行 RSI 启动器 Future _adminRSILauncher(BuildContext context) async { if (state.rsiLauncherInstalledPath == "") { - showToast(context, - S.current.tools_action_info_rsi_launcher_directory_not_found); + showToast(context, S.current.tools_action_info_rsi_launcher_directory_not_found); } SystemHelper.checkAndLaunchRSILauncher(state.rsiLauncherInstalledPath); } @@ -375,8 +371,7 @@ class ToolsUIModel extends _$ToolsUIModel { actions: [ FilledButton( child: Padding( - padding: - const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8), + padding: const EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8), child: Text(S.current.action_close), ), onPressed: () => Navigator.pop(context), @@ -390,8 +385,7 @@ class ToolsUIModel extends _$ToolsUIModel { Future _cleanShaderCache(BuildContext context) async { state = state.copyWith(working: true); final gameShaderCachePath = await SCLoggerHelper.getShaderCachePath(); - final l = - await Directory(gameShaderCachePath!).list(recursive: false).toList(); + final l = await Directory(gameShaderCachePath!).list(recursive: false).toList(); for (var value in l) { if (value is Directory) { if (!value.absolute.path.contains("Crashes")) { @@ -410,10 +404,8 @@ class ToolsUIModel extends _$ToolsUIModel { if ((await SystemHelper.getPID("\"RSI Launcher\"")).isNotEmpty) { if (!context.mounted) return; - showToast( - context, S.current.tools_action_info_rsi_launcher_running_warning, - constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width * .35)); + showToast(context, S.current.tools_action_info_rsi_launcher_running_warning, + constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .35)); return; } @@ -427,20 +419,15 @@ class ToolsUIModel extends _$ToolsUIModel { try { state = state.copyWith(working: true); final aria2cManager = ref.read(aria2cModelProvider.notifier); - await aria2cManager - .launchDaemon(appGlobalState.applicationBinaryModuleDir!); + await aria2cManager.launchDaemon(appGlobalState.applicationBinaryModuleDir!); final aria2c = ref.read(aria2cModelProvider).aria2c!; // check download task list - for (var value in [ - ...await aria2c.tellActive(), - ...await aria2c.tellWaiting(0, 100000) - ]) { + for (var value in [...await aria2c.tellActive(), ...await aria2c.tellWaiting(0, 100000)]) { final t = HomeDownloaderUIModel.getTaskTypeAndName(value); if (t.key == "torrent" && t.value.contains("Data.p4k")) { if (!context.mounted) return; - showToast( - context, S.current.tools_action_info_p4k_download_in_progress); + showToast(context, S.current.tools_action_info_p4k_download_in_progress); state = state.copyWith(working: false); return; } @@ -449,15 +436,12 @@ class ToolsUIModel extends _$ToolsUIModel { if (torrentUrl == "") { state = state.copyWith(working: false); if (!context.mounted) return; - showToast( - context, S.current.tools_action_info_function_under_maintenance); + showToast(context, S.current.tools_action_info_function_under_maintenance); return; } - final userSelect = await FilePicker.platform.saveFile( - initialDirectory: savePath, - fileName: fileName, - lockParentWindow: true); + final userSelect = + await FilePicker.platform.saveFile(initialDirectory: savePath, fileName: fileName, lockParentWindow: true); if (userSelect == null) { state = state.copyWith(working: false); return; @@ -478,8 +462,7 @@ class ToolsUIModel extends _$ToolsUIModel { } final b64Str = base64Encode(btData.data!); - final gid = - await aria2c.addTorrent(b64Str, extraParams: {"dir": savePath}); + final gid = await aria2c.addTorrent(b64Str, extraParams: {"dir": savePath}); state = state.copyWith(working: false); dPrint("Aria2cManager.aria2c.addUri resp === $gid"); await aria2c.saveSession(); @@ -497,24 +480,19 @@ class ToolsUIModel extends _$ToolsUIModel { launchUrlString("https://support.citizenwiki.cn/d/8"); } - Future _checkPhotographyStatus(BuildContext context, - {bool? setMode}) async { + Future _checkPhotographyStatus(BuildContext context, {bool? setMode}) async { final scInstalledPath = state.scInstalledPath; final keys = ["AudioShakeStrength", "CameraSpringMovement", "ShakeScale"]; - final attributesFile = File( - "$scInstalledPath\\USER\\Client\\0\\Profiles\\default\\attributes.xml"); + final attributesFile = File("$scInstalledPath\\USER\\Client\\0\\Profiles\\default\\attributes.xml"); if (setMode == null) { bool isEnable = false; if (scInstalledPath.isNotEmpty) { if (await attributesFile.exists()) { - final xmlFile = - XmlDocument.parse(await attributesFile.readAsString()); + final xmlFile = XmlDocument.parse(await attributesFile.readAsString()); isEnable = true; for (var k in keys) { if (!isEnable) break; - final e = xmlFile.rootElement.children - .where((element) => element.getAttribute("name") == k) - .firstOrNull; + final e = xmlFile.rootElement.children.where((element) => element.getAttribute("name") == k).firstOrNull; if (e != null && e.getAttribute("value") == "0") { } else { isEnable = false; @@ -531,8 +509,7 @@ class ToolsUIModel extends _$ToolsUIModel { } final xmlFile = XmlDocument.parse(await attributesFile.readAsString()); // clear all - xmlFile.rootElement.children.removeWhere( - (element) => keys.contains(element.getAttribute("name"))); + xmlFile.rootElement.children.removeWhere((element) => keys.contains(element.getAttribute("name"))); if (setMode) { for (var element in keys) { XmlElement newNode = XmlElement(XmlName('Attr'), [ @@ -550,8 +527,7 @@ class ToolsUIModel extends _$ToolsUIModel { } _onChangePhotographyMode(BuildContext context, bool isEnable) async { - _checkPhotographyStatus(context, setMode: !isEnable) - .unwrap(context: context); + _checkPhotographyStatus(context, setMode: !isEnable).unwrap(context: context); loadToolsCard(context, skipPathScan: true); } @@ -564,23 +540,18 @@ class ToolsUIModel extends _$ToolsUIModel { } _doHostsBooster(BuildContext context) async { - showDialog( - context: context, - builder: (BuildContext context) => const HostsBoosterDialogUI()); + showDialog(context: context, builder: (BuildContext context) => const HostsBoosterDialogUI()); } _unp4kc(BuildContext context) async { context.push("/tools/unp4kc"); } - static rsiEnhance(BuildContext context, - {bool showNotGameInstallMsg = false}) async { + static rsiEnhance(BuildContext context, {bool showNotGameInstallMsg = false}) async { if ((await SystemHelper.getPID("\"RSI Launcher\"")).isNotEmpty) { if (!context.mounted) return; - showToast( - context, S.current.tools_action_info_rsi_launcher_running_warning, - constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width * .35)); + showToast(context, S.current.tools_action_info_rsi_launcher_running_warning, + constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * .35)); return; } if (!context.mounted) return; @@ -590,4 +561,13 @@ class ToolsUIModel extends _$ToolsUIModel { showNotGameInstallMsg: showNotGameInstallMsg, )); } + + _showLogAnalyze(BuildContext context) async { + if (state.scInstalledPath.isEmpty) { + showToast(context, S.current.tools_action_info_valid_game_directory_needed); + return; + } + if (!context.mounted) return; + await MultiWindowManager.launchSubWindow("log_analyze", appGlobalState); + } } diff --git a/lib/ui/tools/tools_ui_model.g.dart b/lib/ui/tools/tools_ui_model.g.dart index 8f0a1b9..df2406d 100644 --- a/lib/ui/tools/tools_ui_model.g.dart +++ b/lib/ui/tools/tools_ui_model.g.dart @@ -6,7 +6,7 @@ part of 'tools_ui_model.dart'; // RiverpodGenerator // ************************************************************************** -String _$toolsUIModelHash() => r'ba4d824a61e83b496dbff4d7b5f2e29bd2ed4904'; +String _$toolsUIModelHash() => r'af6e6de30c191a7c9a4e2a1c96a688fba6ee086c'; /// See also [ToolsUIModel]. @ProviderFor(ToolsUIModel) diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index b500fac..c6f7148 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,7 @@ #include "generated_plugin_registrant.h" +#include #include #include #include @@ -13,6 +14,9 @@ #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) desktop_multi_window_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopMultiWindowPlugin"); + desktop_multi_window_plugin_register_with_registrar(desktop_multi_window_registrar); g_autoptr(FlPluginRegistrar) desktop_webview_window_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopWebviewWindowPlugin"); desktop_webview_window_plugin_register_with_registrar(desktop_webview_window_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 1b4d191..04e5b88 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + desktop_multi_window desktop_webview_window flutter_acrylic screen_retriever_linux diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 5630bd4..f207171 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,7 @@ import FlutterMacOS import Foundation +import desktop_multi_window import desktop_webview_window import device_info_plus import file_picker @@ -15,6 +16,7 @@ import url_launcher_macos import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FlutterMultiWindowPlugin.register(with: registry.registrar(forPlugin: "FlutterMultiWindowPlugin")) DesktopWebviewWindowPlugin.register(with: registry.registrar(forPlugin: "DesktopWebviewWindowPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) diff --git a/pubspec.yaml b/pubspec.yaml index 38866b8..97d10dc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -66,6 +66,7 @@ dependencies: re_highlight: ^0.0.3 shelf: ^1.4.1 qr_flutter: ^4.1.0 + desktop_multi_window: ^0.2.1 dependency_overrides: http: ^1.1.2 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index c899bb8..a155824 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,7 @@ #include "generated_plugin_registrant.h" +#include #include #include #include @@ -13,6 +14,8 @@ #include void RegisterPlugins(flutter::PluginRegistry* registry) { + DesktopMultiWindowPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("DesktopMultiWindowPlugin")); DesktopWebviewWindowPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("DesktopWebviewWindowPlugin")); FlutterAcrylicPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index da41b17..82bd548 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + desktop_multi_window desktop_webview_window flutter_acrylic screen_retriever_windows