483 lines
12 KiB
Dart
483 lines
12 KiB
Dart
import 'dart:io';
|
|
import 'package:fl_clash/common/common.dart';
|
|
import 'package:fl_clash/enum/enum.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
|
|
import 'models.dart';
|
|
|
|
part 'generated/config.g.dart';
|
|
|
|
part 'generated/config.freezed.dart';
|
|
|
|
const defaultAppSetting = AppSetting();
|
|
|
|
@freezed
|
|
class AppSetting with _$AppSetting {
|
|
const factory AppSetting({
|
|
String? locale,
|
|
@Default(false) bool onlyProxy,
|
|
@Default(false) bool autoLaunch,
|
|
@Default(false) bool adminAutoLaunch,
|
|
@Default(false) bool silentLaunch,
|
|
@Default(false) bool autoRun,
|
|
@Default(false) bool openLogs,
|
|
@Default(true) bool closeConnections,
|
|
@Default(defaultTestUrl) String testUrl,
|
|
@Default(true) bool isAnimateToPage,
|
|
@Default(true) bool autoCheckUpdate,
|
|
@Default(false) bool showLabel,
|
|
@Default(false) bool disclaimerAccepted,
|
|
@Default(true) bool minimizeOnExit,
|
|
@Default(false) bool hidden,
|
|
}) = _AppSetting;
|
|
|
|
factory AppSetting.fromJson(Map<String, Object?> json) =>
|
|
_$AppSettingFromJson(json);
|
|
|
|
factory AppSetting.realFromJson(Map<String, Object?>? json) {
|
|
final appSetting =
|
|
json == null ? defaultAppSetting : AppSetting.fromJson(json);
|
|
return appSetting.copyWith(
|
|
isAnimateToPage: system.isDesktop ? false : true,
|
|
);
|
|
}
|
|
}
|
|
|
|
@freezed
|
|
class AccessControl with _$AccessControl {
|
|
const factory AccessControl({
|
|
@Default(AccessControlMode.rejectSelected) AccessControlMode mode,
|
|
@Default([]) List<String> acceptList,
|
|
@Default([]) List<String> rejectList,
|
|
@Default(AccessSortType.none) AccessSortType sort,
|
|
@Default(true) bool isFilterSystemApp,
|
|
}) = _AccessControl;
|
|
|
|
factory AccessControl.fromJson(Map<String, Object?> json) =>
|
|
_$AccessControlFromJson(json);
|
|
}
|
|
|
|
extension AccessControlExt on AccessControl {
|
|
List<String> get currentList => switch (mode) {
|
|
AccessControlMode.acceptSelected => acceptList,
|
|
AccessControlMode.rejectSelected => rejectList,
|
|
};
|
|
}
|
|
|
|
@freezed
|
|
class WindowProps with _$WindowProps {
|
|
const factory WindowProps({
|
|
@Default(1000) double width,
|
|
@Default(600) double height,
|
|
double? top,
|
|
double? left,
|
|
}) = _WindowProps;
|
|
|
|
factory WindowProps.fromJson(Map<String, Object?>? json) =>
|
|
json == null ? const WindowProps() : _$WindowPropsFromJson(json);
|
|
}
|
|
|
|
const defaultBypassDomain = [
|
|
"*zhihu.com",
|
|
"*zhimg.com",
|
|
"*jd.com",
|
|
"100ime-iat-api.xfyun.cn",
|
|
"*360buyimg.com",
|
|
"localhost",
|
|
"*.local",
|
|
"127.*",
|
|
"10.*",
|
|
"172.16.*",
|
|
"172.17.*",
|
|
"172.18.*",
|
|
"172.19.*",
|
|
"172.2*",
|
|
"172.30.*",
|
|
"172.31.*",
|
|
"192.168.*"
|
|
];
|
|
|
|
const defaultVpnProps = VpnProps();
|
|
|
|
@freezed
|
|
class VpnProps with _$VpnProps {
|
|
const factory VpnProps({
|
|
@Default(true) bool enable,
|
|
@Default(true) bool systemProxy,
|
|
@Default(false) bool ipv6,
|
|
@Default(true) bool allowBypass,
|
|
@Default(defaultBypassDomain) List<String> bypassDomain,
|
|
}) = _VpnProps;
|
|
|
|
factory VpnProps.fromJson(Map<String, Object?>? json) =>
|
|
json == null ? const VpnProps() : _$VpnPropsFromJson(json);
|
|
}
|
|
|
|
@freezed
|
|
class DesktopProps with _$DesktopProps {
|
|
const factory DesktopProps({
|
|
@Default(true) bool systemProxy,
|
|
}) = _DesktopProps;
|
|
|
|
factory DesktopProps.fromJson(Map<String, Object?>? json) =>
|
|
json == null ? const DesktopProps() : _$DesktopPropsFromJson(json);
|
|
}
|
|
|
|
const defaultProxiesStyle = ProxiesStyle();
|
|
|
|
@freezed
|
|
class ProxiesStyle with _$ProxiesStyle {
|
|
const factory ProxiesStyle({
|
|
@Default(ProxiesType.tab) ProxiesType type,
|
|
@Default(ProxiesSortType.none) ProxiesSortType sortType,
|
|
@Default(ProxiesLayout.standard) ProxiesLayout layout,
|
|
@Default(ProxiesIconStyle.standard) ProxiesIconStyle iconStyle,
|
|
@Default(ProxyCardType.expand) ProxyCardType cardType,
|
|
@Default({}) Map<String, String> iconMap,
|
|
}) = _ProxiesStyle;
|
|
|
|
factory ProxiesStyle.fromJson(Map<String, Object?>? json) =>
|
|
json == null ? defaultProxiesStyle : _$ProxiesStyleFromJson(json);
|
|
}
|
|
|
|
const defaultThemeProps = ThemeProps();
|
|
|
|
@freezed
|
|
class ThemeProps with _$ThemeProps {
|
|
const factory ThemeProps({
|
|
@Default(0xFF795548) int? primaryColor,
|
|
@Default(ThemeMode.system) ThemeMode themeMode,
|
|
@Default(false) bool prueBlack,
|
|
@Default(FontFamily.system) FontFamily fontFamily,
|
|
}) = _ThemeProps;
|
|
|
|
factory ThemeProps.fromJson(Map<String, Object?> json) => _$ThemePropsFromJson(json);
|
|
|
|
factory ThemeProps.realFromJson(Map<String, Object?>? json) {
|
|
if (json == null) {
|
|
return Platform.isWindows
|
|
? defaultThemeProps.copyWith(fontFamily: FontFamily.miSans)
|
|
: defaultThemeProps;
|
|
}
|
|
try {
|
|
return ThemeProps.fromJson(json);
|
|
} catch (_) {
|
|
return Platform.isWindows
|
|
? defaultThemeProps.copyWith(fontFamily: FontFamily.miSans)
|
|
: defaultThemeProps;
|
|
}
|
|
}
|
|
}
|
|
|
|
const defaultCustomFontSizeScale = 1.0;
|
|
|
|
@JsonSerializable()
|
|
class Config extends ChangeNotifier {
|
|
AppSetting _appSetting;
|
|
List<Profile> _profiles;
|
|
String? _currentProfileId;
|
|
bool _isAccessControl;
|
|
AccessControl _accessControl;
|
|
DAV? _dav;
|
|
WindowProps _windowProps;
|
|
ThemeProps _themeProps;
|
|
VpnProps _vpnProps;
|
|
DesktopProps _desktopProps;
|
|
bool _overrideDns;
|
|
List<HotKeyAction> _hotKeyActions;
|
|
ProxiesStyle _proxiesStyle;
|
|
|
|
Config()
|
|
: _profiles = [],
|
|
_isAccessControl = false,
|
|
_accessControl = const AccessControl(),
|
|
_windowProps = const WindowProps(),
|
|
_vpnProps = defaultVpnProps,
|
|
_desktopProps = const DesktopProps(),
|
|
_overrideDns = false,
|
|
_appSetting = defaultAppSetting,
|
|
_hotKeyActions = [],
|
|
_proxiesStyle = defaultProxiesStyle,
|
|
_themeProps = defaultThemeProps;
|
|
|
|
@JsonKey(fromJson: AppSetting.realFromJson)
|
|
AppSetting get appSetting => _appSetting;
|
|
|
|
set appSetting(AppSetting value) {
|
|
if (_appSetting != value) {
|
|
_appSetting = value;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
deleteProfileById(String id) {
|
|
_profiles = profiles.where((element) => element.id != id).toList();
|
|
notifyListeners();
|
|
}
|
|
|
|
Profile? getCurrentProfileForId(String? value) {
|
|
if (value == null) {
|
|
return null;
|
|
}
|
|
return _profiles.firstWhere((element) => element.id == value);
|
|
}
|
|
|
|
Profile? getCurrentProfile() {
|
|
return getCurrentProfileForId(_currentProfileId);
|
|
}
|
|
|
|
String? _getLabel(String? label, String id) {
|
|
final realLabel = label ?? id;
|
|
final hasDup = _profiles.indexWhere(
|
|
(element) => element.label == realLabel && element.id != id) !=
|
|
-1;
|
|
if (hasDup) {
|
|
return _getLabel(other.getOverwriteLabel(realLabel), id);
|
|
} else {
|
|
return label;
|
|
}
|
|
}
|
|
|
|
_setProfile(Profile profile) {
|
|
final List<Profile> profilesTemp = List.from(_profiles);
|
|
final index =
|
|
profilesTemp.indexWhere((element) => element.id == profile.id);
|
|
final updateProfile = profile.copyWith(
|
|
label: _getLabel(profile.label, profile.id),
|
|
);
|
|
if (index == -1) {
|
|
profilesTemp.add(updateProfile);
|
|
} else {
|
|
profilesTemp[index] = updateProfile;
|
|
}
|
|
_profiles = profilesTemp;
|
|
}
|
|
|
|
setProfile(Profile profile) {
|
|
_setProfile(profile);
|
|
notifyListeners();
|
|
}
|
|
|
|
@JsonKey(defaultValue: [])
|
|
List<Profile> get profiles => _profiles;
|
|
|
|
set profiles(List<Profile> value) {
|
|
if (_profiles != value) {
|
|
_profiles = value;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
String? get currentProfileId => _currentProfileId;
|
|
|
|
set currentProfileId(String? value) {
|
|
if (_currentProfileId != value) {
|
|
_currentProfileId = value;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
Profile? get currentProfile {
|
|
final index =
|
|
profiles.indexWhere((profile) => profile.id == _currentProfileId);
|
|
return index == -1 ? null : profiles[index];
|
|
}
|
|
|
|
String? get currentGroupName => currentProfile?.currentGroupName;
|
|
|
|
Set<String> get currentUnfoldSet => currentProfile?.unfoldSet ?? {};
|
|
|
|
updateCurrentUnfoldSet(Set<String> value) {
|
|
if (!stringSetEquality.equals(currentUnfoldSet, value)) {
|
|
_setProfile(
|
|
currentProfile!.copyWith(
|
|
unfoldSet: value,
|
|
),
|
|
);
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
updateCurrentGroupName(String groupName) {
|
|
if (currentProfile != null &&
|
|
currentProfile!.currentGroupName != groupName) {
|
|
_setProfile(
|
|
currentProfile!.copyWith(
|
|
currentGroupName: groupName,
|
|
),
|
|
);
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
SelectedMap get currentSelectedMap {
|
|
return currentProfile?.selectedMap ?? {};
|
|
}
|
|
|
|
updateCurrentSelectedMap(String groupName, String proxyName) {
|
|
if (currentProfile != null &&
|
|
currentProfile!.selectedMap[groupName] != proxyName) {
|
|
final SelectedMap selectedMap = Map.from(
|
|
currentProfile?.selectedMap ?? {},
|
|
)..[groupName] = proxyName;
|
|
_setProfile(
|
|
currentProfile!.copyWith(
|
|
selectedMap: selectedMap,
|
|
),
|
|
);
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
@JsonKey(defaultValue: false)
|
|
bool get isAccessControl {
|
|
if (!Platform.isAndroid) return false;
|
|
return _isAccessControl;
|
|
}
|
|
|
|
set isAccessControl(bool value) {
|
|
if (_isAccessControl != value) {
|
|
_isAccessControl = value;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
AccessControl get accessControl => _accessControl;
|
|
|
|
set accessControl(AccessControl value) {
|
|
if (_accessControl != value) {
|
|
_accessControl = value;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
DAV? get dav => _dav;
|
|
|
|
set dav(DAV? value) {
|
|
if (_dav != value) {
|
|
_dav = value;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
WindowProps get windowProps => _windowProps;
|
|
|
|
set windowProps(WindowProps value) {
|
|
if (_windowProps != value) {
|
|
_windowProps = value;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
VpnProps get vpnProps => _vpnProps;
|
|
|
|
set vpnProps(VpnProps value) {
|
|
if (_vpnProps != value) {
|
|
_vpnProps = value;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
DesktopProps get desktopProps => _desktopProps;
|
|
|
|
set desktopProps(DesktopProps value) {
|
|
if (_desktopProps != value) {
|
|
_desktopProps = value;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
@JsonKey(defaultValue: false)
|
|
bool get overrideDns => _overrideDns;
|
|
|
|
set overrideDns(bool value) {
|
|
if (_overrideDns != value) {
|
|
_overrideDns = value;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
@JsonKey(defaultValue: [])
|
|
List<HotKeyAction> get hotKeyActions => _hotKeyActions;
|
|
|
|
set hotKeyActions(List<HotKeyAction> value) {
|
|
if (_hotKeyActions != value) {
|
|
_hotKeyActions = value;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
ProxiesStyle get proxiesStyle => _proxiesStyle;
|
|
|
|
set proxiesStyle(ProxiesStyle value) {
|
|
if (_proxiesStyle != value ||
|
|
!stringAndStringMapEntryIterableEquality.equals(
|
|
_proxiesStyle.iconMap.entries,
|
|
value.iconMap.entries,
|
|
)) {
|
|
_proxiesStyle = value;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
@JsonKey(fromJson: ThemeProps.realFromJson)
|
|
ThemeProps get themeProps => _themeProps;
|
|
|
|
set themeProps(ThemeProps value) {
|
|
if (_themeProps != value) {
|
|
_themeProps = value;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
updateOrAddHotKeyAction(HotKeyAction hotKeyAction) {
|
|
final index =
|
|
_hotKeyActions.indexWhere((item) => item.action == hotKeyAction.action);
|
|
if (index == -1) {
|
|
_hotKeyActions = List.from(_hotKeyActions)..add(hotKeyAction);
|
|
} else {
|
|
_hotKeyActions = List.from(_hotKeyActions)..[index] = hotKeyAction;
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
update([
|
|
Config? config,
|
|
RecoveryOption recoveryOptions = RecoveryOption.all,
|
|
]) {
|
|
if (config != null) {
|
|
_profiles = config._profiles;
|
|
for (final profile in config._profiles) {
|
|
_setProfile(profile);
|
|
}
|
|
final onlyProfiles = recoveryOptions == RecoveryOption.onlyProfiles;
|
|
if (_currentProfileId == null && onlyProfiles && profiles.isNotEmpty) {
|
|
_currentProfileId = _profiles.first.id;
|
|
}
|
|
if (onlyProfiles) return;
|
|
_appSetting = config._appSetting;
|
|
_currentProfileId = config._currentProfileId;
|
|
_dav = config._dav;
|
|
_isAccessControl = config._isAccessControl;
|
|
_accessControl = config._accessControl;
|
|
_themeProps = config._themeProps;
|
|
_windowProps = config._windowProps;
|
|
_proxiesStyle = config._proxiesStyle;
|
|
_vpnProps = config._vpnProps;
|
|
_overrideDns = config._overrideDns;
|
|
_desktopProps = config._desktopProps;
|
|
_hotKeyActions = config._hotKeyActions;
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
Map<String, dynamic> toJson() {
|
|
return _$ConfigToJson(this);
|
|
}
|
|
|
|
factory Config.fromJson(Map<String, dynamic> json) {
|
|
return _$ConfigFromJson(json);
|
|
}
|
|
}
|