diff --git a/assets/images/icon/macos/status_1.png b/assets/images/icon/macos/status_1.png new file mode 100644 index 000000000..e8aed4240 Binary files /dev/null and b/assets/images/icon/macos/status_1.png differ diff --git a/assets/images/icon/macos/status_2.png b/assets/images/icon/macos/status_2.png new file mode 100644 index 000000000..a8979b541 Binary files /dev/null and b/assets/images/icon/macos/status_2.png differ diff --git a/assets/images/icon/macos/status_3.png b/assets/images/icon/macos/status_3.png new file mode 100644 index 000000000..a12ae0c95 Binary files /dev/null and b/assets/images/icon/macos/status_3.png differ diff --git a/lib/common/tray.dart b/lib/common/tray.dart index dfff0931e..b4753ec3e 100644 --- a/lib/common/tray.dart +++ b/lib/common/tray.dart @@ -30,26 +30,58 @@ class Tray { await trayManager.destroy(); } - String getTryIcon({required bool isStart, required bool tunEnable}) { - if (system.isMacOS || !isStart) { - return 'assets/images/icon/status_1.$trayIconSuffix'; + String get _macosIconDir => 'assets/images/icon/macos'; + + String getTryIcon({ + required bool isStart, + required bool tunEnable, + required bool systemProxy, + String? customStopped, + String? customProxy, + String? customTun, + }) { + final defaultSuffix = + system.isMacOS ? 'png' : trayIconSuffix; + final defaultDir = + system.isMacOS ? _macosIconDir : 'assets/images/icon'; + if (!isStart || (!tunEnable && !systemProxy)) { + final p = customStopped; + if (p != null && File(p).existsSync()) return p; + return '$defaultDir/status_1.$defaultSuffix'; } - if (!tunEnable) { - return 'assets/images/icon/status_2.$trayIconSuffix'; + if (tunEnable) { + final p = customTun; + if (p != null && File(p).existsSync()) return p; + return '$defaultDir/status_3.$defaultSuffix'; } - return 'assets/images/icon/status_3.$trayIconSuffix'; + final p = customProxy; + if (p != null && File(p).existsSync()) return p; + return '$defaultDir/status_2.$defaultSuffix'; } Future _updateSystemTray({ required bool isStart, required bool tunEnable, + required bool systemProxy, + String? customStopped, + String? customProxy, + String? customTun, + required bool trayIconUseTemplate, }) async { if (Platform.isLinux) { await trayManager.destroy(); } + final iconPath = getTryIcon( + isStart: isStart, + tunEnable: tunEnable, + systemProxy: systemProxy, + customStopped: customStopped, + customProxy: customProxy, + customTun: customTun, + ); await trayManager.setIcon( - getTryIcon(isStart: isStart, tunEnable: tunEnable), - isTemplate: true, + iconPath, + isTemplate: iconPath.startsWith('/') ? trayIconUseTemplate : true, ); if (!Platform.isLinux) { await trayManager.setToolTip(appName); @@ -59,6 +91,10 @@ class Tray { Future update({ required TrayState trayState, required Traffic traffic, + String? trayIconStoppedPath, + String? trayIconProxyPath, + String? trayIconTunPath, + required bool trayIconUseTemplate, }) async { if (system.isAndroid) { return; @@ -67,6 +103,11 @@ class Tray { await _updateSystemTray( isStart: trayState.isStart, tunEnable: trayState.tunEnable, + systemProxy: trayState.systemProxy, + customStopped: trayIconStoppedPath, + customProxy: trayIconProxyPath, + customTun: trayIconTunPath, + trayIconUseTemplate: trayIconUseTemplate, ); } List menuItems = []; @@ -188,6 +229,11 @@ class Tray { await _updateSystemTray( isStart: trayState.isStart, tunEnable: trayState.tunEnable, + systemProxy: trayState.systemProxy, + customStopped: trayIconStoppedPath, + customProxy: trayIconProxyPath, + customTun: trayIconTunPath, + trayIconUseTemplate: trayIconUseTemplate, ); } updateTrayTitle(showTrayTitle: trayState.showTrayTitle, traffic: traffic); diff --git a/lib/controller.dart b/lib/controller.dart index 7d27f56e4..02bffe549 100644 --- a/lib/controller.dart +++ b/lib/controller.dart @@ -956,11 +956,16 @@ extension SystemControllerExt on AppController { } Future updateTray() async { + final setting = _ref.read(appSettingProvider); tray?.update( trayState: _ref.read(trayStateProvider), traffic: _ref.read( trafficsProvider.select((state) => state.list.safeLast(Traffic())), ), + trayIconStoppedPath: setting.trayIconStoppedPath, + trayIconProxyPath: setting.trayIconProxyPath, + trayIconTunPath: setting.trayIconTunPath, + trayIconUseTemplate: setting.trayIconUseTemplate, ); } diff --git a/lib/l10n/intl/messages_en.dart b/lib/l10n/intl/messages_en.dart index 951298097..60b73fc4f 100644 --- a/lib/l10n/intl/messages_en.dart +++ b/lib/l10n/intl/messages_en.dart @@ -757,6 +757,16 @@ class MessageLookup extends MessageLookupByLibrary { "specialProxy": MessageLookupByLibrary.simpleMessage("Special proxy"), "specialRules": MessageLookupByLibrary.simpleMessage("special rules"), "speedStatistics": MessageLookupByLibrary.simpleMessage("Speed statistics"), + "trayIcon": MessageLookupByLibrary.simpleMessage("Tray icon"), + "trayIconDesc": MessageLookupByLibrary.simpleMessage( + "Customize tray icon for each state", + ), + "trayIconUseTemplate": MessageLookupByLibrary.simpleMessage( + "Use template image", + ), + "trayIconUseTemplateDesc": MessageLookupByLibrary.simpleMessage( + "Render custom icons as monochrome template (follows system appearance)", + ), "stackMode": MessageLookupByLibrary.simpleMessage("Stack mode"), "standard": MessageLookupByLibrary.simpleMessage("Standard"), "standardModeDesc": MessageLookupByLibrary.simpleMessage( diff --git a/lib/l10n/intl/messages_ja.dart b/lib/l10n/intl/messages_ja.dart index 89a3994fd..082d41ebc 100644 --- a/lib/l10n/intl/messages_ja.dart +++ b/lib/l10n/intl/messages_ja.dart @@ -578,6 +578,10 @@ class MessageLookup extends MessageLookupByLibrary { "specialProxy": MessageLookupByLibrary.simpleMessage("特殊プロキシ"), "specialRules": MessageLookupByLibrary.simpleMessage("特殊ルール"), "speedStatistics": MessageLookupByLibrary.simpleMessage("速度統計"), + "trayIcon": MessageLookupByLibrary.simpleMessage("トレイアイコン"), + "trayIconDesc": MessageLookupByLibrary.simpleMessage("各状態のトレイアイコンをカスタマイズ"), + "trayIconUseTemplate": MessageLookupByLibrary.simpleMessage("テンプレート画像を使用"), + "trayIconUseTemplateDesc": MessageLookupByLibrary.simpleMessage("カスタムアイコンをモノクロテンプレートとして描画(システム外観に従う)"), "stackMode": MessageLookupByLibrary.simpleMessage("スタックモード"), "standard": MessageLookupByLibrary.simpleMessage("標準"), "standardModeDesc": MessageLookupByLibrary.simpleMessage( diff --git a/lib/l10n/intl/messages_ru.dart b/lib/l10n/intl/messages_ru.dart index 3a55c20db..ad7482c08 100644 --- a/lib/l10n/intl/messages_ru.dart +++ b/lib/l10n/intl/messages_ru.dart @@ -802,6 +802,16 @@ class MessageLookup extends MessageLookupByLibrary { "speedStatistics": MessageLookupByLibrary.simpleMessage( "Статистика скорости", ), + "trayIcon": MessageLookupByLibrary.simpleMessage("Значок трея"), + "trayIconDesc": MessageLookupByLibrary.simpleMessage( + "Настройка значка трея для каждого состояния", + ), + "trayIconUseTemplate": MessageLookupByLibrary.simpleMessage( + "Использовать шаблон изображения", + ), + "trayIconUseTemplateDesc": MessageLookupByLibrary.simpleMessage( + "Отображать пользовательские значки как монохромный шаблон (следует оформлению системы)", + ), "stackMode": MessageLookupByLibrary.simpleMessage("Режим стека"), "standard": MessageLookupByLibrary.simpleMessage("Стандартный"), "standardModeDesc": MessageLookupByLibrary.simpleMessage( diff --git a/lib/l10n/intl/messages_zh_CN.dart b/lib/l10n/intl/messages_zh_CN.dart index 73ae31360..c0d1b2bd6 100644 --- a/lib/l10n/intl/messages_zh_CN.dart +++ b/lib/l10n/intl/messages_zh_CN.dart @@ -510,6 +510,10 @@ class MessageLookup extends MessageLookupByLibrary { "specialProxy": MessageLookupByLibrary.simpleMessage("特殊代理"), "specialRules": MessageLookupByLibrary.simpleMessage("特殊规则"), "speedStatistics": MessageLookupByLibrary.simpleMessage("网速统计"), + "trayIcon": MessageLookupByLibrary.simpleMessage("托盘图标"), + "trayIconDesc": MessageLookupByLibrary.simpleMessage("自定义各状态下的托盘图标"), + "trayIconUseTemplate": MessageLookupByLibrary.simpleMessage("使用模板图像"), + "trayIconUseTemplateDesc": MessageLookupByLibrary.simpleMessage("将自定义图标渲染为单色模板(跟随系统外观)"), "stackMode": MessageLookupByLibrary.simpleMessage("栈模式"), "standard": MessageLookupByLibrary.simpleMessage("标准"), "standardModeDesc": MessageLookupByLibrary.simpleMessage( diff --git a/lib/l10n/l10n.dart b/lib/l10n/l10n.dart index 1a5c357b7..53175448f 100644 --- a/lib/l10n/l10n.dart +++ b/lib/l10n/l10n.dart @@ -3579,6 +3579,41 @@ class AppLocalizations { ); } + /// `Tray icon` + String get trayIcon { + return Intl.message('Tray icon', name: 'trayIcon', desc: '', args: []); + } + + /// `Customize tray icon for each state` + String get trayIconDesc { + return Intl.message( + 'Customize tray icon for each state', + name: 'trayIconDesc', + desc: '', + args: [], + ); + } + + /// `Use template image` + String get trayIconUseTemplate { + return Intl.message( + 'Use template image', + name: 'trayIconUseTemplate', + desc: '', + args: [], + ); + } + + /// `Render custom icons as monochrome template (follows system appearance)` + String get trayIconUseTemplateDesc { + return Intl.message( + 'Render custom icons as monochrome template (follows system appearance)', + name: 'trayIconUseTemplateDesc', + desc: '', + args: [], + ); + } + /// `The current page has changes. Are you sure you want to reset?` String get resetPageChangesTip { return Intl.message( diff --git a/lib/manager/tray_manager.dart b/lib/manager/tray_manager.dart index 998c5c1d2..600911fd3 100755 --- a/lib/manager/tray_manager.dart +++ b/lib/manager/tray_manager.dart @@ -1,5 +1,6 @@ import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/controller.dart'; +import 'package:fl_clash/providers/config.dart'; import 'package:fl_clash/providers/state.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -24,6 +25,14 @@ class _TrayContainerState extends ConsumerState with TrayListener { appController.updateTray(); } }); + ref.listenManual( + appSettingProvider.select( + (s) => (s.trayIconStoppedPath, s.trayIconProxyPath, s.trayIconTunPath, s.trayIconUseTemplate), + ), + (prev, next) { + if (prev != next) appController.updateTray(); + }, + ); if (system.isMacOS) { ref.listenManual(trayTitleStateProvider, (prev, next) { if (prev != next) { diff --git a/lib/models/config.dart b/lib/models/config.dart index ebb7886b7..68f7b6b74 100644 --- a/lib/models/config.dart +++ b/lib/models/config.dart @@ -84,6 +84,10 @@ abstract class AppSettingProps with _$AppSettingProps { @Default(false) bool developerMode, @Default(RestoreStrategy.compatible) RestoreStrategy restoreStrategy, @Default(true) bool showTrayTitle, + @Default(null) String? trayIconStoppedPath, + @Default(null) String? trayIconProxyPath, + @Default(null) String? trayIconTunPath, + @Default(false) bool trayIconUseTemplate, }) = _AppSettingProps; factory AppSettingProps.fromJson(Map json) => diff --git a/lib/models/generated/config.freezed.dart b/lib/models/generated/config.freezed.dart index 932e74bbf..e833265b4 100644 --- a/lib/models/generated/config.freezed.dart +++ b/lib/models/generated/config.freezed.dart @@ -15,7 +15,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$AppSettingProps { - String? get locale;@JsonKey(fromJson: dashboardWidgetsSafeFormJson) List get dashboardWidgets; bool get onlyStatisticsProxy; bool get autoLaunch; bool get silentLaunch; bool get autoRun; bool get openLogs; bool get closeConnections; String get testUrl; bool get isAnimateToPage; bool get autoCheckUpdate; bool get showLabel; bool get disclaimerAccepted; bool get crashlyticsTip; bool get crashlytics; bool get minimizeOnExit; bool get hidden; bool get developerMode; RestoreStrategy get restoreStrategy; bool get showTrayTitle; + String? get locale;@JsonKey(fromJson: dashboardWidgetsSafeFormJson) List get dashboardWidgets; bool get onlyStatisticsProxy; bool get autoLaunch; bool get silentLaunch; bool get autoRun; bool get openLogs; bool get closeConnections; String get testUrl; bool get isAnimateToPage; bool get autoCheckUpdate; bool get showLabel; bool get disclaimerAccepted; bool get crashlyticsTip; bool get crashlytics; bool get minimizeOnExit; bool get hidden; bool get developerMode; RestoreStrategy get restoreStrategy; bool get showTrayTitle; String? get trayIconStoppedPath; String? get trayIconProxyPath; String? get trayIconTunPath; bool get trayIconUseTemplate; /// Create a copy of AppSettingProps /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -28,16 +28,16 @@ $AppSettingPropsCopyWith get copyWith => _$AppSettingPropsCopyW @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is AppSettingProps&&(identical(other.locale, locale) || other.locale == locale)&&const DeepCollectionEquality().equals(other.dashboardWidgets, dashboardWidgets)&&(identical(other.onlyStatisticsProxy, onlyStatisticsProxy) || other.onlyStatisticsProxy == onlyStatisticsProxy)&&(identical(other.autoLaunch, autoLaunch) || other.autoLaunch == autoLaunch)&&(identical(other.silentLaunch, silentLaunch) || other.silentLaunch == silentLaunch)&&(identical(other.autoRun, autoRun) || other.autoRun == autoRun)&&(identical(other.openLogs, openLogs) || other.openLogs == openLogs)&&(identical(other.closeConnections, closeConnections) || other.closeConnections == closeConnections)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl)&&(identical(other.isAnimateToPage, isAnimateToPage) || other.isAnimateToPage == isAnimateToPage)&&(identical(other.autoCheckUpdate, autoCheckUpdate) || other.autoCheckUpdate == autoCheckUpdate)&&(identical(other.showLabel, showLabel) || other.showLabel == showLabel)&&(identical(other.disclaimerAccepted, disclaimerAccepted) || other.disclaimerAccepted == disclaimerAccepted)&&(identical(other.crashlyticsTip, crashlyticsTip) || other.crashlyticsTip == crashlyticsTip)&&(identical(other.crashlytics, crashlytics) || other.crashlytics == crashlytics)&&(identical(other.minimizeOnExit, minimizeOnExit) || other.minimizeOnExit == minimizeOnExit)&&(identical(other.hidden, hidden) || other.hidden == hidden)&&(identical(other.developerMode, developerMode) || other.developerMode == developerMode)&&(identical(other.restoreStrategy, restoreStrategy) || other.restoreStrategy == restoreStrategy)&&(identical(other.showTrayTitle, showTrayTitle) || other.showTrayTitle == showTrayTitle)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is AppSettingProps&&(identical(other.locale, locale) || other.locale == locale)&&const DeepCollectionEquality().equals(other.dashboardWidgets, dashboardWidgets)&&(identical(other.onlyStatisticsProxy, onlyStatisticsProxy) || other.onlyStatisticsProxy == onlyStatisticsProxy)&&(identical(other.autoLaunch, autoLaunch) || other.autoLaunch == autoLaunch)&&(identical(other.silentLaunch, silentLaunch) || other.silentLaunch == silentLaunch)&&(identical(other.autoRun, autoRun) || other.autoRun == autoRun)&&(identical(other.openLogs, openLogs) || other.openLogs == openLogs)&&(identical(other.closeConnections, closeConnections) || other.closeConnections == closeConnections)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl)&&(identical(other.isAnimateToPage, isAnimateToPage) || other.isAnimateToPage == isAnimateToPage)&&(identical(other.autoCheckUpdate, autoCheckUpdate) || other.autoCheckUpdate == autoCheckUpdate)&&(identical(other.showLabel, showLabel) || other.showLabel == showLabel)&&(identical(other.disclaimerAccepted, disclaimerAccepted) || other.disclaimerAccepted == disclaimerAccepted)&&(identical(other.crashlyticsTip, crashlyticsTip) || other.crashlyticsTip == crashlyticsTip)&&(identical(other.crashlytics, crashlytics) || other.crashlytics == crashlytics)&&(identical(other.minimizeOnExit, minimizeOnExit) || other.minimizeOnExit == minimizeOnExit)&&(identical(other.hidden, hidden) || other.hidden == hidden)&&(identical(other.developerMode, developerMode) || other.developerMode == developerMode)&&(identical(other.restoreStrategy, restoreStrategy) || other.restoreStrategy == restoreStrategy)&&(identical(other.showTrayTitle, showTrayTitle) || other.showTrayTitle == showTrayTitle)&&(identical(other.trayIconStoppedPath, trayIconStoppedPath) || other.trayIconStoppedPath == trayIconStoppedPath)&&(identical(other.trayIconProxyPath, trayIconProxyPath) || other.trayIconProxyPath == trayIconProxyPath)&&(identical(other.trayIconTunPath, trayIconTunPath) || other.trayIconTunPath == trayIconTunPath)&&(identical(other.trayIconUseTemplate, trayIconUseTemplate) || other.trayIconUseTemplate == trayIconUseTemplate)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hashAll([runtimeType,locale,const DeepCollectionEquality().hash(dashboardWidgets),onlyStatisticsProxy,autoLaunch,silentLaunch,autoRun,openLogs,closeConnections,testUrl,isAnimateToPage,autoCheckUpdate,showLabel,disclaimerAccepted,crashlyticsTip,crashlytics,minimizeOnExit,hidden,developerMode,restoreStrategy,showTrayTitle]); +int get hashCode => Object.hashAll([runtimeType,locale,const DeepCollectionEquality().hash(dashboardWidgets),onlyStatisticsProxy,autoLaunch,silentLaunch,autoRun,openLogs,closeConnections,testUrl,isAnimateToPage,autoCheckUpdate,showLabel,disclaimerAccepted,crashlyticsTip,crashlytics,minimizeOnExit,hidden,developerMode,restoreStrategy,showTrayTitle,trayIconStoppedPath,trayIconProxyPath,trayIconTunPath,trayIconUseTemplate]); @override String toString() { - return 'AppSettingProps(locale: $locale, dashboardWidgets: $dashboardWidgets, onlyStatisticsProxy: $onlyStatisticsProxy, autoLaunch: $autoLaunch, silentLaunch: $silentLaunch, autoRun: $autoRun, openLogs: $openLogs, closeConnections: $closeConnections, testUrl: $testUrl, isAnimateToPage: $isAnimateToPage, autoCheckUpdate: $autoCheckUpdate, showLabel: $showLabel, disclaimerAccepted: $disclaimerAccepted, crashlyticsTip: $crashlyticsTip, crashlytics: $crashlytics, minimizeOnExit: $minimizeOnExit, hidden: $hidden, developerMode: $developerMode, restoreStrategy: $restoreStrategy, showTrayTitle: $showTrayTitle)'; + return 'AppSettingProps(locale: $locale, dashboardWidgets: $dashboardWidgets, onlyStatisticsProxy: $onlyStatisticsProxy, autoLaunch: $autoLaunch, silentLaunch: $silentLaunch, autoRun: $autoRun, openLogs: $openLogs, closeConnections: $closeConnections, testUrl: $testUrl, isAnimateToPage: $isAnimateToPage, autoCheckUpdate: $autoCheckUpdate, showLabel: $showLabel, disclaimerAccepted: $disclaimerAccepted, crashlyticsTip: $crashlyticsTip, crashlytics: $crashlytics, minimizeOnExit: $minimizeOnExit, hidden: $hidden, developerMode: $developerMode, restoreStrategy: $restoreStrategy, showTrayTitle: $showTrayTitle, trayIconStoppedPath: $trayIconStoppedPath, trayIconProxyPath: $trayIconProxyPath, trayIconTunPath: $trayIconTunPath, trayIconUseTemplate: $trayIconUseTemplate)'; } @@ -48,7 +48,7 @@ abstract mixin class $AppSettingPropsCopyWith<$Res> { factory $AppSettingPropsCopyWith(AppSettingProps value, $Res Function(AppSettingProps) _then) = _$AppSettingPropsCopyWithImpl; @useResult $Res call({ - String? locale,@JsonKey(fromJson: dashboardWidgetsSafeFormJson) List dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RestoreStrategy restoreStrategy, bool showTrayTitle + String? locale,@JsonKey(fromJson: dashboardWidgetsSafeFormJson) List dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RestoreStrategy restoreStrategy, bool showTrayTitle, String? trayIconStoppedPath, String? trayIconProxyPath, String? trayIconTunPath, bool trayIconUseTemplate }); @@ -65,7 +65,7 @@ class _$AppSettingPropsCopyWithImpl<$Res> /// Create a copy of AppSettingProps /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? locale = freezed,Object? dashboardWidgets = null,Object? onlyStatisticsProxy = null,Object? autoLaunch = null,Object? silentLaunch = null,Object? autoRun = null,Object? openLogs = null,Object? closeConnections = null,Object? testUrl = null,Object? isAnimateToPage = null,Object? autoCheckUpdate = null,Object? showLabel = null,Object? disclaimerAccepted = null,Object? crashlyticsTip = null,Object? crashlytics = null,Object? minimizeOnExit = null,Object? hidden = null,Object? developerMode = null,Object? restoreStrategy = null,Object? showTrayTitle = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? locale = freezed,Object? dashboardWidgets = null,Object? onlyStatisticsProxy = null,Object? autoLaunch = null,Object? silentLaunch = null,Object? autoRun = null,Object? openLogs = null,Object? closeConnections = null,Object? testUrl = null,Object? isAnimateToPage = null,Object? autoCheckUpdate = null,Object? showLabel = null,Object? disclaimerAccepted = null,Object? crashlyticsTip = null,Object? crashlytics = null,Object? minimizeOnExit = null,Object? hidden = null,Object? developerMode = null,Object? restoreStrategy = null,Object? showTrayTitle = null,Object? trayIconStoppedPath = freezed,Object? trayIconProxyPath = freezed,Object? trayIconTunPath = freezed,Object? trayIconUseTemplate = null,}) { return _then(_self.copyWith( locale: freezed == locale ? _self.locale : locale // ignore: cast_nullable_to_non_nullable as String?,dashboardWidgets: null == dashboardWidgets ? _self.dashboardWidgets : dashboardWidgets // ignore: cast_nullable_to_non_nullable @@ -87,6 +87,10 @@ as bool,hidden: null == hidden ? _self.hidden : hidden // ignore: cast_nullable_ as bool,developerMode: null == developerMode ? _self.developerMode : developerMode // ignore: cast_nullable_to_non_nullable as bool,restoreStrategy: null == restoreStrategy ? _self.restoreStrategy : restoreStrategy // ignore: cast_nullable_to_non_nullable as RestoreStrategy,showTrayTitle: null == showTrayTitle ? _self.showTrayTitle : showTrayTitle // ignore: cast_nullable_to_non_nullable +as bool,trayIconStoppedPath: freezed == trayIconStoppedPath ? _self.trayIconStoppedPath : trayIconStoppedPath // ignore: cast_nullable_to_non_nullable +as String?,trayIconProxyPath: freezed == trayIconProxyPath ? _self.trayIconProxyPath : trayIconProxyPath // ignore: cast_nullable_to_non_nullable +as String?,trayIconTunPath: freezed == trayIconTunPath ? _self.trayIconTunPath : trayIconTunPath // ignore: cast_nullable_to_non_nullable +as String?,trayIconUseTemplate: null == trayIconUseTemplate ? _self.trayIconUseTemplate : trayIconUseTemplate // ignore: cast_nullable_to_non_nullable as bool, )); } @@ -172,10 +176,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String? locale, @JsonKey(fromJson: dashboardWidgetsSafeFormJson) List dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RestoreStrategy restoreStrategy, bool showTrayTitle)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String? locale, @JsonKey(fromJson: dashboardWidgetsSafeFormJson) List dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RestoreStrategy restoreStrategy, bool showTrayTitle, String? trayIconStoppedPath, String? trayIconProxyPath, String? trayIconTunPath, bool trayIconUseTemplate)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _AppSettingProps() when $default != null: -return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_that.autoLaunch,_that.silentLaunch,_that.autoRun,_that.openLogs,_that.closeConnections,_that.testUrl,_that.isAnimateToPage,_that.autoCheckUpdate,_that.showLabel,_that.disclaimerAccepted,_that.crashlyticsTip,_that.crashlytics,_that.minimizeOnExit,_that.hidden,_that.developerMode,_that.restoreStrategy,_that.showTrayTitle);case _: +return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_that.autoLaunch,_that.silentLaunch,_that.autoRun,_that.openLogs,_that.closeConnections,_that.testUrl,_that.isAnimateToPage,_that.autoCheckUpdate,_that.showLabel,_that.disclaimerAccepted,_that.crashlyticsTip,_that.crashlytics,_that.minimizeOnExit,_that.hidden,_that.developerMode,_that.restoreStrategy,_that.showTrayTitle,_that.trayIconStoppedPath,_that.trayIconProxyPath,_that.trayIconTunPath,_that.trayIconUseTemplate);case _: return orElse(); } @@ -193,10 +197,10 @@ return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_t /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String? locale, @JsonKey(fromJson: dashboardWidgetsSafeFormJson) List dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RestoreStrategy restoreStrategy, bool showTrayTitle) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String? locale, @JsonKey(fromJson: dashboardWidgetsSafeFormJson) List dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RestoreStrategy restoreStrategy, bool showTrayTitle, String? trayIconStoppedPath, String? trayIconProxyPath, String? trayIconTunPath, bool trayIconUseTemplate) $default,) {final _that = this; switch (_that) { case _AppSettingProps(): -return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_that.autoLaunch,_that.silentLaunch,_that.autoRun,_that.openLogs,_that.closeConnections,_that.testUrl,_that.isAnimateToPage,_that.autoCheckUpdate,_that.showLabel,_that.disclaimerAccepted,_that.crashlyticsTip,_that.crashlytics,_that.minimizeOnExit,_that.hidden,_that.developerMode,_that.restoreStrategy,_that.showTrayTitle);case _: +return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_that.autoLaunch,_that.silentLaunch,_that.autoRun,_that.openLogs,_that.closeConnections,_that.testUrl,_that.isAnimateToPage,_that.autoCheckUpdate,_that.showLabel,_that.disclaimerAccepted,_that.crashlyticsTip,_that.crashlytics,_that.minimizeOnExit,_that.hidden,_that.developerMode,_that.restoreStrategy,_that.showTrayTitle,_that.trayIconStoppedPath,_that.trayIconProxyPath,_that.trayIconTunPath,_that.trayIconUseTemplate);case _: throw StateError('Unexpected subclass'); } @@ -213,10 +217,10 @@ return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_t /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? locale, @JsonKey(fromJson: dashboardWidgetsSafeFormJson) List dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RestoreStrategy restoreStrategy, bool showTrayTitle)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? locale, @JsonKey(fromJson: dashboardWidgetsSafeFormJson) List dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RestoreStrategy restoreStrategy, bool showTrayTitle, String? trayIconStoppedPath, String? trayIconProxyPath, String? trayIconTunPath, bool trayIconUseTemplate)? $default,) {final _that = this; switch (_that) { case _AppSettingProps() when $default != null: -return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_that.autoLaunch,_that.silentLaunch,_that.autoRun,_that.openLogs,_that.closeConnections,_that.testUrl,_that.isAnimateToPage,_that.autoCheckUpdate,_that.showLabel,_that.disclaimerAccepted,_that.crashlyticsTip,_that.crashlytics,_that.minimizeOnExit,_that.hidden,_that.developerMode,_that.restoreStrategy,_that.showTrayTitle);case _: +return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_that.autoLaunch,_that.silentLaunch,_that.autoRun,_that.openLogs,_that.closeConnections,_that.testUrl,_that.isAnimateToPage,_that.autoCheckUpdate,_that.showLabel,_that.disclaimerAccepted,_that.crashlyticsTip,_that.crashlytics,_that.minimizeOnExit,_that.hidden,_that.developerMode,_that.restoreStrategy,_that.showTrayTitle,_that.trayIconStoppedPath,_that.trayIconProxyPath,_that.trayIconTunPath,_that.trayIconUseTemplate);case _: return null; } @@ -228,7 +232,7 @@ return $default(_that.locale,_that.dashboardWidgets,_that.onlyStatisticsProxy,_t @JsonSerializable() class _AppSettingProps implements AppSettingProps { - const _AppSettingProps({this.locale, @JsonKey(fromJson: dashboardWidgetsSafeFormJson) final List dashboardWidgets = defaultDashboardWidgets, this.onlyStatisticsProxy = false, this.autoLaunch = false, this.silentLaunch = false, this.autoRun = false, this.openLogs = false, this.closeConnections = true, this.testUrl = defaultTestUrl, this.isAnimateToPage = true, this.autoCheckUpdate = true, this.showLabel = false, this.disclaimerAccepted = false, this.crashlyticsTip = false, this.crashlytics = false, this.minimizeOnExit = true, this.hidden = false, this.developerMode = false, this.restoreStrategy = RestoreStrategy.compatible, this.showTrayTitle = true}): _dashboardWidgets = dashboardWidgets; + const _AppSettingProps({this.locale, @JsonKey(fromJson: dashboardWidgetsSafeFormJson) final List dashboardWidgets = defaultDashboardWidgets, this.onlyStatisticsProxy = false, this.autoLaunch = false, this.silentLaunch = false, this.autoRun = false, this.openLogs = false, this.closeConnections = true, this.testUrl = defaultTestUrl, this.isAnimateToPage = true, this.autoCheckUpdate = true, this.showLabel = false, this.disclaimerAccepted = false, this.crashlyticsTip = false, this.crashlytics = false, this.minimizeOnExit = true, this.hidden = false, this.developerMode = false, this.restoreStrategy = RestoreStrategy.compatible, this.showTrayTitle = true, this.trayIconStoppedPath = null, this.trayIconProxyPath = null, this.trayIconTunPath = null, this.trayIconUseTemplate = false}): _dashboardWidgets = dashboardWidgets; factory _AppSettingProps.fromJson(Map json) => _$AppSettingPropsFromJson(json); @override final String? locale; @@ -257,6 +261,10 @@ class _AppSettingProps implements AppSettingProps { @override@JsonKey() final bool developerMode; @override@JsonKey() final RestoreStrategy restoreStrategy; @override@JsonKey() final bool showTrayTitle; +@override@JsonKey() final String? trayIconStoppedPath; +@override@JsonKey() final String? trayIconProxyPath; +@override@JsonKey() final String? trayIconTunPath; +@override@JsonKey() final bool trayIconUseTemplate; /// Create a copy of AppSettingProps /// with the given fields replaced by the non-null parameter values. @@ -271,16 +279,16 @@ Map toJson() { @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettingProps&&(identical(other.locale, locale) || other.locale == locale)&&const DeepCollectionEquality().equals(other._dashboardWidgets, _dashboardWidgets)&&(identical(other.onlyStatisticsProxy, onlyStatisticsProxy) || other.onlyStatisticsProxy == onlyStatisticsProxy)&&(identical(other.autoLaunch, autoLaunch) || other.autoLaunch == autoLaunch)&&(identical(other.silentLaunch, silentLaunch) || other.silentLaunch == silentLaunch)&&(identical(other.autoRun, autoRun) || other.autoRun == autoRun)&&(identical(other.openLogs, openLogs) || other.openLogs == openLogs)&&(identical(other.closeConnections, closeConnections) || other.closeConnections == closeConnections)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl)&&(identical(other.isAnimateToPage, isAnimateToPage) || other.isAnimateToPage == isAnimateToPage)&&(identical(other.autoCheckUpdate, autoCheckUpdate) || other.autoCheckUpdate == autoCheckUpdate)&&(identical(other.showLabel, showLabel) || other.showLabel == showLabel)&&(identical(other.disclaimerAccepted, disclaimerAccepted) || other.disclaimerAccepted == disclaimerAccepted)&&(identical(other.crashlyticsTip, crashlyticsTip) || other.crashlyticsTip == crashlyticsTip)&&(identical(other.crashlytics, crashlytics) || other.crashlytics == crashlytics)&&(identical(other.minimizeOnExit, minimizeOnExit) || other.minimizeOnExit == minimizeOnExit)&&(identical(other.hidden, hidden) || other.hidden == hidden)&&(identical(other.developerMode, developerMode) || other.developerMode == developerMode)&&(identical(other.restoreStrategy, restoreStrategy) || other.restoreStrategy == restoreStrategy)&&(identical(other.showTrayTitle, showTrayTitle) || other.showTrayTitle == showTrayTitle)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppSettingProps&&(identical(other.locale, locale) || other.locale == locale)&&const DeepCollectionEquality().equals(other._dashboardWidgets, _dashboardWidgets)&&(identical(other.onlyStatisticsProxy, onlyStatisticsProxy) || other.onlyStatisticsProxy == onlyStatisticsProxy)&&(identical(other.autoLaunch, autoLaunch) || other.autoLaunch == autoLaunch)&&(identical(other.silentLaunch, silentLaunch) || other.silentLaunch == silentLaunch)&&(identical(other.autoRun, autoRun) || other.autoRun == autoRun)&&(identical(other.openLogs, openLogs) || other.openLogs == openLogs)&&(identical(other.closeConnections, closeConnections) || other.closeConnections == closeConnections)&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl)&&(identical(other.isAnimateToPage, isAnimateToPage) || other.isAnimateToPage == isAnimateToPage)&&(identical(other.autoCheckUpdate, autoCheckUpdate) || other.autoCheckUpdate == autoCheckUpdate)&&(identical(other.showLabel, showLabel) || other.showLabel == showLabel)&&(identical(other.disclaimerAccepted, disclaimerAccepted) || other.disclaimerAccepted == disclaimerAccepted)&&(identical(other.crashlyticsTip, crashlyticsTip) || other.crashlyticsTip == crashlyticsTip)&&(identical(other.crashlytics, crashlytics) || other.crashlytics == crashlytics)&&(identical(other.minimizeOnExit, minimizeOnExit) || other.minimizeOnExit == minimizeOnExit)&&(identical(other.hidden, hidden) || other.hidden == hidden)&&(identical(other.developerMode, developerMode) || other.developerMode == developerMode)&&(identical(other.restoreStrategy, restoreStrategy) || other.restoreStrategy == restoreStrategy)&&(identical(other.showTrayTitle, showTrayTitle) || other.showTrayTitle == showTrayTitle)&&(identical(other.trayIconStoppedPath, trayIconStoppedPath) || other.trayIconStoppedPath == trayIconStoppedPath)&&(identical(other.trayIconProxyPath, trayIconProxyPath) || other.trayIconProxyPath == trayIconProxyPath)&&(identical(other.trayIconTunPath, trayIconTunPath) || other.trayIconTunPath == trayIconTunPath)&&(identical(other.trayIconUseTemplate, trayIconUseTemplate) || other.trayIconUseTemplate == trayIconUseTemplate)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hashAll([runtimeType,locale,const DeepCollectionEquality().hash(_dashboardWidgets),onlyStatisticsProxy,autoLaunch,silentLaunch,autoRun,openLogs,closeConnections,testUrl,isAnimateToPage,autoCheckUpdate,showLabel,disclaimerAccepted,crashlyticsTip,crashlytics,minimizeOnExit,hidden,developerMode,restoreStrategy,showTrayTitle]); +int get hashCode => Object.hashAll([runtimeType,locale,const DeepCollectionEquality().hash(_dashboardWidgets),onlyStatisticsProxy,autoLaunch,silentLaunch,autoRun,openLogs,closeConnections,testUrl,isAnimateToPage,autoCheckUpdate,showLabel,disclaimerAccepted,crashlyticsTip,crashlytics,minimizeOnExit,hidden,developerMode,restoreStrategy,showTrayTitle,trayIconStoppedPath,trayIconProxyPath,trayIconTunPath,trayIconUseTemplate]); @override String toString() { - return 'AppSettingProps(locale: $locale, dashboardWidgets: $dashboardWidgets, onlyStatisticsProxy: $onlyStatisticsProxy, autoLaunch: $autoLaunch, silentLaunch: $silentLaunch, autoRun: $autoRun, openLogs: $openLogs, closeConnections: $closeConnections, testUrl: $testUrl, isAnimateToPage: $isAnimateToPage, autoCheckUpdate: $autoCheckUpdate, showLabel: $showLabel, disclaimerAccepted: $disclaimerAccepted, crashlyticsTip: $crashlyticsTip, crashlytics: $crashlytics, minimizeOnExit: $minimizeOnExit, hidden: $hidden, developerMode: $developerMode, restoreStrategy: $restoreStrategy, showTrayTitle: $showTrayTitle)'; + return 'AppSettingProps(locale: $locale, dashboardWidgets: $dashboardWidgets, onlyStatisticsProxy: $onlyStatisticsProxy, autoLaunch: $autoLaunch, silentLaunch: $silentLaunch, autoRun: $autoRun, openLogs: $openLogs, closeConnections: $closeConnections, testUrl: $testUrl, isAnimateToPage: $isAnimateToPage, autoCheckUpdate: $autoCheckUpdate, showLabel: $showLabel, disclaimerAccepted: $disclaimerAccepted, crashlyticsTip: $crashlyticsTip, crashlytics: $crashlytics, minimizeOnExit: $minimizeOnExit, hidden: $hidden, developerMode: $developerMode, restoreStrategy: $restoreStrategy, showTrayTitle: $showTrayTitle, trayIconStoppedPath: $trayIconStoppedPath, trayIconProxyPath: $trayIconProxyPath, trayIconTunPath: $trayIconTunPath, trayIconUseTemplate: $trayIconUseTemplate)'; } @@ -291,7 +299,7 @@ abstract mixin class _$AppSettingPropsCopyWith<$Res> implements $AppSettingProps factory _$AppSettingPropsCopyWith(_AppSettingProps value, $Res Function(_AppSettingProps) _then) = __$AppSettingPropsCopyWithImpl; @override @useResult $Res call({ - String? locale,@JsonKey(fromJson: dashboardWidgetsSafeFormJson) List dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RestoreStrategy restoreStrategy, bool showTrayTitle + String? locale,@JsonKey(fromJson: dashboardWidgetsSafeFormJson) List dashboardWidgets, bool onlyStatisticsProxy, bool autoLaunch, bool silentLaunch, bool autoRun, bool openLogs, bool closeConnections, String testUrl, bool isAnimateToPage, bool autoCheckUpdate, bool showLabel, bool disclaimerAccepted, bool crashlyticsTip, bool crashlytics, bool minimizeOnExit, bool hidden, bool developerMode, RestoreStrategy restoreStrategy, bool showTrayTitle, String? trayIconStoppedPath, String? trayIconProxyPath, String? trayIconTunPath, bool trayIconUseTemplate }); @@ -308,7 +316,7 @@ class __$AppSettingPropsCopyWithImpl<$Res> /// Create a copy of AppSettingProps /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? locale = freezed,Object? dashboardWidgets = null,Object? onlyStatisticsProxy = null,Object? autoLaunch = null,Object? silentLaunch = null,Object? autoRun = null,Object? openLogs = null,Object? closeConnections = null,Object? testUrl = null,Object? isAnimateToPage = null,Object? autoCheckUpdate = null,Object? showLabel = null,Object? disclaimerAccepted = null,Object? crashlyticsTip = null,Object? crashlytics = null,Object? minimizeOnExit = null,Object? hidden = null,Object? developerMode = null,Object? restoreStrategy = null,Object? showTrayTitle = null,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? locale = freezed,Object? dashboardWidgets = null,Object? onlyStatisticsProxy = null,Object? autoLaunch = null,Object? silentLaunch = null,Object? autoRun = null,Object? openLogs = null,Object? closeConnections = null,Object? testUrl = null,Object? isAnimateToPage = null,Object? autoCheckUpdate = null,Object? showLabel = null,Object? disclaimerAccepted = null,Object? crashlyticsTip = null,Object? crashlytics = null,Object? minimizeOnExit = null,Object? hidden = null,Object? developerMode = null,Object? restoreStrategy = null,Object? showTrayTitle = null,Object? trayIconStoppedPath = freezed,Object? trayIconProxyPath = freezed,Object? trayIconTunPath = freezed,Object? trayIconUseTemplate = null,}) { return _then(_AppSettingProps( locale: freezed == locale ? _self.locale : locale // ignore: cast_nullable_to_non_nullable as String?,dashboardWidgets: null == dashboardWidgets ? _self._dashboardWidgets : dashboardWidgets // ignore: cast_nullable_to_non_nullable @@ -330,6 +338,10 @@ as bool,hidden: null == hidden ? _self.hidden : hidden // ignore: cast_nullable_ as bool,developerMode: null == developerMode ? _self.developerMode : developerMode // ignore: cast_nullable_to_non_nullable as bool,restoreStrategy: null == restoreStrategy ? _self.restoreStrategy : restoreStrategy // ignore: cast_nullable_to_non_nullable as RestoreStrategy,showTrayTitle: null == showTrayTitle ? _self.showTrayTitle : showTrayTitle // ignore: cast_nullable_to_non_nullable +as bool,trayIconStoppedPath: freezed == trayIconStoppedPath ? _self.trayIconStoppedPath : trayIconStoppedPath // ignore: cast_nullable_to_non_nullable +as String?,trayIconProxyPath: freezed == trayIconProxyPath ? _self.trayIconProxyPath : trayIconProxyPath // ignore: cast_nullable_to_non_nullable +as String?,trayIconTunPath: freezed == trayIconTunPath ? _self.trayIconTunPath : trayIconTunPath // ignore: cast_nullable_to_non_nullable +as String?,trayIconUseTemplate: null == trayIconUseTemplate ? _self.trayIconUseTemplate : trayIconUseTemplate // ignore: cast_nullable_to_non_nullable as bool, )); } diff --git a/lib/models/generated/config.g.dart b/lib/models/generated/config.g.dart index 6d8a38942..c4148574a 100644 --- a/lib/models/generated/config.g.dart +++ b/lib/models/generated/config.g.dart @@ -35,6 +35,10 @@ _AppSettingProps _$AppSettingPropsFromJson(Map json) => ) ?? RestoreStrategy.compatible, showTrayTitle: json['showTrayTitle'] as bool? ?? true, + trayIconStoppedPath: json['trayIconStoppedPath'] as String? ?? null, + trayIconProxyPath: json['trayIconProxyPath'] as String? ?? null, + trayIconTunPath: json['trayIconTunPath'] as String? ?? null, + trayIconUseTemplate: json['trayIconUseTemplate'] as bool? ?? false, ); Map _$AppSettingPropsToJson(_AppSettingProps instance) => @@ -61,6 +65,10 @@ Map _$AppSettingPropsToJson(_AppSettingProps instance) => 'developerMode': instance.developerMode, 'restoreStrategy': _$RestoreStrategyEnumMap[instance.restoreStrategy]!, 'showTrayTitle': instance.showTrayTitle, + 'trayIconStoppedPath': instance.trayIconStoppedPath, + 'trayIconProxyPath': instance.trayIconProxyPath, + 'trayIconTunPath': instance.trayIconTunPath, + 'trayIconUseTemplate': instance.trayIconUseTemplate, }; const _$RestoreStrategyEnumMap = { diff --git a/lib/views/tools.dart b/lib/views/tools.dart index 96662883c..8c6c22885 100644 --- a/lib/views/tools.dart +++ b/lib/views/tools.dart @@ -20,6 +20,7 @@ import 'package:path/path.dart' show dirname, join; import 'config/advanced.dart'; import 'developer.dart'; import 'theme.dart'; +import 'tray_icon_setting.dart'; class ToolsView extends ConsumerStatefulWidget { const ToolsView({super.key}); @@ -74,6 +75,7 @@ class _ToolViewState extends ConsumerState { if (system.isDesktop) const _HotkeyItem(), if (system.isWindows) const _LoopbackItem(), if (system.isAndroid) const _AccessItem(), + if (system.isDesktop) const _TrayIconItem(), const _ConfigItem(), const _AdvancedConfigItem(), const _SettingItem(), @@ -269,6 +271,20 @@ class _SettingItem extends StatelessWidget { } } +class _TrayIconItem extends StatelessWidget { + const _TrayIconItem(); + + @override + Widget build(BuildContext context) { + return ListItem.open( + leading: const Icon(Icons.image_outlined), + title: Text(context.appLocalizations.trayIcon), + subtitle: Text(context.appLocalizations.trayIconDesc), + delegate: OpenDelegate(widget: const TrayIconView()), + ); + } +} + class _DisclaimerItem extends StatelessWidget { const _DisclaimerItem(); diff --git a/lib/views/tray_icon_setting.dart b/lib/views/tray_icon_setting.dart new file mode 100644 index 000000000..4abd4812e --- /dev/null +++ b/lib/views/tray_icon_setting.dart @@ -0,0 +1,173 @@ +import 'dart:io'; + +import 'package:fl_clash/common/common.dart'; +import 'package:fl_clash/providers/config.dart'; +import 'package:fl_clash/widgets/widgets.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:path/path.dart' as p; + +class TrayIconView extends ConsumerWidget { + const TrayIconView({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final stoppedPath = ref.watch( + appSettingProvider.select((s) => s.trayIconStoppedPath), + ); + final proxyPath = ref.watch( + appSettingProvider.select((s) => s.trayIconProxyPath), + ); + final tunPath = ref.watch( + appSettingProvider.select((s) => s.trayIconTunPath), + ); + final useTemplate = ref.watch( + appSettingProvider.select((s) => s.trayIconUseTemplate), + ); + + final items = [ + SwitchListTile( + title: Text(appLocalizations.trayIconUseTemplate), + subtitle: Text(appLocalizations.trayIconUseTemplateDesc), + value: useTemplate, + onChanged: (v) => ref + .read(appSettingProvider.notifier) + .update((s) => s.copyWith(trayIconUseTemplate: v)), + ), + _TrayIconRow( + label: appLocalizations.stop, + iconPath: stoppedPath, + defaultAsset: 'assets/images/icon/macos/status_1.png', + onPicked: (path) => ref + .read(appSettingProvider.notifier) + .update((s) => s.copyWith(trayIconStoppedPath: path)), + onReset: () => ref + .read(appSettingProvider.notifier) + .update((s) => s.copyWith(trayIconStoppedPath: null)), + ), + _TrayIconRow( + label: appLocalizations.systemProxy, + iconPath: proxyPath, + defaultAsset: 'assets/images/icon/macos/status_2.png', + onPicked: (path) => ref + .read(appSettingProvider.notifier) + .update((s) => s.copyWith(trayIconProxyPath: path)), + onReset: () => ref + .read(appSettingProvider.notifier) + .update((s) => s.copyWith(trayIconProxyPath: null)), + ), + _TrayIconRow( + label: appLocalizations.tun, + iconPath: tunPath, + defaultAsset: 'assets/images/icon/macos/status_3.png', + onPicked: (path) => ref + .read(appSettingProvider.notifier) + .update((s) => s.copyWith(trayIconTunPath: path)), + onReset: () => ref + .read(appSettingProvider.notifier) + .update((s) => s.copyWith(trayIconTunPath: null)), + ), + ]; + + return BaseScaffold( + title: appLocalizations.trayIcon, + body: ListView.separated( + itemCount: items.length, + itemBuilder: (_, index) => items[index], + separatorBuilder: (_, _) => const Divider(height: 0), + ), + ); + } +} + +class _TrayIconRow extends StatefulWidget { + final String label; + final String? iconPath; + final String defaultAsset; + final void Function(String path) onPicked; + final void Function() onReset; + + const _TrayIconRow({ + required this.label, + required this.iconPath, + required this.defaultAsset, + required this.onPicked, + required this.onReset, + }); + + @override + State<_TrayIconRow> createState() => _TrayIconRowState(); +} + +class _TrayIconRowState extends State<_TrayIconRow> { + int _imageVersion = 0; + + Widget _buildIconPreview() { + final path = widget.iconPath; + if (path != null && File(path).existsSync()) { + return Image.file( + File(path), + key: ValueKey(_imageVersion), + width: 22, + height: 22, + ); + } + return Image.asset(widget.defaultAsset, width: 22, height: 22); + } + + Future _pickIcon() async { + final file = await picker.pickerFile(); + if (file == null) return; + final srcPath = file.path; + if (srcPath == null) return; + + final ext = p.extension(srcPath).toLowerCase(); + if (ext != '.png' && ext != '.ico') { + return; + } + + final destDir = Directory( + p.join(await appPath.homeDirPath, 'tray_icons'), + ); + if (!await destDir.exists()) { + await destDir.create(recursive: true); + } + final destPath = p.join(destDir.path, '${widget.label}$ext'); + await File(srcPath).copy(destPath); + PaintingBinding.instance.imageCache.evict(FileImage(File(destPath))); + setState(() => _imageVersion++); + widget.onPicked(destPath); + } + + @override + Widget build(BuildContext context) { + final hasCustom = widget.iconPath != null && File(widget.iconPath!).existsSync(); + return ListItem( + leading: _buildIconPreview(), + title: Text(widget.label), + subtitle: hasCustom + ? Text( + p.basename(widget.iconPath!), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ) + : Text(appLocalizations.defaultText), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + TextButton( + onPressed: _pickIcon, + child: Text(appLocalizations.edit), + ), + if (hasCustom) + TextButton( + onPressed: widget.onReset, + child: Text(appLocalizations.reset), + ), + ], + ), + tileTitleAlignment: ListTileTitleAlignment.center, + ); + } +} + diff --git a/plugins/tray_manager b/plugins/tray_manager index 6163dc8f6..0a65df075 160000 --- a/plugins/tray_manager +++ b/plugins/tray_manager @@ -1 +1 @@ -Subproject commit 6163dc8f62f6f177a85072b0e1e2eee76fed54f0 +Subproject commit 0a65df0751670703a2850c3f37d6cdbf7bef69c8 diff --git a/pubspec.lock b/pubspec.lock index 65d0acabd..f7d14ded5 100755 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,34 +5,34 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f + sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d url: "https://pub.dev" source: hosted - version: "85.0.0" + version: "91.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: f4ad0fea5f102201015c9aae9d93bc02f75dd9491529a8c21f88d17a8523d44c + sha256: a40a0cee526a7e1f387c6847bd8a5ccbf510a75952ef8a28338e989558072cb0 url: "https://pub.dev" source: hosted - version: "7.6.0" + version: "8.4.0" analyzer_buffer: dependency: transitive description: name: analyzer_buffer - sha256: f7833bee67c03c37241c67f8741b17cc501b69d9758df7a5a4a13ed6c947be43 + sha256: aba2f75e63b3135fd1efaa8b6abefe1aa6e41b6bd9806221620fa48f98156033 url: "https://pub.dev" source: hosted - version: "0.1.10" + version: "0.1.11" analyzer_plugin: dependency: transitive description: name: analyzer_plugin - sha256: a5ab7590c27b779f3d4de67f31c4109dbe13dd7339f86461a6f2a8ab2594d8ce + sha256: "08cfefa90b4f4dd3b447bda831cecf644029f9f8e22820f6ee310213ebe2dd53" url: "https://pub.dev" source: hosted - version: "0.13.4" + version: "0.13.10" animations: dependency: "direct main" description: @@ -173,10 +173,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" charcode: dependency: transitive description: @@ -293,42 +293,42 @@ packages: dependency: transitive description: name: custom_lint - sha256: "78085fbe842de7c5bef92de811ca81536968dbcbbcdac5c316711add2d15e796" + sha256: "751ee9440920f808266c3ec2553420dea56d3c7837dd2d62af76b11be3fcece5" url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.8.1" custom_lint_builder: dependency: transitive description: name: custom_lint_builder - sha256: cc5532d5733d4eccfccaaec6070a1926e9f21e613d93ad0927fad020b95c9e52 + sha256: "1128db6f58e71d43842f3b9be7465c83f0c47f4dd8918f878dd6ad3b72a32072" url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.8.1" custom_lint_core: dependency: transitive description: name: custom_lint_core - sha256: cc4684d22ca05bf0a4a51127e19a8aea576b42079ed2bc9e956f11aaebe35dd1 + sha256: "85b339346154d5646952d44d682965dfe9e12cae5febd706f0db3aa5010d6423" url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.8.1" custom_lint_visitor: dependency: transitive description: name: custom_lint_visitor - sha256: "4a86a0d8415a91fbb8298d6ef03e9034dc8e323a599ddc4120a0e36c433983a2" + sha256: "91f2a81e9f0abb4b9f3bb529f78b6227ce6050300d1ae5b1e2c69c66c7a566d8" url: "https://pub.dev" source: hosted - version: "1.0.0+7.7.0" + version: "1.0.0+8.4.0" dart_style: dependency: transitive description: name: dart_style - sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" + sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.3" dbus: dependency: transitive description: @@ -790,14 +790,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.5+1" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" json_annotation: dependency: "direct main" description: @@ -866,18 +858,18 @@ packages: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.19" material_color_utilities: dependency: "direct main" description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" menu_base: dependency: transitive description: @@ -890,10 +882,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -914,10 +906,10 @@ packages: dependency: transitive description: name: mockito - sha256: "2314cbe9165bcd16106513df9cf3c3224713087f09723b128928dc11a4379f99" + sha256: a45d1aa065b796922db7b9e7e7e45f921aed17adf3a8318a1f47097e7e695566 url: "https://pub.dev" source: hosted - version: "5.5.0" + version: "5.6.3" nm: dependency: transitive description: @@ -1302,10 +1294,10 @@ packages: dependency: transitive description: name: source_gen - sha256: "7b19d6ba131c6eb98bfcbf8d56c1a7002eba438af2e7ae6f8398b2b0f4f381e3" + sha256: adc962c96fffb2de1728ef396a995aaedcafbe635abdca13d2a987ce17e57751 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "4.2.1" source_helper: dependency: transitive description: @@ -1478,26 +1470,26 @@ packages: dependency: transitive description: name: test - sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" + sha256: "280d6d890011ca966ad08df7e8a4ddfab0fb3aa49f96ed6de56e3521347a9ae7" url: "https://pub.dev" source: hosted - version: "1.26.2" + version: "1.30.0" test_api: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.10" test_core: dependency: transitive description: name: test_core - sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" + sha256: "0381bd1585d1a924763c308100f2138205252fb90c9d4eeaf28489ee65ccde51" url: "https://pub.dev" source: hosted - version: "0.6.11" + version: "0.6.16" timing: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index cca65ac8e..b3b0e9ccf 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -51,7 +51,7 @@ dependencies: flutter_riverpod: ^3.0.0 riverpod_annotation: ^3.0.0 riverpod: ^3.0.0 - material_color_utilities: ^0.11.1 + material_color_utilities: ^0.13.0 flutter_js: git: url: https://github.com/chen08209/flutter_js @@ -91,6 +91,7 @@ flutter: - assets/images/avatar/ - assets/images/empty/ - assets/images/icon/ + - assets/images/icon/macos/ fonts: - family: JetBrainsMono fonts: