Compare commits

..

1 Commits

Author SHA1 Message Date
chen08209
a98f4c345b Support override script
Support proxies search

Add some scenes auto close connections

Update core

Optimize more details
2025-05-29 16:58:46 +08:00
23 changed files with 997 additions and 155 deletions

View File

@@ -385,7 +385,6 @@
"logsTest": "Logs test",
"emptyTip": "{label} cannot be empty",
"urlTip": "{label} must be a url",
"proxyPortTip": "proxy port must be between 1024 and 49151",
"numberTip": "{label} must be a number",
"interval": "Interval",
"existsTip": "Current {label} already exists",
@@ -397,5 +396,11 @@
"rename": "Rename",
"unnamed": "Unnamed",
"pleaseEnterScriptName": "Please enter a script name",
"overrideInvalidTip": "Does not take effect in script mode"
"overrideInvalidTip": "Does not take effect in script mode",
"mixedPort": "Mixed Port",
"socksPort": "Socks Port",
"redirPort": "Redir Port",
"tproxyPort": "Tproxy Port",
"portTip": "{label} must be between 1024 and 49151",
"portConflictTip": "Please enter a different port"
}

View File

@@ -386,7 +386,6 @@
"logsTest": "ログテスト",
"emptyTip": "{label}は空欄にできません",
"urlTip": "{label}はURLである必要があります",
"proxyPortTip": "プロキシポートは1024から49151の間でなければなりません",
"numberTip": "{label}は数字でなければなりません",
"interval": "インターバル",
"existsTip": "現在の{label}は既に存在しています",
@@ -398,5 +397,11 @@
"rename": "リネーム",
"unnamed": "無題",
"pleaseEnterScriptName": "スクリプト名を入力してください",
"overrideInvalidTip": "スクリプトモードでは有効になりません"
"overrideInvalidTip": "スクリプトモードでは有効になりません",
"mixedPort": "混合ポート",
"socksPort": "Socksポート",
"redirPort": "Redirポート",
"tproxyPort": "Tproxyポート",
"portTip": "{label} は 1024 から 49151 の間でなければなりません",
"portConflictTip": "別のポートを入力してください"
}

View File

@@ -386,7 +386,6 @@
"logsTest": "Тест журналов",
"emptyTip": "{label} не может быть пустым",
"urlTip": "{label} должен быть URL",
"proxyPortTip": "Порт прокси должен быть в диапазоне от 1024 до 49151",
"numberTip": "{label} должно быть числом",
"interval": "Интервал",
"existsTip": "Текущий {label} уже существует",
@@ -398,5 +397,11 @@
"rename": "Переименовать",
"unnamed": "Без имени",
"pleaseEnterScriptName": "Пожалуйста, введите название скрипта",
"overrideInvalidTip": "В скриптовом режиме не действует"
"overrideInvalidTip": "В скриптовом режиме не действует",
"mixedPort": "Смешанный порт",
"socksPort": "Socks-порт",
"redirPort": "Redir-порт",
"tproxyPort": "Tproxy-порт",
"portTip": "{label} должен быть числом от 1024 до 49151",
"portConflictTip": "Введите другой порт"
}

View File

@@ -386,7 +386,6 @@
"logsTest": "日志测试",
"emptyTip": "{label}不能为空",
"urlTip": "{label}必须为URL",
"proxyPortTip": "代理端口必须在1024到49151之间",
"numberTip": "{label}必须为数字",
"interval": "间隔",
"existsTip": "{label}当前已存在",
@@ -398,5 +397,11 @@
"rename": "重命名",
"unnamed": "未命名",
"pleaseEnterScriptName": "请输入脚本名称",
"overrideInvalidTip": "在脚本模式下不生效"
"overrideInvalidTip": "在脚本模式下不生效",
"mixedPort": "混合端口",
"socksPort": "Socks端口",
"redirPort": "Redir端口",
"tproxyPort": "Tproxy端口",
"portTip": "{label} 必须在 1024 到 49151 之间",
"portConflictTip": "请输入不同的端口"
}

View File

@@ -28,6 +28,7 @@ final defaultTextScaleFactor =
const httpTimeoutDuration = Duration(milliseconds: 5000);
const moreDuration = Duration(milliseconds: 100);
const animateDuration = Duration(milliseconds: 100);
const midDuration = Duration(milliseconds: 200);
const commonDuration = Duration(milliseconds: 300);
const defaultUpdateDuration = Duration(days: 1);
const mmdbFileName = "geoip.metadb";

View File

@@ -43,6 +43,16 @@ class Request {
return response;
}
Future<Response> getTextResponseForUrl(String url) async {
final response = await _clashDio.get(
url,
options: Options(
responseType: ResponseType.plain,
),
);
return response;
}
Future<MemoryImage?> getImage(String url) async {
if (url.isEmpty) return null;
final response = await _dio.get<Uint8List>(

View File

@@ -34,9 +34,11 @@ class MessageLookup extends MessageLookupByLibrary {
static String m5(label) => "${label} must be a number";
static String m6(count) => "${count} items have been selected";
static String m6(label) => "${label} must be between 1024 and 49151";
static String m7(label) => "${label} must be a url";
static String m7(count) => "${count} items have been selected";
static String m8(label) => "${label} must be a url";
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
@@ -382,6 +384,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Modify the default system exit event",
),
"minutes": MessageLookupByLibrary.simpleMessage("Minutes"),
"mixedPort": MessageLookupByLibrary.simpleMessage("Mixed Port"),
"mode": MessageLookupByLibrary.simpleMessage("Mode"),
"monochromeScheme": MessageLookupByLibrary.simpleMessage("Monochrome"),
"months": MessageLookupByLibrary.simpleMessage("Months"),
@@ -478,6 +481,10 @@ class MessageLookup extends MessageLookupByLibrary {
"Please upload a valid QR code",
),
"port": MessageLookupByLibrary.simpleMessage("Port"),
"portConflictTip": MessageLookupByLibrary.simpleMessage(
"Please enter a different port",
),
"portTip": m6,
"preferH3Desc": MessageLookupByLibrary.simpleMessage(
"Prioritize the use of DOH\'s http/3",
),
@@ -524,9 +531,6 @@ class MessageLookup extends MessageLookupByLibrary {
"proxyPortDesc": MessageLookupByLibrary.simpleMessage(
"Set the Clash listening port",
),
"proxyPortTip": MessageLookupByLibrary.simpleMessage(
"proxy port must be between 1024 and 49151",
),
"proxyProviders": MessageLookupByLibrary.simpleMessage("Proxy providers"),
"pureBlackMode": MessageLookupByLibrary.simpleMessage("Pure black mode"),
"qrcode": MessageLookupByLibrary.simpleMessage("QR code"),
@@ -549,6 +553,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Override",
),
"recoverySuccess": MessageLookupByLibrary.simpleMessage("Recovery success"),
"redirPort": MessageLookupByLibrary.simpleMessage("Redir Port"),
"redo": MessageLookupByLibrary.simpleMessage("redo"),
"regExp": MessageLookupByLibrary.simpleMessage("RegExp"),
"remote": MessageLookupByLibrary.simpleMessage("Remote"),
@@ -600,7 +605,7 @@ class MessageLookup extends MessageLookupByLibrary {
"seconds": MessageLookupByLibrary.simpleMessage("Seconds"),
"selectAll": MessageLookupByLibrary.simpleMessage("Select all"),
"selected": MessageLookupByLibrary.simpleMessage("Selected"),
"selectedCountTitle": m6,
"selectedCountTitle": m7,
"settings": MessageLookupByLibrary.simpleMessage("Settings"),
"show": MessageLookupByLibrary.simpleMessage("Show"),
"shrink": MessageLookupByLibrary.simpleMessage("Shrink"),
@@ -609,6 +614,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Start in the background",
),
"size": MessageLookupByLibrary.simpleMessage("Size"),
"socksPort": MessageLookupByLibrary.simpleMessage("Socks Port"),
"sort": MessageLookupByLibrary.simpleMessage("Sort"),
"source": MessageLookupByLibrary.simpleMessage("Source"),
"sourceIp": MessageLookupByLibrary.simpleMessage("Source IP"),
@@ -657,6 +663,7 @@ class MessageLookup extends MessageLookupByLibrary {
"toggle": MessageLookupByLibrary.simpleMessage("Toggle"),
"tonalSpotScheme": MessageLookupByLibrary.simpleMessage("TonalSpot"),
"tools": MessageLookupByLibrary.simpleMessage("Tools"),
"tproxyPort": MessageLookupByLibrary.simpleMessage("Tproxy Port"),
"trafficUsage": MessageLookupByLibrary.simpleMessage("Traffic usage"),
"tun": MessageLookupByLibrary.simpleMessage("TUN"),
"tunDesc": MessageLookupByLibrary.simpleMessage(
@@ -679,7 +686,7 @@ class MessageLookup extends MessageLookupByLibrary {
"urlDesc": MessageLookupByLibrary.simpleMessage(
"Obtain profile through URL",
),
"urlTip": m7,
"urlTip": m8,
"useHosts": MessageLookupByLibrary.simpleMessage("Use hosts"),
"useSystemHosts": MessageLookupByLibrary.simpleMessage("Use system hosts"),
"value": MessageLookupByLibrary.simpleMessage("Value"),

View File

@@ -32,9 +32,11 @@ class MessageLookup extends MessageLookupByLibrary {
static String m5(label) => "${label}は数字でなければなりません";
static String m6(count) => "${count} 項目が選択されています";
static String m6(label) => "${label} は 1024 から 49151 の間でなければなりません";
static String m7(label) => "${label}はURLである必要があります";
static String m7(count) => "${count} 項目が選択されています";
static String m8(label) => "${label}はURLである必要があります";
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
@@ -286,6 +288,7 @@ class MessageLookup extends MessageLookupByLibrary {
"システムの終了イベントを変更",
),
"minutes": MessageLookupByLibrary.simpleMessage(""),
"mixedPort": MessageLookupByLibrary.simpleMessage("混合ポート"),
"mode": MessageLookupByLibrary.simpleMessage("モード"),
"monochromeScheme": MessageLookupByLibrary.simpleMessage("モノクローム"),
"months": MessageLookupByLibrary.simpleMessage(""),
@@ -364,6 +367,8 @@ class MessageLookup extends MessageLookupByLibrary {
"有効なQRコードをアップロードしてください",
),
"port": MessageLookupByLibrary.simpleMessage("ポート"),
"portConflictTip": MessageLookupByLibrary.simpleMessage("別のポートを入力してください"),
"portTip": m6,
"preferH3Desc": MessageLookupByLibrary.simpleMessage("DOHのHTTP/3を優先使用"),
"pressKeyboard": MessageLookupByLibrary.simpleMessage("キーボードを押してください"),
"preview": MessageLookupByLibrary.simpleMessage("プレビュー"),
@@ -400,9 +405,6 @@ class MessageLookup extends MessageLookupByLibrary {
),
"proxyPort": MessageLookupByLibrary.simpleMessage("プロキシポート"),
"proxyPortDesc": MessageLookupByLibrary.simpleMessage("Clashのリスニングポートを設定"),
"proxyPortTip": MessageLookupByLibrary.simpleMessage(
"プロキシポートは1024から49151の間でなければなりません",
),
"proxyProviders": MessageLookupByLibrary.simpleMessage("プロキシプロバイダー"),
"pureBlackMode": MessageLookupByLibrary.simpleMessage("純黒モード"),
"qrcode": MessageLookupByLibrary.simpleMessage("QRコード"),
@@ -417,6 +419,7 @@ class MessageLookup extends MessageLookupByLibrary {
"オーバーライド",
),
"recoverySuccess": MessageLookupByLibrary.simpleMessage("復元成功"),
"redirPort": MessageLookupByLibrary.simpleMessage("Redirポート"),
"redo": MessageLookupByLibrary.simpleMessage("やり直す"),
"regExp": MessageLookupByLibrary.simpleMessage("正規表現"),
"remote": MessageLookupByLibrary.simpleMessage("リモート"),
@@ -458,13 +461,14 @@ class MessageLookup extends MessageLookupByLibrary {
"seconds": MessageLookupByLibrary.simpleMessage(""),
"selectAll": MessageLookupByLibrary.simpleMessage("すべて選択"),
"selected": MessageLookupByLibrary.simpleMessage("選択済み"),
"selectedCountTitle": m6,
"selectedCountTitle": m7,
"settings": MessageLookupByLibrary.simpleMessage("設定"),
"show": MessageLookupByLibrary.simpleMessage("表示"),
"shrink": MessageLookupByLibrary.simpleMessage("縮小"),
"silentLaunch": MessageLookupByLibrary.simpleMessage("バックグラウンド起動"),
"silentLaunchDesc": MessageLookupByLibrary.simpleMessage("バックグラウンドで起動"),
"size": MessageLookupByLibrary.simpleMessage("サイズ"),
"socksPort": MessageLookupByLibrary.simpleMessage("Socksポート"),
"sort": MessageLookupByLibrary.simpleMessage("並び替え"),
"source": MessageLookupByLibrary.simpleMessage("ソース"),
"sourceIp": MessageLookupByLibrary.simpleMessage("送信元IP"),
@@ -505,6 +509,7 @@ class MessageLookup extends MessageLookupByLibrary {
"toggle": MessageLookupByLibrary.simpleMessage("トグル"),
"tonalSpotScheme": MessageLookupByLibrary.simpleMessage("トーンスポット"),
"tools": MessageLookupByLibrary.simpleMessage("ツール"),
"tproxyPort": MessageLookupByLibrary.simpleMessage("Tproxyポート"),
"trafficUsage": MessageLookupByLibrary.simpleMessage("トラフィック使用量"),
"tun": MessageLookupByLibrary.simpleMessage("TUN"),
"tunDesc": MessageLookupByLibrary.simpleMessage("管理者モードでのみ有効"),
@@ -523,7 +528,7 @@ class MessageLookup extends MessageLookupByLibrary {
"upload": MessageLookupByLibrary.simpleMessage("アップロード"),
"url": MessageLookupByLibrary.simpleMessage("URL"),
"urlDesc": MessageLookupByLibrary.simpleMessage("URL経由でプロファイルを取得"),
"urlTip": m7,
"urlTip": m8,
"useHosts": MessageLookupByLibrary.simpleMessage("ホストを使用"),
"useSystemHosts": MessageLookupByLibrary.simpleMessage("システムホストを使用"),
"value": MessageLookupByLibrary.simpleMessage(""),

View File

@@ -33,9 +33,11 @@ class MessageLookup extends MessageLookupByLibrary {
static String m5(label) => "${label} должно быть числом";
static String m6(count) => "Выбрано ${count} элементов";
static String m6(label) => "${label} должен быть числом от 1024 до 49151";
static String m7(label) => "${label} должен быть URL";
static String m7(count) => "Выбрано ${count} элементов";
static String m8(label) => "${label} должен быть URL";
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
@@ -407,6 +409,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Изменить стандартное событие выхода из системы",
),
"minutes": MessageLookupByLibrary.simpleMessage("Минут"),
"mixedPort": MessageLookupByLibrary.simpleMessage("Смешанный порт"),
"mode": MessageLookupByLibrary.simpleMessage("Режим"),
"monochromeScheme": MessageLookupByLibrary.simpleMessage("Монохром"),
"months": MessageLookupByLibrary.simpleMessage("Месяцев"),
@@ -507,6 +510,10 @@ class MessageLookup extends MessageLookupByLibrary {
"Пожалуйста, загрузите действительный QR-код",
),
"port": MessageLookupByLibrary.simpleMessage("Порт"),
"portConflictTip": MessageLookupByLibrary.simpleMessage(
"Введите другой порт",
),
"portTip": m6,
"preferH3Desc": MessageLookupByLibrary.simpleMessage(
"Приоритетное использование HTTP/3 для DOH",
),
@@ -555,9 +562,6 @@ class MessageLookup extends MessageLookupByLibrary {
"proxyPortDesc": MessageLookupByLibrary.simpleMessage(
"Установить порт прослушивания Clash",
),
"proxyPortTip": MessageLookupByLibrary.simpleMessage(
"Порт прокси должен быть в диапазоне от 1024 до 49151",
),
"proxyProviders": MessageLookupByLibrary.simpleMessage("Провайдеры прокси"),
"pureBlackMode": MessageLookupByLibrary.simpleMessage("Чисто черный режим"),
"qrcode": MessageLookupByLibrary.simpleMessage("QR-код"),
@@ -584,6 +588,7 @@ class MessageLookup extends MessageLookupByLibrary {
"recoverySuccess": MessageLookupByLibrary.simpleMessage(
"Восстановление успешно",
),
"redirPort": MessageLookupByLibrary.simpleMessage("Redir-порт"),
"redo": MessageLookupByLibrary.simpleMessage("Повторить"),
"regExp": MessageLookupByLibrary.simpleMessage("Регулярное выражение"),
"remote": MessageLookupByLibrary.simpleMessage("Удаленный"),
@@ -637,7 +642,7 @@ class MessageLookup extends MessageLookupByLibrary {
"seconds": MessageLookupByLibrary.simpleMessage("Секунд"),
"selectAll": MessageLookupByLibrary.simpleMessage("Выбрать все"),
"selected": MessageLookupByLibrary.simpleMessage("Выбрано"),
"selectedCountTitle": m6,
"selectedCountTitle": m7,
"settings": MessageLookupByLibrary.simpleMessage("Настройки"),
"show": MessageLookupByLibrary.simpleMessage("Показать"),
"shrink": MessageLookupByLibrary.simpleMessage("Сжать"),
@@ -646,6 +651,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Запуск в фоновом режиме",
),
"size": MessageLookupByLibrary.simpleMessage("Размер"),
"socksPort": MessageLookupByLibrary.simpleMessage("Socks-порт"),
"sort": MessageLookupByLibrary.simpleMessage("Сортировка"),
"source": MessageLookupByLibrary.simpleMessage("Источник"),
"sourceIp": MessageLookupByLibrary.simpleMessage("Исходный IP"),
@@ -694,6 +700,7 @@ class MessageLookup extends MessageLookupByLibrary {
"toggle": MessageLookupByLibrary.simpleMessage("Переключить"),
"tonalSpotScheme": MessageLookupByLibrary.simpleMessage("Тональный акцент"),
"tools": MessageLookupByLibrary.simpleMessage("Инструменты"),
"tproxyPort": MessageLookupByLibrary.simpleMessage("Tproxy-порт"),
"trafficUsage": MessageLookupByLibrary.simpleMessage(
"Использование трафика",
),
@@ -720,7 +727,7 @@ class MessageLookup extends MessageLookupByLibrary {
"urlDesc": MessageLookupByLibrary.simpleMessage(
"Получить профиль через URL",
),
"urlTip": m7,
"urlTip": m8,
"useHosts": MessageLookupByLibrary.simpleMessage("Использовать hosts"),
"useSystemHosts": MessageLookupByLibrary.simpleMessage(
"Использовать системные hosts",

View File

@@ -32,9 +32,11 @@ class MessageLookup extends MessageLookupByLibrary {
static String m5(label) => "${label}必须为数字";
static String m6(count) => "已选择 ${count}";
static String m6(label) => "${label} 必须在 1024 到 49151 之间";
static String m7(label) => "${label}必须为URL";
static String m7(count) => "已选择 ${count}";
static String m8(label) => "${label}必须为URL";
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
@@ -258,6 +260,7 @@ class MessageLookup extends MessageLookupByLibrary {
"minimizeOnExit": MessageLookupByLibrary.simpleMessage("退出时最小化"),
"minimizeOnExitDesc": MessageLookupByLibrary.simpleMessage("修改系统默认退出事件"),
"minutes": MessageLookupByLibrary.simpleMessage("分钟"),
"mixedPort": MessageLookupByLibrary.simpleMessage("混合端口"),
"mode": MessageLookupByLibrary.simpleMessage("模式"),
"monochromeScheme": MessageLookupByLibrary.simpleMessage("单色"),
"months": MessageLookupByLibrary.simpleMessage(""),
@@ -318,6 +321,8 @@ class MessageLookup extends MessageLookupByLibrary {
"请上传有效的二维码",
),
"port": MessageLookupByLibrary.simpleMessage("端口"),
"portConflictTip": MessageLookupByLibrary.simpleMessage("请输入不同的端口"),
"portTip": m6,
"preferH3Desc": MessageLookupByLibrary.simpleMessage("优先使用DOH的http/3"),
"pressKeyboard": MessageLookupByLibrary.simpleMessage("请按下按键"),
"preview": MessageLookupByLibrary.simpleMessage("预览"),
@@ -350,7 +355,6 @@ class MessageLookup extends MessageLookupByLibrary {
"proxyNameserverDesc": MessageLookupByLibrary.simpleMessage("用于解析代理节点的域名"),
"proxyPort": MessageLookupByLibrary.simpleMessage("代理端口"),
"proxyPortDesc": MessageLookupByLibrary.simpleMessage("设置Clash监听端口"),
"proxyPortTip": MessageLookupByLibrary.simpleMessage("代理端口必须在1024到49151之间"),
"proxyProviders": MessageLookupByLibrary.simpleMessage("代理提供者"),
"pureBlackMode": MessageLookupByLibrary.simpleMessage("纯黑模式"),
"qrcode": MessageLookupByLibrary.simpleMessage("二维码"),
@@ -363,6 +367,7 @@ class MessageLookup extends MessageLookupByLibrary {
"recoveryStrategy_compatible": MessageLookupByLibrary.simpleMessage("兼容"),
"recoveryStrategy_override": MessageLookupByLibrary.simpleMessage("覆盖"),
"recoverySuccess": MessageLookupByLibrary.simpleMessage("恢复成功"),
"redirPort": MessageLookupByLibrary.simpleMessage("Redir端口"),
"redo": MessageLookupByLibrary.simpleMessage("重做"),
"regExp": MessageLookupByLibrary.simpleMessage("正则"),
"remote": MessageLookupByLibrary.simpleMessage("远程"),
@@ -398,13 +403,14 @@ class MessageLookup extends MessageLookupByLibrary {
"seconds": MessageLookupByLibrary.simpleMessage(""),
"selectAll": MessageLookupByLibrary.simpleMessage("全选"),
"selected": MessageLookupByLibrary.simpleMessage("已选择"),
"selectedCountTitle": m6,
"selectedCountTitle": m7,
"settings": MessageLookupByLibrary.simpleMessage("设置"),
"show": MessageLookupByLibrary.simpleMessage("显示"),
"shrink": MessageLookupByLibrary.simpleMessage("紧凑"),
"silentLaunch": MessageLookupByLibrary.simpleMessage("静默启动"),
"silentLaunchDesc": MessageLookupByLibrary.simpleMessage("后台启动"),
"size": MessageLookupByLibrary.simpleMessage("尺寸"),
"socksPort": MessageLookupByLibrary.simpleMessage("Socks端口"),
"sort": MessageLookupByLibrary.simpleMessage("排序"),
"source": MessageLookupByLibrary.simpleMessage("来源"),
"sourceIp": MessageLookupByLibrary.simpleMessage("源IP"),
@@ -443,6 +449,7 @@ class MessageLookup extends MessageLookupByLibrary {
"toggle": MessageLookupByLibrary.simpleMessage("切换"),
"tonalSpotScheme": MessageLookupByLibrary.simpleMessage("调性点缀"),
"tools": MessageLookupByLibrary.simpleMessage("工具"),
"tproxyPort": MessageLookupByLibrary.simpleMessage("Tproxy端口"),
"trafficUsage": MessageLookupByLibrary.simpleMessage("流量统计"),
"tun": MessageLookupByLibrary.simpleMessage("虚拟网卡"),
"tunDesc": MessageLookupByLibrary.simpleMessage("仅在管理员模式生效"),
@@ -459,7 +466,7 @@ class MessageLookup extends MessageLookupByLibrary {
"upload": MessageLookupByLibrary.simpleMessage("上传"),
"url": MessageLookupByLibrary.simpleMessage("URL"),
"urlDesc": MessageLookupByLibrary.simpleMessage("通过URL获取配置文件"),
"urlTip": m7,
"urlTip": m8,
"useHosts": MessageLookupByLibrary.simpleMessage("使用Hosts"),
"useSystemHosts": MessageLookupByLibrary.simpleMessage("使用系统Hosts"),
"value": MessageLookupByLibrary.simpleMessage(""),

View File

@@ -2970,16 +2970,6 @@ class AppLocalizations {
);
}
/// `proxy port must be between 1024 and 49151`
String get proxyPortTip {
return Intl.message(
'proxy port must be between 1024 and 49151',
name: 'proxyPortTip',
desc: '',
args: [],
);
}
/// `{label} must be a number`
String numberTip(Object label) {
return Intl.message(
@@ -3074,6 +3064,46 @@ class AppLocalizations {
args: [],
);
}
/// `Mixed Port`
String get mixedPort {
return Intl.message('Mixed Port', name: 'mixedPort', desc: '', args: []);
}
/// `Socks Port`
String get socksPort {
return Intl.message('Socks Port', name: 'socksPort', desc: '', args: []);
}
/// `Redir Port`
String get redirPort {
return Intl.message('Redir Port', name: 'redirPort', desc: '', args: []);
}
/// `Tproxy Port`
String get tproxyPort {
return Intl.message('Tproxy Port', name: 'tproxyPort', desc: '', args: []);
}
/// `{label} must be between 1024 and 49151`
String portTip(Object label) {
return Intl.message(
'$label must be between 1024 and 49151',
name: 'portTip',
desc: '',
args: [label],
);
}
/// `Please enter a different port`
String get portConflictTip {
return Intl.message(
'Please enter a different port',
name: 'portConflictTip',
desc: '',
args: [],
);
}
}
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {

View File

@@ -458,6 +458,10 @@ class ClashConfigSnippet with _$ClashConfigSnippet {
class ClashConfig with _$ClashConfig {
const factory ClashConfig({
@Default(defaultMixedPort) @JsonKey(name: "mixed-port") int mixedPort,
@Default(0) @JsonKey(name: "socks-port") int socksPort,
@Default(0) @JsonKey(name: "port") int port,
@Default(0) @JsonKey(name: "redir-port") int redirPort,
@Default(0) @JsonKey(name: "tproxy-port") int tproxyPort,
@Default(Mode.rule) Mode mode,
@Default(false) @JsonKey(name: "allow-lan") bool allowLan,
@Default(LogLevel.error) @JsonKey(name: "log-level") LogLevel logLevel,

View File

@@ -3486,6 +3486,14 @@ ClashConfig _$ClashConfigFromJson(Map<String, dynamic> json) {
mixin _$ClashConfig {
@JsonKey(name: "mixed-port")
int get mixedPort => throw _privateConstructorUsedError;
@JsonKey(name: "socks-port")
int get socksPort => throw _privateConstructorUsedError;
@JsonKey(name: "port")
int get port => throw _privateConstructorUsedError;
@JsonKey(name: "redir-port")
int get redirPort => throw _privateConstructorUsedError;
@JsonKey(name: "tproxy-port")
int get tproxyPort => throw _privateConstructorUsedError;
Mode get mode => throw _privateConstructorUsedError;
@JsonKey(name: "allow-lan")
bool get allowLan => throw _privateConstructorUsedError;
@@ -3536,6 +3544,10 @@ abstract class $ClashConfigCopyWith<$Res> {
@useResult
$Res call(
{@JsonKey(name: "mixed-port") int mixedPort,
@JsonKey(name: "socks-port") int socksPort,
@JsonKey(name: "port") int port,
@JsonKey(name: "redir-port") int redirPort,
@JsonKey(name: "tproxy-port") int tproxyPort,
Mode mode,
@JsonKey(name: "allow-lan") bool allowLan,
@JsonKey(name: "log-level") LogLevel logLevel,
@@ -3579,6 +3591,10 @@ class _$ClashConfigCopyWithImpl<$Res, $Val extends ClashConfig>
@override
$Res call({
Object? mixedPort = null,
Object? socksPort = null,
Object? port = null,
Object? redirPort = null,
Object? tproxyPort = null,
Object? mode = null,
Object? allowLan = null,
Object? logLevel = null,
@@ -3602,6 +3618,22 @@ class _$ClashConfigCopyWithImpl<$Res, $Val extends ClashConfig>
? _value.mixedPort
: mixedPort // ignore: cast_nullable_to_non_nullable
as int,
socksPort: null == socksPort
? _value.socksPort
: socksPort // ignore: cast_nullable_to_non_nullable
as int,
port: null == port
? _value.port
: port // ignore: cast_nullable_to_non_nullable
as int,
redirPort: null == redirPort
? _value.redirPort
: redirPort // ignore: cast_nullable_to_non_nullable
as int,
tproxyPort: null == tproxyPort
? _value.tproxyPort
: tproxyPort // ignore: cast_nullable_to_non_nullable
as int,
mode: null == mode
? _value.mode
: mode // ignore: cast_nullable_to_non_nullable
@@ -3714,6 +3746,10 @@ abstract class _$$ClashConfigImplCopyWith<$Res>
@useResult
$Res call(
{@JsonKey(name: "mixed-port") int mixedPort,
@JsonKey(name: "socks-port") int socksPort,
@JsonKey(name: "port") int port,
@JsonKey(name: "redir-port") int redirPort,
@JsonKey(name: "tproxy-port") int tproxyPort,
Mode mode,
@JsonKey(name: "allow-lan") bool allowLan,
@JsonKey(name: "log-level") LogLevel logLevel,
@@ -3758,6 +3794,10 @@ class __$$ClashConfigImplCopyWithImpl<$Res>
@override
$Res call({
Object? mixedPort = null,
Object? socksPort = null,
Object? port = null,
Object? redirPort = null,
Object? tproxyPort = null,
Object? mode = null,
Object? allowLan = null,
Object? logLevel = null,
@@ -3781,6 +3821,22 @@ class __$$ClashConfigImplCopyWithImpl<$Res>
? _value.mixedPort
: mixedPort // ignore: cast_nullable_to_non_nullable
as int,
socksPort: null == socksPort
? _value.socksPort
: socksPort // ignore: cast_nullable_to_non_nullable
as int,
port: null == port
? _value.port
: port // ignore: cast_nullable_to_non_nullable
as int,
redirPort: null == redirPort
? _value.redirPort
: redirPort // ignore: cast_nullable_to_non_nullable
as int,
tproxyPort: null == tproxyPort
? _value.tproxyPort
: tproxyPort // ignore: cast_nullable_to_non_nullable
as int,
mode: null == mode
? _value.mode
: mode // ignore: cast_nullable_to_non_nullable
@@ -3858,6 +3914,10 @@ class __$$ClashConfigImplCopyWithImpl<$Res>
class _$ClashConfigImpl implements _ClashConfig {
const _$ClashConfigImpl(
{@JsonKey(name: "mixed-port") this.mixedPort = defaultMixedPort,
@JsonKey(name: "socks-port") this.socksPort = 0,
@JsonKey(name: "port") this.port = 0,
@JsonKey(name: "redir-port") this.redirPort = 0,
@JsonKey(name: "tproxy-port") this.tproxyPort = 0,
this.mode = Mode.rule,
@JsonKey(name: "allow-lan") this.allowLan = false,
@JsonKey(name: "log-level") this.logLevel = LogLevel.error,
@@ -3893,6 +3953,18 @@ class _$ClashConfigImpl implements _ClashConfig {
@JsonKey(name: "mixed-port")
final int mixedPort;
@override
@JsonKey(name: "socks-port")
final int socksPort;
@override
@JsonKey(name: "port")
final int port;
@override
@JsonKey(name: "redir-port")
final int redirPort;
@override
@JsonKey(name: "tproxy-port")
final int tproxyPort;
@override
@JsonKey()
final Mode mode;
@override
@@ -3963,7 +4035,7 @@ class _$ClashConfigImpl implements _ClashConfig {
@override
String toString() {
return 'ClashConfig(mixedPort: $mixedPort, mode: $mode, allowLan: $allowLan, logLevel: $logLevel, ipv6: $ipv6, findProcessMode: $findProcessMode, keepAliveInterval: $keepAliveInterval, unifiedDelay: $unifiedDelay, tcpConcurrent: $tcpConcurrent, tun: $tun, dns: $dns, geoXUrl: $geoXUrl, geodataLoader: $geodataLoader, proxyGroups: $proxyGroups, rule: $rule, globalUa: $globalUa, externalController: $externalController, hosts: $hosts)';
return 'ClashConfig(mixedPort: $mixedPort, socksPort: $socksPort, port: $port, redirPort: $redirPort, tproxyPort: $tproxyPort, mode: $mode, allowLan: $allowLan, logLevel: $logLevel, ipv6: $ipv6, findProcessMode: $findProcessMode, keepAliveInterval: $keepAliveInterval, unifiedDelay: $unifiedDelay, tcpConcurrent: $tcpConcurrent, tun: $tun, dns: $dns, geoXUrl: $geoXUrl, geodataLoader: $geodataLoader, proxyGroups: $proxyGroups, rule: $rule, globalUa: $globalUa, externalController: $externalController, hosts: $hosts)';
}
@override
@@ -3973,6 +4045,13 @@ class _$ClashConfigImpl implements _ClashConfig {
other is _$ClashConfigImpl &&
(identical(other.mixedPort, mixedPort) ||
other.mixedPort == mixedPort) &&
(identical(other.socksPort, socksPort) ||
other.socksPort == socksPort) &&
(identical(other.port, port) || other.port == port) &&
(identical(other.redirPort, redirPort) ||
other.redirPort == redirPort) &&
(identical(other.tproxyPort, tproxyPort) ||
other.tproxyPort == tproxyPort) &&
(identical(other.mode, mode) || other.mode == mode) &&
(identical(other.allowLan, allowLan) ||
other.allowLan == allowLan) &&
@@ -4004,26 +4083,31 @@ class _$ClashConfigImpl implements _ClashConfig {
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(
runtimeType,
mixedPort,
mode,
allowLan,
logLevel,
ipv6,
findProcessMode,
keepAliveInterval,
unifiedDelay,
tcpConcurrent,
tun,
dns,
geoXUrl,
geodataLoader,
const DeepCollectionEquality().hash(_proxyGroups),
const DeepCollectionEquality().hash(_rule),
globalUa,
externalController,
const DeepCollectionEquality().hash(_hosts));
int get hashCode => Object.hashAll([
runtimeType,
mixedPort,
socksPort,
port,
redirPort,
tproxyPort,
mode,
allowLan,
logLevel,
ipv6,
findProcessMode,
keepAliveInterval,
unifiedDelay,
tcpConcurrent,
tun,
dns,
geoXUrl,
geodataLoader,
const DeepCollectionEquality().hash(_proxyGroups),
const DeepCollectionEquality().hash(_rule),
globalUa,
externalController,
const DeepCollectionEquality().hash(_hosts)
]);
/// Create a copy of ClashConfig
/// with the given fields replaced by the non-null parameter values.
@@ -4044,6 +4128,10 @@ class _$ClashConfigImpl implements _ClashConfig {
abstract class _ClashConfig implements ClashConfig {
const factory _ClashConfig(
{@JsonKey(name: "mixed-port") final int mixedPort,
@JsonKey(name: "socks-port") final int socksPort,
@JsonKey(name: "port") final int port,
@JsonKey(name: "redir-port") final int redirPort,
@JsonKey(name: "tproxy-port") final int tproxyPort,
final Mode mode,
@JsonKey(name: "allow-lan") final bool allowLan,
@JsonKey(name: "log-level") final LogLevel logLevel,
@@ -4073,6 +4161,18 @@ abstract class _ClashConfig implements ClashConfig {
@JsonKey(name: "mixed-port")
int get mixedPort;
@override
@JsonKey(name: "socks-port")
int get socksPort;
@override
@JsonKey(name: "port")
int get port;
@override
@JsonKey(name: "redir-port")
int get redirPort;
@override
@JsonKey(name: "tproxy-port")
int get tproxyPort;
@override
Mode get mode;
@override
@JsonKey(name: "allow-lan")

View File

@@ -333,6 +333,10 @@ Map<String, dynamic> _$$ClashConfigSnippetImplToJson(
_$ClashConfigImpl _$$ClashConfigImplFromJson(Map<String, dynamic> json) =>
_$ClashConfigImpl(
mixedPort: (json['mixed-port'] as num?)?.toInt() ?? defaultMixedPort,
socksPort: (json['socks-port'] as num?)?.toInt() ?? 0,
port: (json['port'] as num?)?.toInt() ?? 0,
redirPort: (json['redir-port'] as num?)?.toInt() ?? 0,
tproxyPort: (json['tproxy-port'] as num?)?.toInt() ?? 0,
mode: $enumDecodeNullable(_$ModeEnumMap, json['mode']) ?? Mode.rule,
allowLan: json['allow-lan'] as bool? ?? false,
logLevel: $enumDecodeNullable(_$LogLevelEnumMap, json['log-level']) ??
@@ -378,6 +382,10 @@ _$ClashConfigImpl _$$ClashConfigImplFromJson(Map<String, dynamic> json) =>
Map<String, dynamic> _$$ClashConfigImplToJson(_$ClashConfigImpl instance) =>
<String, dynamic>{
'mixed-port': instance.mixedPort,
'socks-port': instance.socksPort,
'port': instance.port,
'redir-port': instance.redirPort,
'tproxy-port': instance.tproxyPort,
'mode': _$ModeEnumMap[instance.mode]!,
'allow-lan': instance.allowLan,
'log-level': _$LogLevelEnumMap[instance.logLevel]!,

View File

@@ -514,6 +514,215 @@ abstract class _VM4<A, B, C, D> implements VM4<A, B, C, D> {
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$VM5<A, B, C, D, E> {
A get a => throw _privateConstructorUsedError;
B get b => throw _privateConstructorUsedError;
C get c => throw _privateConstructorUsedError;
D get d => throw _privateConstructorUsedError;
E get e => throw _privateConstructorUsedError;
/// Create a copy of VM5
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$VM5CopyWith<A, B, C, D, E, VM5<A, B, C, D, E>> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $VM5CopyWith<A, B, C, D, E, $Res> {
factory $VM5CopyWith(
VM5<A, B, C, D, E> value, $Res Function(VM5<A, B, C, D, E>) then) =
_$VM5CopyWithImpl<A, B, C, D, E, $Res, VM5<A, B, C, D, E>>;
@useResult
$Res call({A a, B b, C c, D d, E e});
}
/// @nodoc
class _$VM5CopyWithImpl<A, B, C, D, E, $Res, $Val extends VM5<A, B, C, D, E>>
implements $VM5CopyWith<A, B, C, D, E, $Res> {
_$VM5CopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of VM5
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? a = freezed,
Object? b = freezed,
Object? c = freezed,
Object? d = freezed,
Object? e = freezed,
}) {
return _then(_value.copyWith(
a: freezed == a
? _value.a
: a // ignore: cast_nullable_to_non_nullable
as A,
b: freezed == b
? _value.b
: b // ignore: cast_nullable_to_non_nullable
as B,
c: freezed == c
? _value.c
: c // ignore: cast_nullable_to_non_nullable
as C,
d: freezed == d
? _value.d
: d // ignore: cast_nullable_to_non_nullable
as D,
e: freezed == e
? _value.e
: e // ignore: cast_nullable_to_non_nullable
as E,
) as $Val);
}
}
/// @nodoc
abstract class _$$VM5ImplCopyWith<A, B, C, D, E, $Res>
implements $VM5CopyWith<A, B, C, D, E, $Res> {
factory _$$VM5ImplCopyWith(_$VM5Impl<A, B, C, D, E> value,
$Res Function(_$VM5Impl<A, B, C, D, E>) then) =
__$$VM5ImplCopyWithImpl<A, B, C, D, E, $Res>;
@override
@useResult
$Res call({A a, B b, C c, D d, E e});
}
/// @nodoc
class __$$VM5ImplCopyWithImpl<A, B, C, D, E, $Res>
extends _$VM5CopyWithImpl<A, B, C, D, E, $Res, _$VM5Impl<A, B, C, D, E>>
implements _$$VM5ImplCopyWith<A, B, C, D, E, $Res> {
__$$VM5ImplCopyWithImpl(_$VM5Impl<A, B, C, D, E> _value,
$Res Function(_$VM5Impl<A, B, C, D, E>) _then)
: super(_value, _then);
/// Create a copy of VM5
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? a = freezed,
Object? b = freezed,
Object? c = freezed,
Object? d = freezed,
Object? e = freezed,
}) {
return _then(_$VM5Impl<A, B, C, D, E>(
a: freezed == a
? _value.a
: a // ignore: cast_nullable_to_non_nullable
as A,
b: freezed == b
? _value.b
: b // ignore: cast_nullable_to_non_nullable
as B,
c: freezed == c
? _value.c
: c // ignore: cast_nullable_to_non_nullable
as C,
d: freezed == d
? _value.d
: d // ignore: cast_nullable_to_non_nullable
as D,
e: freezed == e
? _value.e
: e // ignore: cast_nullable_to_non_nullable
as E,
));
}
}
/// @nodoc
class _$VM5Impl<A, B, C, D, E> implements _VM5<A, B, C, D, E> {
const _$VM5Impl(
{required this.a,
required this.b,
required this.c,
required this.d,
required this.e});
@override
final A a;
@override
final B b;
@override
final C c;
@override
final D d;
@override
final E e;
@override
String toString() {
return 'VM5<$A, $B, $C, $D, $E>(a: $a, b: $b, c: $c, d: $d, e: $e)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$VM5Impl<A, B, C, D, E> &&
const DeepCollectionEquality().equals(other.a, a) &&
const DeepCollectionEquality().equals(other.b, b) &&
const DeepCollectionEquality().equals(other.c, c) &&
const DeepCollectionEquality().equals(other.d, d) &&
const DeepCollectionEquality().equals(other.e, e));
}
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(a),
const DeepCollectionEquality().hash(b),
const DeepCollectionEquality().hash(c),
const DeepCollectionEquality().hash(d),
const DeepCollectionEquality().hash(e));
/// Create a copy of VM5
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$VM5ImplCopyWith<A, B, C, D, E, _$VM5Impl<A, B, C, D, E>> get copyWith =>
__$$VM5ImplCopyWithImpl<A, B, C, D, E, _$VM5Impl<A, B, C, D, E>>(
this, _$identity);
}
abstract class _VM5<A, B, C, D, E> implements VM5<A, B, C, D, E> {
const factory _VM5(
{required final A a,
required final B b,
required final C c,
required final D d,
required final E e}) = _$VM5Impl<A, B, C, D, E>;
@override
A get a;
@override
B get b;
@override
C get c;
@override
D get d;
@override
E get e;
/// Create a copy of VM5
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$VM5ImplCopyWith<A, B, C, D, E, _$VM5Impl<A, B, C, D, E>> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
mixin _$StartButtonSelectorState {
bool get isInit => throw _privateConstructorUsedError;

View File

@@ -34,6 +34,17 @@ class VM4<A, B, C, D> with _$VM4<A, B, C, D> {
}) = _VM4;
}
@freezed
class VM5<A, B, C, D, E> with _$VM5<A, B, C, D, E> {
const factory VM5({
required A a,
required B b,
required C c,
required D d,
required E e,
}) = _VM5;
}
@freezed
class StartButtonSelectorState with _$StartButtonSelectorState {
const factory StartButtonSelectorState({

View File

@@ -2,6 +2,7 @@ import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/common.dart';
import 'package:fl_clash/providers/app.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -18,6 +19,7 @@ class EditorPage extends ConsumerStatefulWidget {
final String title;
final String content;
final List<Language> languages;
final bool supportRemoteDownload;
final bool titleEditable;
final Function(BuildContext context, String title, String content)? onSave;
final Future<bool> Function(
@@ -30,6 +32,7 @@ class EditorPage extends ConsumerStatefulWidget {
this.titleEditable = false,
this.onSave,
this.onPop,
this.supportRemoteDownload = false,
this.languages = const [
Language.yaml,
],
@@ -108,6 +111,30 @@ class _EditorPageState extends ConsumerState<EditorPage> {
_findController.findMode();
}
_handleRemoteDownload() async {
final url = await globalState.showCommonDialog(
child: InputDialog(
title: appLocalizations.download,
value: "",
labelText: appLocalizations.url,
validator: (value) {
if (value == null || value.isEmpty) {
return appLocalizations.emptyTip(appLocalizations.value);
}
if (!value.isUrl) {
return appLocalizations.urlTip(appLocalizations.value);
}
return null;
},
),
);
if (url == null) {
return;
}
final res = await request.getTextResponseForUrl(url);
_controller.text = res.data;
}
@override
Widget build(BuildContext context) {
final isMobileView = ref.watch(isMobileViewProvider);
@@ -157,6 +184,13 @@ class _EditorPageState extends ConsumerState<EditorPage> {
),
),
),
if (widget.supportRemoteDownload)
IconButton(
onPressed: _handleRemoteDownload,
icon: Icon(
Icons.arrow_downward,
),
),
_wrapController(
(value) => CommonPopupBox(
targetBuilder: (open) {

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:convert';
import 'dart:ffi' show Pointer;
import 'dart:io';
import 'dart:isolate';
@@ -338,6 +339,10 @@ class GlobalState {
rawConfig["socks-port"] = 0;
rawConfig["keep-alive-interval"] = realPatchConfig.keepAliveInterval;
rawConfig["mixed-port"] = realPatchConfig.mixedPort;
rawConfig["port"] = realPatchConfig.port;
rawConfig["socks-port"] = realPatchConfig.socksPort;
rawConfig["redir-port"] = realPatchConfig.redirPort;
rawConfig["tproxy-port"] = realPatchConfig.tproxyPort;
rawConfig["find-process-mode"] = realPatchConfig.findProcessMode.name;
rawConfig["allow-lan"] = realPatchConfig.allowLan;
rawConfig["mode"] = realPatchConfig.mode.name;
@@ -351,8 +356,13 @@ class GlobalState {
rawConfig["tun"]["route-address"] = realPatchConfig.tun.routeAddress;
rawConfig["tun"]["auto-route"] = realPatchConfig.tun.autoRoute;
rawConfig["geodata-loader"] = realPatchConfig.geodataLoader.name;
if (rawConfig["sniffer"] != null) {
rawConfig["sniffer"] = Sniffer.fromJson(rawConfig["sniffer"]);
if (rawConfig["sniffer"]?["sniff"] != null) {
for (final value in (rawConfig["sniffer"]?["sniff"] as Map).values) {
if (value["ports"] != null && value["ports"] is List) {
value["ports"] =
value["ports"]?.map((item) => item.toString()).toList() ?? [];
}
}
}
if (rawConfig["profile"] == null) {
rawConfig["profile"] = {};
@@ -441,8 +451,11 @@ class GlobalState {
if (res.isError) {
throw res.stringResult;
}
final value = runtime.convertValue<Map<String, dynamic>>(res) ?? config;
return value;
final value = switch (res.rawResult is Pointer) {
true => runtime.convertValue<Map<String, dynamic>>(res),
false => Map<String, dynamic>.from(res.rawResult),
};
return value ?? config;
}
}

View File

@@ -2,6 +2,7 @@ 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/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -150,46 +151,56 @@ class TestUrlItem extends ConsumerWidget {
}
}
class MixedPortItem extends ConsumerWidget {
const MixedPortItem({super.key});
class PortItem extends ConsumerWidget {
const PortItem({super.key});
handleShowPortDialog() async {
await globalState.showCommonDialog(
child: _PortDialog(),
);
// inputDelegate.onChanged(value);
}
@override
Widget build(BuildContext context, ref) {
final mixedPort =
ref.watch(patchClashConfigProvider.select((state) => state.mixedPort));
return ListItem.input(
return ListItem(
leading: const Icon(Icons.adjust_outlined),
title: Text(appLocalizations.proxyPort),
title: Text(appLocalizations.port),
subtitle: Text("$mixedPort"),
delegate: InputDelegate(
title: appLocalizations.proxyPort,
value: "$mixedPort",
validator: (String? value) {
if (value == null || value.isEmpty) {
return appLocalizations.emptyTip(appLocalizations.proxyPort);
}
final mixedPort = int.tryParse(value);
if (mixedPort == null) {
return appLocalizations.numberTip(appLocalizations.proxyPort);
}
if (mixedPort < 1024 || mixedPort > 49151) {
return appLocalizations.proxyPortTip;
}
return null;
},
onChanged: (String? value) {
if (value == null) {
return;
}
final mixedPort = int.parse(value);
ref.read(patchClashConfigProvider.notifier).updateState(
(state) => state.copyWith(
mixedPort: mixedPort,
),
);
},
resetValue: "$defaultMixedPort",
),
onTap: () {
handleShowPortDialog();
},
// delegate: InputDelegate(
// title: appLocalizations.port,
// value: "$mixedPort",
// validator: (String? value) {
// if (value == null || value.isEmpty) {
// return appLocalizations.emptyTip(appLocalizations.proxyPort);
// }
// final mixedPort = int.tryParse(value);
// if (mixedPort == null) {
// return appLocalizations.numberTip(appLocalizations.proxyPort);
// }
// if (mixedPort < 1024 || mixedPort > 49151) {
// return appLocalizations.proxyPortTip;
// }
// return null;
// },
// onChanged: (String? value) {
// if (value == null) {
// return;
// }
// final mixedPort = int.parse(value);
// ref.read(patchClashConfigProvider.notifier).updateState(
// (state) => state.copyWith(
// mixedPort: mixedPort,
// ),
// );
// },
// resetValue: "$defaultMixedPort",
// ),
);
}
}
@@ -417,7 +428,7 @@ final generalItems = <Widget>[
UaItem(),
if (system.isDesktop) KeepAliveIntervalItem(),
TestUrlItem(),
MixedPortItem(),
PortItem(),
HostsItem(),
Ipv6Item(),
AllowLanItem(),
@@ -433,3 +444,349 @@ final generalItems = <Widget>[
),
)
.toList();
class _PortDialog extends ConsumerStatefulWidget {
const _PortDialog();
@override
ConsumerState<_PortDialog> createState() => _PortDialogState();
}
class _PortDialogState extends ConsumerState<_PortDialog> {
final _formKey = GlobalKey<FormState>();
bool _isMore = false;
late TextEditingController _mixedPortController;
late TextEditingController _portController;
late TextEditingController _socksPortController;
late TextEditingController _redirPortController;
late TextEditingController _tProxyPortController;
@override
void initState() {
super.initState();
final vm5 = ref.read(patchClashConfigProvider.select((state) {
return VM5(
a: state.mixedPort,
b: state.port,
c: state.socksPort,
d: state.redirPort,
e: state.tproxyPort,
);
}));
_mixedPortController = TextEditingController(
text: vm5.a.toString(),
);
_portController = TextEditingController(
text: vm5.b.toString(),
);
_socksPortController = TextEditingController(
text: vm5.c.toString(),
);
_redirPortController = TextEditingController(
text: vm5.d.toString(),
);
_tProxyPortController = TextEditingController(
text: vm5.e.toString(),
);
}
_handleReset() async {
final res = await globalState.showMessage(
message: TextSpan(
text: appLocalizations.resetTip,
),
);
if (res != true) {
return;
}
ref.read(patchClashConfigProvider.notifier).updateState(
(state) => state.copyWith(
mixedPort: 7890,
port: 0,
socksPort: 0,
redirPort: 0,
tproxyPort: 0,
),
);
if (mounted) {
Navigator.of(context).pop();
}
}
_handleUpdate() {
if (_formKey.currentState?.validate() == false) return;
ref.read(patchClashConfigProvider.notifier).updateState(
(state) => state.copyWith(
mixedPort: int.parse(_mixedPortController.text),
port: int.parse(_portController.text),
socksPort: int.parse(_socksPortController.text),
redirPort: int.parse(_redirPortController.text),
tproxyPort: int.parse(_tProxyPortController.text),
),
);
Navigator.of(context).pop();
}
_handleMore() {
setState(() {
_isMore = !_isMore;
});
}
@override
Widget build(BuildContext context) {
return CommonDialog(
title: appLocalizations.port,
actions: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton.filledTonal(
onPressed: _handleMore,
icon: CommonExpandIcon(
expand: _isMore,
),
),
Row(
children: [
TextButton(
onPressed: _handleReset,
child: Text(appLocalizations.reset),
),
const SizedBox(
width: 4,
),
TextButton(
onPressed: _handleUpdate,
child: Text(appLocalizations.submit),
)
],
)
],
)
],
child: Form(
autovalidateMode: AutovalidateMode.onUserInteraction,
key: _formKey,
child: Padding(
padding: EdgeInsets.only(top: 8),
child: AnimatedSize(
duration: midDuration,
curve: Curves.easeOutQuad,
alignment: Alignment.topCenter,
child: Column(
spacing: 24,
children: [
TextFormField(
keyboardType: TextInputType.url,
maxLines: 1,
minLines: 1,
controller: _mixedPortController,
onFieldSubmitted: (_) {
_handleUpdate();
},
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: appLocalizations.mixedPort,
),
validator: (value) {
if (value == null || value.isEmpty) {
return appLocalizations
.emptyTip(appLocalizations.mixedPort);
}
final port = int.tryParse(value);
if (port == null) {
return appLocalizations
.numberTip(appLocalizations.mixedPort);
}
if (port < 1024 || port > 49151) {
return appLocalizations.mixedPort;
}
final ports = [
_portController.text,
_socksPortController.text,
_tProxyPortController.text,
_redirPortController.text
].map((item) => item.trim());
if (ports.contains(value.trim())) {
return appLocalizations.portConflictTip;
}
return null;
},
),
if (_isMore) ...[
TextFormField(
keyboardType: TextInputType.url,
maxLines: 1,
minLines: 1,
controller: _portController,
onFieldSubmitted: (_) {
_handleUpdate();
},
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: appLocalizations.port,
),
validator: (value) {
if (value == null || value.isEmpty) {
return appLocalizations.emptyTip(appLocalizations.port);
}
final port = int.tryParse(value);
if (port == null) {
return appLocalizations.numberTip(
appLocalizations.port,
);
}
if (port == 0) {
return null;
}
if (port < 1024 || port > 49151) {
return appLocalizations.portTip(appLocalizations.port);
}
final ports = [
_mixedPortController.text,
_socksPortController.text,
_tProxyPortController.text,
_redirPortController.text
].map((item) => item.trim());
if (ports.contains(value.trim())) {
return appLocalizations.portConflictTip;
}
return null;
},
),
TextFormField(
keyboardType: TextInputType.url,
maxLines: 1,
minLines: 1,
controller: _socksPortController,
onFieldSubmitted: (_) {
_handleUpdate();
},
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: appLocalizations.socksPort,
),
validator: (value) {
if (value == null || value.isEmpty) {
return appLocalizations
.emptyTip(appLocalizations.socksPort);
}
final port = int.tryParse(value);
if (port == null) {
return appLocalizations
.numberTip(appLocalizations.socksPort);
}
if (port == 0) {
return null;
}
if (port < 1024 || port > 49151) {
return appLocalizations
.portTip(appLocalizations.socksPort);
}
final ports = [
_portController.text,
_mixedPortController.text,
_tProxyPortController.text,
_redirPortController.text
].map((item) => item.trim());
if (ports.contains(value.trim())) {
return appLocalizations.portConflictTip;
}
return null;
},
),
TextFormField(
keyboardType: TextInputType.url,
maxLines: 1,
minLines: 1,
controller: _redirPortController,
onFieldSubmitted: (_) {
_handleUpdate();
},
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: appLocalizations.redirPort,
),
validator: (value) {
if (value == null || value.isEmpty) {
return appLocalizations
.emptyTip(appLocalizations.redirPort);
}
final port = int.tryParse(value);
if (port == null) {
return appLocalizations
.numberTip(appLocalizations.redirPort);
}
if (port == 0) {
return null;
}
if (port < 1024 || port > 49151) {
return appLocalizations
.portTip(appLocalizations.redirPort);
}
final ports = [
_portController.text,
_socksPortController.text,
_tProxyPortController.text,
_mixedPortController.text
].map((item) => item.trim());
if (ports.contains(value.trim())) {
return appLocalizations.portConflictTip;
}
return null;
},
),
TextFormField(
keyboardType: TextInputType.url,
maxLines: 1,
minLines: 1,
controller: _tProxyPortController,
onFieldSubmitted: (_) {
_handleUpdate();
},
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: appLocalizations.tproxyPort,
),
validator: (value) {
if (value == null || value.isEmpty) {
return appLocalizations
.emptyTip(appLocalizations.tproxyPort);
}
final port = int.tryParse(value);
if (port == null) {
return appLocalizations
.numberTip(appLocalizations.tproxyPort);
}
if (port == 0) {
return null;
}
if (port < 1024 || port > 49151) {
return appLocalizations.portTip(
appLocalizations.tproxyPort,
);
}
final ports = [
_portController.text,
_socksPortController.text,
_mixedPortController.text,
_redirPortController.text
].map((item) => item.trim());
if (ports.contains(value.trim())) {
return appLocalizations.portConflictTip;
}
return null;
},
),
]
],
),
),
),
),
);
}
}

View File

@@ -198,6 +198,7 @@ class _ScriptsViewState extends ConsumerState<ScriptsView> {
EditorPage(
titleEditable: true,
title: title,
supportRemoteDownload: true,
onSave: (context, title, content) {
_handleEditorSave(
context,

View File

@@ -357,10 +357,7 @@ class ListHeader extends StatefulWidget {
State<ListHeader> createState() => _ListHeaderState();
}
class _ListHeaderState extends State<ListHeader>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _iconTurns;
class _ListHeaderState extends State<ListHeader> {
var isLock = false;
String get icon => widget.group.icon;
@@ -385,39 +382,6 @@ class _ListHeaderState extends State<ListHeader>
widget.onChange(groupName);
}
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 200),
vsync: this,
);
_iconTurns = _animationController.drive(
Tween<double>(begin: 0.0, end: 0.5),
);
if (isExpand) {
_animationController.value = 1.0;
}
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
void didUpdateWidget(ListHeader oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.isExpand != widget.isExpand) {
if (isExpand) {
_animationController.forward();
} else {
_animationController.reverse();
}
}
}
Widget _buildIcon() {
return Consumer(
builder: (_, ref, child) {
@@ -599,21 +563,13 @@ class _ListHeaderState extends State<ListHeader>
SizedBox(
width: 4,
),
AnimatedBuilder(
animation: _animationController.view,
builder: (_, __) {
return IconButton.filledTonal(
onPressed: () {
_handleChange(groupName);
},
icon: RotationTransition(
turns: _iconTurns,
child: const Icon(
Icons.expand_more,
),
),
);
IconButton.filledTonal(
onPressed: () {
_handleChange(groupName);
},
icon: CommonExpandIcon(
expand: isExpand,
),
)
],
)

View File

@@ -60,6 +60,68 @@ class _EffectGestureDetectorState extends State<EffectGestureDetector>
}
}
class CommonExpandIcon extends StatefulWidget {
final bool expand;
const CommonExpandIcon({
super.key,
this.expand = false,
});
@override
State<CommonExpandIcon> createState() => _CommonExpandIconState();
}
class _CommonExpandIconState extends State<CommonExpandIcon>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _iconTurns;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 200),
vsync: this,
);
_iconTurns = _animationController.drive(
Tween<double>(begin: 0.0, end: 0.5),
);
if (widget.expand) {
_animationController.value = 1.0;
}
}
@override
void didUpdateWidget(covariant CommonExpandIcon oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.expand != widget.expand) {
if (widget.expand) {
_animationController.forward();
} else {
_animationController.reverse();
}
}
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animationController.view,
builder: (_, child) {
return RotationTransition(
turns: _iconTurns,
child: child!,
);
},
child: const Icon(
Icons.expand_more,
),
);
}
}
Widget proxyDecorator(
Widget child,
int index,

View File

@@ -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+202505281
version: 0.8.85+202505291
environment:
sdk: '>=3.1.0 <4.0.0'