Compare commits
1 Commits
v0.8.85-pr
...
v0.8.85-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f9dc02436 |
@@ -402,5 +402,8 @@
|
||||
"redirPort": "Redir Port",
|
||||
"tproxyPort": "Tproxy Port",
|
||||
"portTip": "{label} must be between 1024 and 49151",
|
||||
"portConflictTip": "Please enter a different port"
|
||||
"portConflictTip": "Please enter a different port",
|
||||
"import": "Import",
|
||||
"importFile": "Import from file",
|
||||
"importUrl": "Import from URL"
|
||||
}
|
||||
@@ -403,5 +403,8 @@
|
||||
"redirPort": "Redirポート",
|
||||
"tproxyPort": "Tproxyポート",
|
||||
"portTip": "{label} は 1024 から 49151 の間でなければなりません",
|
||||
"portConflictTip": "別のポートを入力してください"
|
||||
"portConflictTip": "別のポートを入力してください",
|
||||
"import": "インポート",
|
||||
"importFile": "ファイルからインポート",
|
||||
"importUrl": "URLからインポート"
|
||||
}
|
||||
@@ -403,5 +403,8 @@
|
||||
"redirPort": "Redir-порт",
|
||||
"tproxyPort": "Tproxy-порт",
|
||||
"portTip": "{label} должен быть числом от 1024 до 49151",
|
||||
"portConflictTip": "Введите другой порт"
|
||||
"portConflictTip": "Введите другой порт",
|
||||
"import": "Импорт",
|
||||
"importFile": "Импорт из файла",
|
||||
"importUrl": "Импорт по URL"
|
||||
}
|
||||
@@ -403,5 +403,8 @@
|
||||
"redirPort": "Redir端口",
|
||||
"tproxyPort": "Tproxy端口",
|
||||
"portTip": "{label} 必须在 1024 到 49151 之间",
|
||||
"portConflictTip": "请输入不同的端口"
|
||||
"portConflictTip": "请输入不同的端口",
|
||||
"import": "导入",
|
||||
"importFile": "通过文件导入",
|
||||
"importUrl": "通过URL导入"
|
||||
}
|
||||
|
||||
Submodule core/Clash.Meta updated: ede40c9d3e...413467ae6b
@@ -214,7 +214,10 @@ class ClashCore {
|
||||
final profilePath = await appPath.getProfilePath(id);
|
||||
final res = await clashInterface.getConfig(profilePath);
|
||||
if (res.isSuccess) {
|
||||
return res.data as Map<String, dynamic>;
|
||||
final data = (res.data ?? {}) as Map<String, dynamic>;
|
||||
data["rules"] = data["rule"];
|
||||
data.remove("rule");
|
||||
return data;
|
||||
} else {
|
||||
throw res.message;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'dart:isolate';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:fl_clash/common/constant.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:fl_clash/enum/enum.dart';
|
||||
import 'package:fl_clash/models/models.dart';
|
||||
import 'package:fl_clash/plugins/service.dart';
|
||||
@@ -240,18 +240,21 @@ class ClashLibHandler {
|
||||
return DateTime.fromMillisecondsSinceEpoch(int.parse(runTimeString));
|
||||
}
|
||||
|
||||
// Map<String, dynamic> getConfig(String path) {
|
||||
// final pathChar = path.toNativeUtf8().cast<Char>();
|
||||
// final configRaw = clashFFI.getConfig(pathChar);
|
||||
// final configString = configRaw.cast<Utf8>().toDartString();
|
||||
// if (configString.isEmpty) {
|
||||
// return {};
|
||||
// }
|
||||
// final config = json.decode(configString);
|
||||
// malloc.free(pathChar);
|
||||
// clashFFI.freeCString(configRaw);
|
||||
// return config;
|
||||
// }
|
||||
Future<Map<String, dynamic>> getConfig(String id) async {
|
||||
final path = await appPath.getProfilePath(id);
|
||||
final pathChar = path.toNativeUtf8().cast<Char>();
|
||||
final configRaw = clashFFI.getConfig(pathChar);
|
||||
final configString = configRaw.cast<Utf8>().toDartString();
|
||||
if (configString.isEmpty) {
|
||||
return {};
|
||||
}
|
||||
final config = json.decode(configString);
|
||||
config["rules"] = config["rule"];
|
||||
config.remove("rule");
|
||||
malloc.free(pathChar);
|
||||
clashFFI.freeCString(configRaw);
|
||||
return config;
|
||||
}
|
||||
|
||||
Future<String> quickStart(
|
||||
InitParams initParams,
|
||||
@@ -289,4 +292,4 @@ ClashLib? get clashLib =>
|
||||
Platform.isAndroid && !globalState.isService ? ClashLib() : null;
|
||||
|
||||
ClashLibHandler? get clashLibHandler =>
|
||||
Platform.isAndroid ? ClashLibHandler() : null;
|
||||
Platform.isAndroid && globalState.isService ? ClashLibHandler() : null;
|
||||
|
||||
@@ -11,7 +11,6 @@ export 'future.dart';
|
||||
export 'http.dart';
|
||||
export 'icons.dart';
|
||||
export 'iterable.dart';
|
||||
export 'javascript.dart';
|
||||
export 'keyboard.dart';
|
||||
export 'launch.dart';
|
||||
export 'link.dart';
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import 'package:flutter_js/flutter_js.dart';
|
||||
|
||||
class Javascript {
|
||||
static Javascript? _instance;
|
||||
|
||||
Javascript._internal();
|
||||
|
||||
JavascriptRuntime get runTime {
|
||||
return getJavascriptRuntime();
|
||||
}
|
||||
|
||||
factory Javascript() {
|
||||
_instance ??= Javascript._internal();
|
||||
return _instance!;
|
||||
}
|
||||
}
|
||||
|
||||
final js = Javascript();
|
||||
@@ -1,11 +1,10 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import 'constant.dart';
|
||||
|
||||
class AppPath {
|
||||
static AppPath? _instance;
|
||||
Completer<Directory> dataDir = Completer();
|
||||
@@ -78,17 +77,28 @@ class AppPath {
|
||||
return join(directory, "$id.yaml");
|
||||
}
|
||||
|
||||
Future<String> getProvidersPath(String id, {String filePath = ""}) async {
|
||||
Future<String> getProvidersDirPath(String id) async {
|
||||
final directory = await profilesPath;
|
||||
final path = join(
|
||||
return join(
|
||||
directory,
|
||||
"providers",
|
||||
id,
|
||||
);
|
||||
if (filePath.isNotEmpty) {
|
||||
return join(path, filePath);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
Future<String> getProvidersFilePath(
|
||||
String id,
|
||||
String type,
|
||||
String url,
|
||||
) async {
|
||||
final directory = await profilesPath;
|
||||
return join(
|
||||
directory,
|
||||
"providers",
|
||||
id,
|
||||
type,
|
||||
url.toMd5(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<String> get tempPath async {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
|
||||
import 'print.dart';
|
||||
|
||||
extension StringExtension on String {
|
||||
@@ -45,6 +47,10 @@ extension StringExtension on String {
|
||||
}
|
||||
}
|
||||
|
||||
bool get isSvg {
|
||||
return endsWith(".svg");
|
||||
}
|
||||
|
||||
bool get isRegex {
|
||||
try {
|
||||
RegExp(this);
|
||||
@@ -55,6 +61,11 @@ extension StringExtension on String {
|
||||
}
|
||||
}
|
||||
|
||||
String toMd5() {
|
||||
final bytes = utf8.encode(this);
|
||||
return md5.convert(bytes).toString();
|
||||
}
|
||||
|
||||
// bool containsToLower(String target) {
|
||||
// return toLowerCase().contains(target);
|
||||
// }
|
||||
|
||||
@@ -9,7 +9,6 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:lpinyin/lpinyin.dart';
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
class Utils {
|
||||
Color? getDelayColor(int? delay) {
|
||||
@@ -337,51 +336,51 @@ class Utils {
|
||||
);
|
||||
}
|
||||
|
||||
dynamic convertYamlNode(dynamic node) {
|
||||
if (node is YamlMap) {
|
||||
final map = <String, dynamic>{};
|
||||
YamlNode? mergeKeyNode;
|
||||
for (final entry in node.nodes.entries) {
|
||||
if (entry.key is YamlScalar &&
|
||||
(entry.key as YamlScalar).value == '<<') {
|
||||
mergeKeyNode = entry.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mergeKeyNode != null) {
|
||||
final mergeValue = mergeKeyNode.value;
|
||||
if (mergeValue is YamlMap) {
|
||||
map.addAll(convertYamlNode(mergeValue) as Map<String, dynamic>);
|
||||
} else if (mergeValue is YamlList) {
|
||||
for (final node in mergeValue.nodes) {
|
||||
if (node.value is YamlMap) {
|
||||
map.addAll(convertYamlNode(node.value) as Map<String, dynamic>);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.nodes.forEach((key, value) {
|
||||
String stringKey;
|
||||
if (key is YamlScalar) {
|
||||
stringKey = key.value.toString();
|
||||
} else {
|
||||
stringKey = key.toString();
|
||||
}
|
||||
map[stringKey] = convertYamlNode(value.value);
|
||||
});
|
||||
return map;
|
||||
} else if (node is YamlList) {
|
||||
final list = <dynamic>[];
|
||||
for (final item in node.nodes) {
|
||||
list.add(convertYamlNode(item.value));
|
||||
}
|
||||
return list;
|
||||
} else if (node is YamlScalar) {
|
||||
return node.value;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
// dynamic convertYamlNode(dynamic node) {
|
||||
// if (node is YamlMap) {
|
||||
// final map = <String, dynamic>{};
|
||||
// YamlNode? mergeKeyNode;
|
||||
// for (final entry in node.nodes.entries) {
|
||||
// if (entry.key is YamlScalar &&
|
||||
// (entry.key as YamlScalar).value == '<<') {
|
||||
// mergeKeyNode = entry.value;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// if (mergeKeyNode != null) {
|
||||
// final mergeValue = mergeKeyNode.value;
|
||||
// if (mergeValue is YamlMap) {
|
||||
// map.addAll(convertYamlNode(mergeValue) as Map<String, dynamic>);
|
||||
// } else if (mergeValue is YamlList) {
|
||||
// for (final node in mergeValue.nodes) {
|
||||
// if (node.value is YamlMap) {
|
||||
// map.addAll(convertYamlNode(node.value) as Map<String, dynamic>);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// node.nodes.forEach((key, value) {
|
||||
// String stringKey;
|
||||
// if (key is YamlScalar) {
|
||||
// stringKey = key.value.toString();
|
||||
// } else {
|
||||
// stringKey = key.toString();
|
||||
// }
|
||||
// map[stringKey] = convertYamlNode(value.value);
|
||||
// });
|
||||
// return map;
|
||||
// } else if (node is YamlList) {
|
||||
// final list = <dynamic>[];
|
||||
// for (final item in node.nodes) {
|
||||
// list.add(convertYamlNode(item.value));
|
||||
// }
|
||||
// return list;
|
||||
// } else if (node is YamlScalar) {
|
||||
// return node.value;
|
||||
// }
|
||||
// return node;
|
||||
// }
|
||||
|
||||
FutureOr<T> handleWatch<T>(Function function) async {
|
||||
if (kDebugMode) {
|
||||
|
||||
@@ -774,14 +774,14 @@ class AppController {
|
||||
|
||||
clearEffect(String profileId) async {
|
||||
final profilePath = await appPath.getProfilePath(profileId);
|
||||
final providersPath = await appPath.getProvidersPath(profileId);
|
||||
final providersDirPath = await appPath.getProvidersDirPath(profileId);
|
||||
return await Isolate.run(() async {
|
||||
final profileFile = File(profilePath);
|
||||
final isExists = await profileFile.exists();
|
||||
if (isExists) {
|
||||
profileFile.delete(recursive: true);
|
||||
}
|
||||
final providersFileDir = File(providersPath);
|
||||
final providersFileDir = File(providersDirPath);
|
||||
final providersFileIsExists = await providersFileDir.exists();
|
||||
if (providersFileIsExists) {
|
||||
providersFileDir.delete(recursive: true);
|
||||
|
||||
@@ -499,3 +499,8 @@ enum Language {
|
||||
yaml,
|
||||
javaScript,
|
||||
}
|
||||
|
||||
enum ImportOption {
|
||||
file,
|
||||
url,
|
||||
}
|
||||
|
||||
@@ -323,7 +323,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Icon configuration",
|
||||
),
|
||||
"iconStyle": MessageLookupByLibrary.simpleMessage("Icon style"),
|
||||
"import": MessageLookupByLibrary.simpleMessage("Import"),
|
||||
"importFile": MessageLookupByLibrary.simpleMessage("Import from file"),
|
||||
"importFromURL": MessageLookupByLibrary.simpleMessage("Import from URL"),
|
||||
"importUrl": MessageLookupByLibrary.simpleMessage("Import from URL"),
|
||||
"infiniteTime": MessageLookupByLibrary.simpleMessage("Long term effective"),
|
||||
"init": MessageLookupByLibrary.simpleMessage("Init"),
|
||||
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage(
|
||||
|
||||
@@ -245,7 +245,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"icon": MessageLookupByLibrary.simpleMessage("アイコン"),
|
||||
"iconConfiguration": MessageLookupByLibrary.simpleMessage("アイコン設定"),
|
||||
"iconStyle": MessageLookupByLibrary.simpleMessage("アイコンスタイル"),
|
||||
"import": MessageLookupByLibrary.simpleMessage("インポート"),
|
||||
"importFile": MessageLookupByLibrary.simpleMessage("ファイルからインポート"),
|
||||
"importFromURL": MessageLookupByLibrary.simpleMessage("URLからインポート"),
|
||||
"importUrl": MessageLookupByLibrary.simpleMessage("URLからインポート"),
|
||||
"infiniteTime": MessageLookupByLibrary.simpleMessage("長期有効"),
|
||||
"init": MessageLookupByLibrary.simpleMessage("初期化"),
|
||||
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage("正しいホットキーを入力"),
|
||||
|
||||
@@ -342,7 +342,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Конфигурация иконки",
|
||||
),
|
||||
"iconStyle": MessageLookupByLibrary.simpleMessage("Стиль иконки"),
|
||||
"import": MessageLookupByLibrary.simpleMessage("Импорт"),
|
||||
"importFile": MessageLookupByLibrary.simpleMessage("Импорт из файла"),
|
||||
"importFromURL": MessageLookupByLibrary.simpleMessage("Импорт из URL"),
|
||||
"importUrl": MessageLookupByLibrary.simpleMessage("Импорт по URL"),
|
||||
"infiniteTime": MessageLookupByLibrary.simpleMessage(
|
||||
"Долгосрочное действие",
|
||||
),
|
||||
|
||||
@@ -221,7 +221,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"icon": MessageLookupByLibrary.simpleMessage("图片"),
|
||||
"iconConfiguration": MessageLookupByLibrary.simpleMessage("图片配置"),
|
||||
"iconStyle": MessageLookupByLibrary.simpleMessage("图标样式"),
|
||||
"import": MessageLookupByLibrary.simpleMessage("导入"),
|
||||
"importFile": MessageLookupByLibrary.simpleMessage("通过文件导入"),
|
||||
"importFromURL": MessageLookupByLibrary.simpleMessage("从URL导入"),
|
||||
"importUrl": MessageLookupByLibrary.simpleMessage("通过URL导入"),
|
||||
"infiniteTime": MessageLookupByLibrary.simpleMessage("长期有效"),
|
||||
"init": MessageLookupByLibrary.simpleMessage("初始化"),
|
||||
"inputCorrectHotkey": MessageLookupByLibrary.simpleMessage("请输入正确的快捷键"),
|
||||
|
||||
@@ -3104,6 +3104,31 @@ class AppLocalizations {
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Import`
|
||||
String get import {
|
||||
return Intl.message('Import', name: 'import', desc: '', args: []);
|
||||
}
|
||||
|
||||
/// `Import from file`
|
||||
String get importFile {
|
||||
return Intl.message(
|
||||
'Import from file',
|
||||
name: 'importFile',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
|
||||
/// `Import from URL`
|
||||
String get importUrl {
|
||||
return Intl.message(
|
||||
'Import from URL',
|
||||
name: 'importUrl',
|
||||
desc: '',
|
||||
args: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||
|
||||
@@ -441,7 +441,7 @@ List<SubRule> _genSubRules(Map<String, dynamic> json) {
|
||||
class ClashConfigSnippet with _$ClashConfigSnippet {
|
||||
const factory ClashConfigSnippet({
|
||||
@Default([]) @JsonKey(name: "proxy-groups") List<ProxyGroup> proxyGroups,
|
||||
@JsonKey(fromJson: _genRule, name: "rule") @Default([]) List<Rule> rule,
|
||||
@JsonKey(fromJson: _genRule, name: "rules") @Default([]) List<Rule> rule,
|
||||
@JsonKey(name: "rule-providers", fromJson: _genRuleProviders)
|
||||
@Default([])
|
||||
List<RuleProvider> ruleProvider,
|
||||
|
||||
@@ -3216,7 +3216,7 @@ ClashConfigSnippet _$ClashConfigSnippetFromJson(Map<String, dynamic> json) {
|
||||
mixin _$ClashConfigSnippet {
|
||||
@JsonKey(name: "proxy-groups")
|
||||
List<ProxyGroup> get proxyGroups => throw _privateConstructorUsedError;
|
||||
@JsonKey(fromJson: _genRule, name: "rule")
|
||||
@JsonKey(fromJson: _genRule, name: "rules")
|
||||
List<Rule> get rule => throw _privateConstructorUsedError;
|
||||
@JsonKey(name: "rule-providers", fromJson: _genRuleProviders)
|
||||
List<RuleProvider> get ruleProvider => throw _privateConstructorUsedError;
|
||||
@@ -3241,7 +3241,7 @@ abstract class $ClashConfigSnippetCopyWith<$Res> {
|
||||
@useResult
|
||||
$Res call(
|
||||
{@JsonKey(name: "proxy-groups") List<ProxyGroup> proxyGroups,
|
||||
@JsonKey(fromJson: _genRule, name: "rule") List<Rule> rule,
|
||||
@JsonKey(fromJson: _genRule, name: "rules") List<Rule> rule,
|
||||
@JsonKey(name: "rule-providers", fromJson: _genRuleProviders)
|
||||
List<RuleProvider> ruleProvider,
|
||||
@JsonKey(name: "sub-rules", fromJson: _genSubRules)
|
||||
@@ -3299,7 +3299,7 @@ abstract class _$$ClashConfigSnippetImplCopyWith<$Res>
|
||||
@useResult
|
||||
$Res call(
|
||||
{@JsonKey(name: "proxy-groups") List<ProxyGroup> proxyGroups,
|
||||
@JsonKey(fromJson: _genRule, name: "rule") List<Rule> rule,
|
||||
@JsonKey(fromJson: _genRule, name: "rules") List<Rule> rule,
|
||||
@JsonKey(name: "rule-providers", fromJson: _genRuleProviders)
|
||||
List<RuleProvider> ruleProvider,
|
||||
@JsonKey(name: "sub-rules", fromJson: _genSubRules)
|
||||
@@ -3351,7 +3351,7 @@ class _$ClashConfigSnippetImpl implements _ClashConfigSnippet {
|
||||
const _$ClashConfigSnippetImpl(
|
||||
{@JsonKey(name: "proxy-groups")
|
||||
final List<ProxyGroup> proxyGroups = const [],
|
||||
@JsonKey(fromJson: _genRule, name: "rule")
|
||||
@JsonKey(fromJson: _genRule, name: "rules")
|
||||
final List<Rule> rule = const [],
|
||||
@JsonKey(name: "rule-providers", fromJson: _genRuleProviders)
|
||||
final List<RuleProvider> ruleProvider = const [],
|
||||
@@ -3376,7 +3376,7 @@ class _$ClashConfigSnippetImpl implements _ClashConfigSnippet {
|
||||
|
||||
final List<Rule> _rule;
|
||||
@override
|
||||
@JsonKey(fromJson: _genRule, name: "rule")
|
||||
@JsonKey(fromJson: _genRule, name: "rules")
|
||||
List<Rule> get rule {
|
||||
if (_rule is EqualUnmodifiableListView) return _rule;
|
||||
// ignore: implicit_dynamic_type
|
||||
@@ -3448,7 +3448,7 @@ class _$ClashConfigSnippetImpl implements _ClashConfigSnippet {
|
||||
abstract class _ClashConfigSnippet implements ClashConfigSnippet {
|
||||
const factory _ClashConfigSnippet(
|
||||
{@JsonKey(name: "proxy-groups") final List<ProxyGroup> proxyGroups,
|
||||
@JsonKey(fromJson: _genRule, name: "rule") final List<Rule> rule,
|
||||
@JsonKey(fromJson: _genRule, name: "rules") final List<Rule> rule,
|
||||
@JsonKey(name: "rule-providers", fromJson: _genRuleProviders)
|
||||
final List<RuleProvider> ruleProvider,
|
||||
@JsonKey(name: "sub-rules", fromJson: _genSubRules)
|
||||
@@ -3461,7 +3461,7 @@ abstract class _ClashConfigSnippet implements ClashConfigSnippet {
|
||||
@JsonKey(name: "proxy-groups")
|
||||
List<ProxyGroup> get proxyGroups;
|
||||
@override
|
||||
@JsonKey(fromJson: _genRule, name: "rule")
|
||||
@JsonKey(fromJson: _genRule, name: "rules")
|
||||
List<Rule> get rule;
|
||||
@override
|
||||
@JsonKey(name: "rule-providers", fromJson: _genRuleProviders)
|
||||
|
||||
@@ -312,7 +312,7 @@ _$ClashConfigSnippetImpl _$$ClashConfigSnippetImplFromJson(
|
||||
?.map((e) => ProxyGroup.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
const [],
|
||||
rule: json['rule'] == null ? const [] : _genRule(json['rule'] as List?),
|
||||
rule: json['rules'] == null ? const [] : _genRule(json['rules'] as List?),
|
||||
ruleProvider: json['rule-providers'] == null
|
||||
? const []
|
||||
: _genRuleProviders(json['rule-providers'] as Map<String, dynamic>),
|
||||
@@ -325,7 +325,7 @@ Map<String, dynamic> _$$ClashConfigSnippetImplToJson(
|
||||
_$ClashConfigSnippetImpl instance) =>
|
||||
<String, dynamic>{
|
||||
'proxy-groups': instance.proxyGroups,
|
||||
'rule': instance.rule,
|
||||
'rules': instance.rule,
|
||||
'rule-providers': instance.ruleProvider,
|
||||
'sub-rules': instance.subRules,
|
||||
};
|
||||
|
||||
@@ -111,10 +111,25 @@ class _EditorPageState extends ConsumerState<EditorPage> {
|
||||
_findController.findMode();
|
||||
}
|
||||
|
||||
_handleRemoteDownload() async {
|
||||
_handleImport() async {
|
||||
final option = await globalState.showCommonDialog<ImportOption>(
|
||||
child: _ImportOptionsDialog(),
|
||||
);
|
||||
if (option == null) {
|
||||
return;
|
||||
}
|
||||
if (option == ImportOption.file) {
|
||||
final file = await picker.pickerFile();
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
final res = String.fromCharCodes(file.bytes?.toList() ?? []);
|
||||
_controller.text = res;
|
||||
return;
|
||||
}
|
||||
final url = await globalState.showCommonDialog(
|
||||
child: InputDialog(
|
||||
title: appLocalizations.download,
|
||||
title: "导入",
|
||||
value: "",
|
||||
labelText: appLocalizations.url,
|
||||
validator: (value) {
|
||||
@@ -186,7 +201,7 @@ class _EditorPageState extends ConsumerState<EditorPage> {
|
||||
),
|
||||
if (widget.supportRemoteDownload)
|
||||
IconButton(
|
||||
onPressed: _handleRemoteDownload,
|
||||
onPressed: _handleImport,
|
||||
icon: Icon(
|
||||
Icons.arrow_downward,
|
||||
),
|
||||
@@ -236,6 +251,7 @@ class _EditorPageState extends ConsumerState<EditorPage> {
|
||||
padding: EdgeInsets.only(
|
||||
right: 16,
|
||||
),
|
||||
autocompleteSymbols: true,
|
||||
focusNode: _focusNode,
|
||||
scrollbarBuilder: (context, child, details) {
|
||||
return CommonScrollBar(
|
||||
@@ -662,7 +678,45 @@ class _NoInputBorder extends InputBorder {
|
||||
double gapExtent = 0.0,
|
||||
double gapPercentage = 0.0,
|
||||
TextDirection? textDirection,
|
||||
}) {
|
||||
// Do not paint.
|
||||
}) {}
|
||||
}
|
||||
|
||||
class _ImportOptionsDialog extends StatefulWidget {
|
||||
const _ImportOptionsDialog();
|
||||
|
||||
@override
|
||||
State<_ImportOptionsDialog> createState() => _ImportOptionsDialogState();
|
||||
}
|
||||
|
||||
class _ImportOptionsDialogState extends State<_ImportOptionsDialog> {
|
||||
_handleOnTab(ImportOption value) {
|
||||
Navigator.of(context).pop(value);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CommonDialog(
|
||||
title: appLocalizations.import,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 16,
|
||||
),
|
||||
child: Wrap(
|
||||
children: [
|
||||
ListItem(
|
||||
onTap: () {
|
||||
_handleOnTab(ImportOption.url);
|
||||
},
|
||||
title: Text(appLocalizations.importUrl),
|
||||
),
|
||||
ListItem(
|
||||
onTap: () {
|
||||
_handleOnTab(ImportOption.file);
|
||||
},
|
||||
title: Text(appLocalizations.importFile),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1959,11 +1959,11 @@ class _GenColorSchemeProviderElement
|
||||
bool get ignoreConfig => (origin as GenColorSchemeProvider).ignoreConfig;
|
||||
}
|
||||
|
||||
String _$needSetupHash() => r'1116c73bb2964321de63bdca631a983d31e30d69';
|
||||
String _$needSetupHash() => r'db01ec73ea3232c99d1c5445c80e31b98785f416';
|
||||
|
||||
/// See also [needSetup].
|
||||
@ProviderFor(needSetup)
|
||||
final needSetupProvider = AutoDisposeProvider<VM2>.internal(
|
||||
final needSetupProvider = AutoDisposeProvider<VM3>.internal(
|
||||
needSetup,
|
||||
name: r'needSetupProvider',
|
||||
debugGetCreateSourceHash:
|
||||
@@ -1974,7 +1974,7 @@ final needSetupProvider = AutoDisposeProvider<VM2>.internal(
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef NeedSetupRef = AutoDisposeProviderRef<VM2>;
|
||||
typedef NeedSetupRef = AutoDisposeProviderRef<VM3>;
|
||||
String _$profileOverrideStateHash() =>
|
||||
r'fa26570a355ab39e27b1f93d1d2f358717065592';
|
||||
|
||||
|
||||
@@ -590,9 +590,19 @@ ColorScheme genColorScheme(
|
||||
}
|
||||
|
||||
@riverpod
|
||||
VM2 needSetup(Ref ref) {
|
||||
VM3 needSetup(Ref ref) {
|
||||
final profileId = ref.watch(currentProfileIdProvider);
|
||||
final content = ref.watch(
|
||||
scriptStateProvider.select((state) => state.currentScript?.content));
|
||||
return VM2(a: profileId, b: content);
|
||||
final overrideDns = ref.watch(overrideDnsProvider);
|
||||
final dns = overrideDns == true
|
||||
? ref.watch(patchClashConfigProvider.select(
|
||||
(state) => state.dns,
|
||||
))
|
||||
: null;
|
||||
return VM3(
|
||||
a: profileId,
|
||||
b: content,
|
||||
c: dns,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:ffi' show Pointer;
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
@@ -15,10 +13,10 @@ import 'package:fl_clash/plugins/service.dart';
|
||||
import 'package:fl_clash/widgets/dialog.dart';
|
||||
import 'package:fl_clash/widgets/scaffold.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_js/flutter_js.dart';
|
||||
import 'package:material_color_utilities/palettes/core_palette.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
||||
import 'common/common.dart';
|
||||
import 'controller.dart';
|
||||
@@ -193,26 +191,26 @@ class GlobalState {
|
||||
);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getProfileMap(String id) async {
|
||||
final profilePath = await appPath.getProfilePath(id);
|
||||
final res = await Isolate.run<Result<dynamic>>(() async {
|
||||
try {
|
||||
final file = File(profilePath);
|
||||
if (!await file.exists()) {
|
||||
return Result.error("");
|
||||
}
|
||||
final value = await file.readAsString();
|
||||
return Result.success(utils.convertYamlNode(loadYaml(value)));
|
||||
} catch (e) {
|
||||
return Result.error(e.toString());
|
||||
}
|
||||
});
|
||||
if (res.isSuccess) {
|
||||
return res.data as Map<String, dynamic>;
|
||||
} else {
|
||||
throw res.message;
|
||||
}
|
||||
}
|
||||
// Future<Map<String, dynamic>> getProfileMap(String id) async {
|
||||
// final profilePath = await appPath.getProfilePath(id);
|
||||
// final res = await Isolate.run<Result<dynamic>>(() async {
|
||||
// try {
|
||||
// final file = File(profilePath);
|
||||
// if (!await file.exists()) {
|
||||
// return Result.error("");
|
||||
// }
|
||||
// final value = await file.readAsString();
|
||||
// return Result.success(utils.convertYamlNode(loadYaml(value)));
|
||||
// } catch (e) {
|
||||
// return Result.error(e.toString());
|
||||
// }
|
||||
// });
|
||||
// if (res.isSuccess) {
|
||||
// return res.data as Map<String, dynamic>;
|
||||
// } else {
|
||||
// throw res.message;
|
||||
// }
|
||||
// }
|
||||
|
||||
Future<T?> showCommonDialog<T>({
|
||||
required Widget child,
|
||||
@@ -315,10 +313,11 @@ class GlobalState {
|
||||
return {};
|
||||
}
|
||||
final profileId = profile.id;
|
||||
|
||||
final rawConfig =
|
||||
await handleEvaluate(await globalState.getProfileMap(profileId));
|
||||
|
||||
final configMap = await switch (clashLibHandler != null) {
|
||||
true => clashLibHandler!.getConfig(profileId),
|
||||
false => clashCore.getConfig(profileId),
|
||||
};
|
||||
final rawConfig = await handleEvaluate(configMap);
|
||||
final routeAddress =
|
||||
config.networkProps.routeMode == RouteMode.bypassPrivate
|
||||
? defaultBypassPrivateRouteAddress
|
||||
@@ -371,9 +370,15 @@ class GlobalState {
|
||||
final proxyProviders = rawConfig["proxy-providers"] as Map;
|
||||
for (final key in proxyProviders.keys) {
|
||||
final proxyProvider = proxyProviders[key];
|
||||
if (proxyProvider["path"] != null) {
|
||||
proxyProvider["path"] = await appPath.getProvidersPath(profile.id,
|
||||
filePath: proxyProvider["path"]);
|
||||
if (proxyProvider["type"] != "http") {
|
||||
continue;
|
||||
}
|
||||
if (proxyProvider["url"] != null) {
|
||||
proxyProvider["path"] = await appPath.getProvidersFilePath(
|
||||
profile.id,
|
||||
"proxies",
|
||||
proxyProvider["url"],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -382,9 +387,15 @@ class GlobalState {
|
||||
final ruleProviders = rawConfig["rule-providers"] as Map;
|
||||
for (final key in ruleProviders.keys) {
|
||||
final ruleProvider = ruleProviders[key];
|
||||
if (ruleProvider["path"] != null) {
|
||||
ruleProvider["path"] = await appPath.getProvidersPath(profile.id,
|
||||
filePath: ruleProvider["path"]);
|
||||
if (ruleProvider["type"] != "http") {
|
||||
continue;
|
||||
}
|
||||
if (ruleProvider["url"] != null) {
|
||||
ruleProvider["path"] = await appPath.getProvidersFilePath(
|
||||
profile.id,
|
||||
"rules",
|
||||
ruleProvider["url"],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -415,11 +426,8 @@ class GlobalState {
|
||||
}
|
||||
}
|
||||
var rules = [];
|
||||
// if (rawConfig["rule"] != null) {
|
||||
// rules.addAll(rawConfig["rule"]);
|
||||
// }
|
||||
if (rawConfig["rules"] != null) {
|
||||
rules.addAll(rawConfig["rules"]);
|
||||
rules = rawConfig["rules"];
|
||||
}
|
||||
rawConfig.remove("rules");
|
||||
|
||||
@@ -442,9 +450,12 @@ class GlobalState {
|
||||
if (currentScript == null) {
|
||||
return config;
|
||||
}
|
||||
if (config["proxy-providers"] == null) {
|
||||
config["proxy-providers"] = {};
|
||||
}
|
||||
final configJs = json.encode(config);
|
||||
final runtime = js.runTime;
|
||||
final res = await js.runTime.evaluateAsync("""
|
||||
final runtime = getJavascriptRuntime();
|
||||
final res = await runtime.evaluateAsync("""
|
||||
${currentScript.content}
|
||||
main($configJs)
|
||||
""");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:fl_clash/common/common.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
class CommonTargetIcon extends StatelessWidget {
|
||||
final String src;
|
||||
@@ -33,11 +34,23 @@ class CommonTargetIcon extends StatelessWidget {
|
||||
},
|
||||
);
|
||||
}
|
||||
return CachedNetworkImage(
|
||||
imageUrl: src,
|
||||
fadeInDuration: Duration.zero,
|
||||
fadeOutDuration: Duration.zero,
|
||||
errorWidget: (_, __, ___) => _defaultIcon(),
|
||||
return FutureBuilder(
|
||||
future: DefaultCacheManager().getSingleFile(src),
|
||||
builder: (_, snapshot) {
|
||||
final data = snapshot.data;
|
||||
if (data == null) {
|
||||
return SizedBox();
|
||||
}
|
||||
return src.isSvg
|
||||
? SvgPicture.file(
|
||||
data,
|
||||
errorBuilder: (_, __, ___) => _defaultIcon(),
|
||||
)
|
||||
: Image.file(
|
||||
data,
|
||||
errorBuilder: (_, __, ___) => _defaultIcon(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
80
pubspec.lock
80
pubspec.lock
@@ -161,30 +161,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.9.5"
|
||||
cached_network_image:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cached_network_image
|
||||
sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.4.1"
|
||||
cached_network_image_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cached_network_image_platform_interface
|
||||
sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.1"
|
||||
cached_network_image_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cached_network_image_web
|
||||
sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -274,7 +250,7 @@ packages:
|
||||
source: hosted
|
||||
version: "0.3.4+2"
|
||||
crypto:
|
||||
dependency: "direct dev"
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: crypto
|
||||
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||
@@ -479,7 +455,7 @@ packages:
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_cache_manager:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_cache_manager
|
||||
sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386"
|
||||
@@ -524,6 +500,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.1"
|
||||
flutter_svg:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_svg
|
||||
sha256: d44bf546b13025ec7353091516f6881f1d4c633993cb109c3916c3a0159dadf1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@@ -886,14 +870,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
octo_image:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: octo_image
|
||||
sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -926,6 +902,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_parsing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_parsing
|
||||
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
path_provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1086,7 +1070,7 @@ packages:
|
||||
source: hosted
|
||||
version: "2.6.4"
|
||||
riverpod_lint:
|
||||
dependency: "direct dev"
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: riverpod_lint
|
||||
sha256: b05408412b0f75dec954e032c855bc28349eeed2d2187f94519e1ddfdf8b3693
|
||||
@@ -1474,6 +1458,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.1"
|
||||
vector_graphics:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics
|
||||
sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.18"
|
||||
vector_graphics_codec:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_codec
|
||||
sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.13"
|
||||
vector_graphics_compiler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_compiler
|
||||
sha256: "557a315b7d2a6dbb0aaaff84d857967ce6bdc96a63dc6ee2a57ce5a6ee5d3331"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.17"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1578,7 +1586,7 @@ packages:
|
||||
source: hosted
|
||||
version: "6.5.0"
|
||||
yaml:
|
||||
dependency: "direct main"
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||
|
||||
11
pubspec.yaml
11
pubspec.yaml
@@ -1,7 +1,7 @@
|
||||
name: fl_clash
|
||||
description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free.
|
||||
publish_to: 'none'
|
||||
version: 0.8.85+202506011
|
||||
version: 0.8.85+202506021
|
||||
environment:
|
||||
sdk: '>=3.1.0 <4.0.0'
|
||||
|
||||
@@ -42,7 +42,6 @@ dependencies:
|
||||
archive: ^3.6.1
|
||||
lpinyin: ^2.0.3
|
||||
emoji_regex: ^0.0.5
|
||||
cached_network_image: ^3.4.0
|
||||
hotkey_manager: ^0.2.3
|
||||
uni_platform: ^0.1.3
|
||||
device_info_plus: ^11.3.3
|
||||
@@ -57,7 +56,11 @@ dependencies:
|
||||
git:
|
||||
url: https://github.com/chen08209/flutter_js
|
||||
ref: master
|
||||
yaml: ^3.1.3
|
||||
# yaml: ^3.1.3
|
||||
flutter_svg: ^2.1.0
|
||||
flutter_cache_manager: ^3.4.1
|
||||
riverpod_lint: ^2.6.3
|
||||
crypto: ^3.0.3
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
@@ -69,8 +72,6 @@ dev_dependencies:
|
||||
freezed: ^2.5.1
|
||||
riverpod_generator: ^2.6.3
|
||||
custom_lint: ^0.7.0
|
||||
riverpod_lint: ^2.6.3
|
||||
crypto: ^3.0.3
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
|
||||
Reference in New Issue
Block a user