feat: 保存配置

This commit is contained in:
speakeloudest 2026-01-07 17:33:55 -08:00
parent 9563a81a6e
commit 3f429e5546
17 changed files with 4124 additions and 1271 deletions

View File

@ -31,7 +31,7 @@ class KRDomain {
// static String kr_currentDomain = "apicn.bearvpn.top";
static List<String> kr_baseDomains = ["api.hifast.biz", "api.airovpn.tel",];
static String kr_currentDomain = "api.hifast.biz1";
static String kr_currentDomain = "api.hifast.biz";
//
static List<String> kr_backupDomainUrls = [
@ -427,12 +427,6 @@ class KRDomain {
///
static Future<void> kr_preCheckDomains() async {
// Debug
// if (kDebugMode) {
// KRLogUtil.kr_i('🐛 Debug 模式,跳过域名预检测', tag: 'KRDomain');
// return;
// }
KRLogUtil.kr_i('🚀 开始预检测域名可用性', tag: 'KRDomain');
//

View File

@ -77,6 +77,24 @@ class KRAppRunData {
return kr_account.value != null && kr_account.value!.startsWith('9000');
}
/// 🔧 P1修复: (/)
/// : ,
Future<void> kr_resetRuntimeState() async {
try {
print('🔄 开始重置 KRAppRunData 运行时状态...');
// ,
await kr_initializeUserInfo();
print('✅ KRAppRunData 状态已重置');
print(' - 登录状态: ${kr_isLogin.value}');
print(' - 账号: ${kr_account.value}');
print(' - Token存在: ${kr_token != null && kr_token!.isNotEmpty}');
} catch (e) {
print('⚠️ KRAppRunData 状态重置失败: $e');
}
}
/// 🔧 2.1Token格式是否有效
/// Token是否符合JWT格式header.payload.signature
/// Token数据

View File

@ -16,7 +16,6 @@ import '../../../localization/app_translations.dart';
import '../../../localization/kr_language_utils.dart';
import '../../../model/business/kr_group_outbound_list.dart';
import '../../../model/business/kr_outbound_item.dart';
import '../../../services/kr_announcement_service.dart';
import '../../../utils/kr_event_bus.dart';
import '../../../utils/kr_update_util.dart';
import '../../../widgets/dialogs/kr_dialog.dart';
@ -87,6 +86,15 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
//
final kr_isConnected = false.obs;
// Prevent rapid toggles from racing start/stop.
final kr_isToggleBusy = false.obs;
DateTime? _lastToggleRequestTime;
// 🔧 1500ms 300ms
//
// kr_isToggleBusy
// Windows DNS/proxy 2-4
static const Duration _toggleDebounceWindow = Duration(milliseconds: 300);
//
final kr_isLatency = false.obs;
@ -116,6 +124,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
// 🔧
DateTime? _lastStatusChangeTime;
String? _lastStoppedErrorSignature;
Timer? _statusWatchdogTimer;
//
@ -311,12 +320,19 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
//
_bindConnectionStatus();
// Hiddify
// /
_setupConnectionModeListener();
//
WidgetsBinding.instance.addObserver(this);
// 🔧
_restoreSelectedNode();
//
//
//
Future.delayed(const Duration(milliseconds: 500), () {
kr_forceSyncConnectionStatus(true);
@ -327,6 +343,10 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
_startStatusWatchdog();
}
/// Windows kr_updateConnectionType
/// TUN
///
/// 🔧
Future<void> _restoreSelectedNode() async {
try {
@ -593,7 +613,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
kr_currentViewStatus.value = KRHomeViewsStatus.kr_loggedIn;
KRLogUtil.kr_i('登录状态变化:设置为已登录', tag: 'HomeController');
KRAnnouncementService().kr_checkAnnouncement();
// splash
KRLogUtil.kr_i('订阅服务已在启动页初始化,跳过重复初始化', tag: 'HomeController');
@ -748,7 +767,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
_lastStatusChangeTime = DateTime.now();
switch (status) {
case SingboxStopped():
case SingboxStopped(:final alert, :final message):
KRLogUtil.kr_i('🔴 状态: 已停止', tag: 'HomeController');
kr_connectText.value = AppTranslations.kr_home.disconnected;
kr_stopConnectionTimer();
@ -758,12 +777,17 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
kr_currentSpeed.value = "--";
kr_isLatency.value = false;
kr_isConnected.value = false;
// kr_isToggleBusy .then()
kr_isToggleBusy.value = false;
kr_currentNodeLatency.value = -2;
// isConnected
kr_isConnected.refresh();
_maybeShowStoppedError(alert: alert, message: message);
break;
case SingboxStarting():
KRLogUtil.kr_i('🟡 状态: 正在启动', tag: 'HomeController');
// stopped
_lastStoppedErrorSignature = null;
kr_connectText.value = AppTranslations.kr_home.connecting;
kr_currentSpeed.value = AppTranslations.kr_home.connecting;
kr_currentNodeLatency.value = -1;
@ -773,7 +797,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
break;
case SingboxStarted():
KRLogUtil.kr_i('🟢 状态: 已启动', tag: 'HomeController');
if (kDebugMode) {}
//
_cancelConnectionTimeout();
@ -782,13 +805,13 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
kr_updateConnectionInfo();
kr_isLatency.value = false;
kr_isConnected.value = true;
// kr_isToggleBusy .then()
kr_isToggleBusy.value = false;
// 🔧 -1(),0()
if (kr_currentNodeLatency.value == -1) {
if (kDebugMode) {}
kr_currentNodeLatency.value = 0;
kr_currentNodeLatency.refresh(); //
if (kDebugMode) {}
}
// 🔧
@ -798,7 +821,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
kr_isConnected.refresh();
// UI
update();
if (kDebugMode) {}
break;
case SingboxStopping():
KRLogUtil.kr_i('🟠 状态: 正在停止', tag: 'HomeController');
@ -883,10 +905,89 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
});
}
/// Hiddify
/// /
/// TUN权限问题导致卡顿
bool _isReconnecting = false; //
KRConnectionType? _lastSuccessfulMode; //
void _setupConnectionModeListener() {
ever(KRSingBoxImp.instance.kr_connectionType, (newType) async {
KRLogUtil.kr_i('🔄 连接类型改变: $newType(前: $_lastSuccessfulMode', tag: 'HomeController');
//
if (!kr_isConnected.value) {
KRLogUtil.kr_i(' VPN未连接仅更新配置无需重新连接', tag: 'HomeController');
_lastSuccessfulMode = newType; //
return;
}
//
// kr_updateConnectionType
if (_lastSuccessfulMode != null && newType == _lastSuccessfulMode) {
KRLogUtil.kr_w('⚠️ 检测到回滚操作不再重试newType=$newTypelastSuccessful=$_lastSuccessfulMode', tag: 'HomeController');
return;
}
//
if (_isReconnecting) {
KRLogUtil.kr_w('⚠️ 已有重连操作进行中,忽略此次切换', tag: 'HomeController');
return;
}
_isReconnecting = true;
try {
KRLogUtil.kr_i('✅ VPN已连接正在应用新的连接模式...', tag: 'HomeController');
KRCommonUtil.kr_showToast('正在切换连接模式...');
// 10
await KRSingBoxImp.instance.kr_restart().timeout(
const Duration(seconds: 10),
onTimeout: () {
KRLogUtil.kr_w('⏱️ 重连超时(10s),可能缺少权限', tag: 'HomeController');
throw TimeoutException('连接模式切换超时,请检查是否需要管理员权限');
},
);
_lastSuccessfulMode = newType; //
KRLogUtil.kr_i('✅ 连接模式切换完成', tag: 'HomeController');
KRCommonUtil.kr_showToast('连接模式已切换');
} catch (e) {
KRLogUtil.kr_e('❌ 连接模式切换失败: $e', tag: 'HomeController');
//
final errorMsg = e.toString();
if (errorMsg.contains('超时') || errorMsg.contains('权限') || errorMsg.contains('admin') || errorMsg.contains('Permission')) {
KRCommonUtil.kr_showToast('全局模式需要管理员权限,请以管理员身份运行应用');
} else {
KRCommonUtil.kr_showToast('切换失败: ${e.toString().split('\n').first}');
}
} finally {
_isReconnecting = false;
}
});
}
/// 🔧 : hiddify-app toggleConnection
Future<void> kr_toggleSwitch(bool value) async {
final currentStatus = KRSingBoxImp.instance.kr_status.value;
if (kr_isToggleBusy.value) {
KRLogUtil.kr_i('toggleSwitch ignored: toggle is busy', tag: 'HomeController');
return;
}
final now = DateTime.now();
if (_lastToggleRequestTime != null &&
now.difference(_lastToggleRequestTime!) < _toggleDebounceWindow) {
KRLogUtil.kr_i('toggleSwitch debounced: ignore rapid taps', tag: 'HomeController');
return;
}
_lastToggleRequestTime = now;
KRLogUtil.kr_i(
'🔵 toggleSwitch 被调用: value=$value, currentStatus=$currentStatus',
tag: 'HomeController');
@ -910,100 +1011,91 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
return;
}
}
kr_isToggleBusy.value = true;
// 🔧 :
_lastStatusChangeTime = DateTime.now();
try {
// 🔧 :
_lastStatusChangeTime = DateTime.now();
if (value) {
//
KRLogUtil.kr_i('🔄 开始连接...', tag: 'HomeController');
if (kDebugMode) {}
await _kr_prepareCountrySelectionBeforeStart();
final selectedAfter =
await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG');
// KRLogUtil.kr_i('准备后 SELECTED_NODE_TAG: ${selectedAfter ?? ''}',
// tag: 'HomeController');
// KRLogUtil.kr_i('准备后 kr_currentNodeName: ${kr_currentNodeName.value}',
// tag: 'HomeController');
// KRLogUtil.kr_i('准备后 kr_cutTag: ${kr_cutTag.value}',
// tag: 'HomeController');
// KRLogUtil.kr_i('准备后 kr_cutSeletedTag: ${kr_cutSeletedTag.value}',
// tag: 'HomeController');
await kr_performNodeSwitch(selectedAfter!);
await KRSingBoxImp.instance.kr_start();
KRLogUtil.kr_i('✅ 连接命令已发送', tag: 'HomeController');
if (kDebugMode) {}
// 🔧 : 3
await _waitForStatus(SingboxStatus.started().runtimeType,
maxSeconds: 3);
} else {
//
KRLogUtil.kr_i('🛑 开始断开连接...', tag: 'HomeController');
if (kDebugMode) {}
await KRSingBoxImp.instance.kr_stop().timeout(
const Duration(seconds: 10),
onTimeout: () {
KRLogUtil.kr_e('⚠️ 停止操作超时', tag: 'HomeController');
throw TimeoutException('Stop operation timeout');
},
);
KRLogUtil.kr_i('✅ 断开命令已发送', tag: 'HomeController');
if (kDebugMode) {}
// 🔧 :
final success = await _waitForStatus(
SingboxStatus.stopped().runtimeType,
maxSeconds: 3,
);
if (!success) {
//
KRLogUtil.kr_w('⚠️ VPN 停止超时3秒强制同步状态', tag: 'HomeController');
kr_forceSyncConnectionStatus();
}
}
} catch (e) {
KRLogUtil.kr_e('❌ 切换失败: $e', tag: 'HomeController');
if (kDebugMode) {}
//
kr_forceSyncConnectionStatus();
}
if (kDebugMode) {}
}
/// 🔧
/// 🔧 : bool
Future<bool> _waitForStatus(Type expectedType, {int maxSeconds = 3}) async {
if (kDebugMode) {}
final startTime = DateTime.now();
while (DateTime.now().difference(startTime).inSeconds < maxSeconds) {
final currentStatus = KRSingBoxImp.instance.kr_status.value;
if (kDebugMode) {}
if (currentStatus.runtimeType == expectedType) {
if (kDebugMode) {}
// kr_isConnected
if (value) {
//
KRLogUtil.kr_i('🔄 开始连接...', tag: 'HomeController');
await _kr_prepareCountrySelectionBeforeStart();
final selectedAfter =
await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG');
// KRLogUtil.kr_i('准备后 SELECTED_NODE_TAG: ${selectedAfter ?? ''}',
// tag: 'HomeController');
// KRLogUtil.kr_i('准备后 kr_currentNodeName: ${kr_currentNodeName.value}',
// tag: 'HomeController');
// KRLogUtil.kr_i('准备后 kr_cutTag: ${kr_cutTag.value}',
// tag: 'HomeController');
// KRLogUtil.kr_i('准备后 kr_cutSeletedTag: ${kr_cutSeletedTag.value}',
// tag: 'HomeController');
await kr_performNodeSwitch(selectedAfter!);
// VPN 使 .then().catchError()
// UI 线
KRSingBoxImp.instance.kr_start().then((_) {
KRLogUtil.kr_i('✅ 启动命令已返回(可能还在进行中)', tag: 'HomeController');
// UI status stream
// 2-4
// status stream
kr_isToggleBusy.value = false;
}).catchError((e) {
//
KRLogUtil.kr_e('❌ 启动 VPN 失败: $e', tag: 'HomeController');
kr_isToggleBusy.value = false;
kr_forceSyncConnectionStatus();
// 🔧
_lastStatusChangeTime = DateTime.now();
return true; //
}
await Future.delayed(const Duration(milliseconds: 100));
Get.snackbar(
'加载失败',
'切换 VPN 状态时出错${ e.toString()}',
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
);
});
KRLogUtil.kr_i('✅ 连接命令已发送(异步执行)', tag: 'HomeController');
// 使 catchError
Future.delayed(const Duration(seconds: 30)).then((_) {
if (kr_isToggleBusy.value) {
KRLogUtil.kr_w('⚠️ 启动超时 30 秒,强制释放开关锁', tag: 'HomeController');
kr_isToggleBusy.value = false;
}
});
} else {
//
KRLogUtil.kr_i('🛑 开始断开连接...', tag: 'HomeController');
// VPN 使 .then().catchError()
KRSingBoxImp.instance.kr_stop().then((_) {
KRLogUtil.kr_i('✅ 停止命令已返回DNS/proxy 恢复可能还在进行中)', tag: 'HomeController');
// UI status stream
// Windows DNS proxy 2-4
//
kr_isToggleBusy.value = false;
}).catchError((e) {
//
KRLogUtil.kr_e('❌ 停止 VPN 失败: $e', tag: 'HomeController');
kr_isToggleBusy.value = false;
kr_forceSyncConnectionStatus();
Get.snackbar(
'dialog.error'.tr,
'home.toggleVpnStatusError'.trParams({'error': e.toString()}),
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 3),
);
});
KRLogUtil.kr_i('✅ 断开命令已发送(异步执行)', tag: 'HomeController');
// 使 catchError
Future.delayed(const Duration(seconds: 15)).then((_) {
if (kr_isToggleBusy.value) {
KRLogUtil.kr_w('⚠️ 关闭超时 15 秒,强制释放开关锁', tag: 'HomeController');
kr_isToggleBusy.value = false;
}
});
}
// 🔧 :
final finalStatus = KRSingBoxImp.instance.kr_status.value;
KRLogUtil.kr_w(
'⏱️ 等待状态超时: 期望=${expectedType.toString()}, 实际=${finalStatus.runtimeType}, 耗时=${maxSeconds}',
tag: 'HomeController',
);
if (kDebugMode) {}
kr_forceSyncConnectionStatus();
return false; //
}
Future<void> _kr_prepareCountrySelectionBeforeStart() async {
@ -1461,92 +1553,45 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
kr_currentNodeLatency.value = -1;
kr_isLatency.value = true; //
// 🔧
KRLogUtil.kr_i('💾 保存新节点选择: $tag', tag: 'HomeController');
await KRSecureStorage()
.kr_saveData(key: 'SELECTED_NODE_TAG', value: tag);
// 🚀 A
// 使 KRSingBoxImp.kr_selectOutbound() selectOutbound API
//
// - 3
// -
// - 20 urltest
KRLogUtil.kr_i('🔄 [方案A] 调用 KRSingBoxImp.kr_selectOutbound()...', tag: 'HomeController');
// 🚀 使 selectOutbound hiddify-app
// VPNVPN开关不闪烁
KRLogUtil.kr_i('🔄 [热切换] 调用 selectOutbound 切换节点...',
tag: 'HomeController');
// 🔧 selector tag
// selectOutbound(groupTag, outboundTag) - tagtag
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
String selectorGroupTag = 'select'; //
// selector
for (var group in activeGroups) {
if (group.type == ProxyType.selector) {
selectorGroupTag = group.tag;
KRLogUtil.kr_i('🔍 找到 selector 组: $selectorGroupTag',
tag: 'HomeController');
break;
}
}
KRLogUtil.kr_i('📡 调用 selectOutbound("$selectorGroupTag", "$tag")',
tag: 'HomeController');
// sing-box selectOutbound API
final result = await KRSingBoxImp.instance.kr_singBox
.selectOutbound(selectorGroupTag, tag)
.run();
//
result.fold(
(error) {
//
KRLogUtil.kr_e('❌ selectOutbound 调用失败: $error',
tag: 'HomeController');
throw Exception('节点切换失败: $error');
},
(_) {
//
KRSingBoxImp.instance.kr_startNodeSelectionMonitor(tag);
KRLogUtil.kr_i('✅ selectOutbound 调用成功', tag: 'HomeController');
},
);
// UI
// UI响应性
kr_cutSeletedTag.value = tag;
kr_updateConnectionInfo();
// kr_moveToSelectedNode();
kr_moveToSelectedNode();
// 🔧
KRLogUtil.kr_i('⏳ [热切换] 等待内核状态同步200ms...', tag: 'HomeController');
await Future.delayed(const Duration(milliseconds: 200));
//
// 使 .then() UI
KRSingBoxImp.instance.kr_selectOutbound(tag).then((_) {
KRLogUtil.kr_i('✅ 节点热切换成功,已启动保护机制: $tag', tag: 'HomeController');
//
_kr_updateLatencyOnConnected();
}).catchError((error) {
KRLogUtil.kr_e('❌ 节点切换失败: $error', tag: 'HomeController');
// 🔍
KRLogUtil.kr_i('🔍 [验证] 检查节点切换结果...', tag: 'HomeController');
try {
final updatedGroups = KRSingBoxImp.instance.kr_activeGroups;
final selectGroup = updatedGroups.firstWhere(
(group) => group.type == ProxyType.selector,
orElse: () => throw Exception('未找到 selector 组'),
);
//
KRLogUtil.kr_i('🔄 节点选择失败,恢复到原节点: $originalTag', tag: 'HomeController');
kr_cutTag.value = originalTag;
kr_currentNodeName.value = originalTag;
kr_cutSeletedTag.value = originalTag;
KRLogUtil.kr_i(
'📊 [验证] ${selectGroup.tag}组当前选中: ${selectGroup.selected}',
tag: 'HomeController');
KRLogUtil.kr_i('📊 [验证] 目标节点: $tag', tag: 'HomeController');
if (selectGroup.selected != tag) {
KRLogUtil.kr_w('⚠️ [验证] 节点选择验证失败,实际选中: ${selectGroup.selected}',
tag: 'HomeController');
//
} else {
KRLogUtil.kr_i('✅ [验证] 节点选择验证成功!', tag: 'HomeController');
//
try {
KRSecureStorage().kr_saveData(key: 'SELECTED_NODE_TAG', value: originalTag);
} catch (e) {
KRLogUtil.kr_e('❌ 恢复节点选择失败: $e', tag: 'HomeController');
}
} catch (e) {
KRLogUtil.kr_w('⚠️ [验证] 节点验证过程出错: $e', tag: 'HomeController');
}
//
_kr_updateLatencyOnConnected();
KRCommonUtil.kr_showToast('节点切换失败,已恢复为: $originalTag');
});
KRLogUtil.kr_i('✅ 节点热切换成功VPN保持连接: $tag', tag: 'HomeController');
KRLogUtil.kr_i('✅ 节点热切换请求已发送VPN保持连接: $tag', tag: 'HomeController');
return true;
} catch (switchError) {
//
@ -2556,11 +2601,55 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
}
}
}
// 🔧
_kr_checkAndRefreshExpiredSubscribe();
});
KRLogUtil.kr_i('✅ 状态监控定时器已启动', tag: 'HomeController');
}
/// 🔧
void _kr_checkAndRefreshExpiredSubscribe() {
try {
final currentSubscribe = kr_subscribeService.kr_currentSubscribe.value;
if (currentSubscribe == null) {
//
KRLogUtil.kr_w('⚠️ 检测到无有效订阅,准备重新拉取订阅数据', tag: 'HomeController');
kr_refreshAll();
return;
}
//
if (currentSubscribe.expireTime.isEmpty) {
return; //
}
try {
final expireTime = DateTime.parse(currentSubscribe.expireTime);
// 1970
if (expireTime.year == 1970) {
return; //
}
final now = DateTime.now();
final isExpired = !expireTime.isAfter(now);
if (isExpired) {
KRLogUtil.kr_w(
'⚠️ 检测到订阅已过期: ${currentSubscribe.name}, expire=${currentSubscribe.expireTime}',
tag: 'HomeController',
);
//
kr_refreshAll();
}
} catch (e) {
KRLogUtil.kr_d('解析过期时间失败: $e', tag: 'HomeController');
}
} catch (e) {
KRLogUtil.kr_d('订阅过期检查异常: $e', tag: 'HomeController');
}
}
///
Timer? _connectionTimeoutTimer;
@ -2591,6 +2680,46 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
});
}
void _maybeShowStoppedError({SingboxAlert? alert, String? message}) {
final messageText = message?.trim();
if (alert == null && (messageText == null || messageText.isEmpty)) return;
final signature = '${alert?.name ?? ""}|${messageText ?? ""}';
if (signature == _lastStoppedErrorSignature) return;
_lastStoppedErrorSignature = signature;
final detail = _presentSingboxStoppedReason(alert: alert, message: messageText);
Get.snackbar(
'network.status.failed'.tr,
detail,
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 4),
);
}
String _presentSingboxStoppedReason({required SingboxAlert? alert, String? message}) {
final parts = <String>[];
if (alert != null) {
parts.add(_presentSingboxAlert(alert));
}
if (message != null && message.trim().isNotEmpty) {
parts.add(message.trim());
}
return parts.join('\n');
}
String _presentSingboxAlert(SingboxAlert alert) {
return switch (alert) {
SingboxAlert.requestVPNPermission => 'failure.connectivity.missingVpnPermission'.tr,
SingboxAlert.requestNotificationPermission => 'failure.connectivity.missingNotificationPermission'.tr,
SingboxAlert.emptyConfiguration => 'failure.singbox.invalidConfig'.tr,
SingboxAlert.startCommandServer => 'failure.singbox.start'.tr,
SingboxAlert.createService => 'failure.singbox.create'.tr,
SingboxAlert.startService => 'failure.singbox.start'.tr,
};
}
void _cancelConnectionTimeout() {
_connectionTimeoutTimer?.cancel();
_connectionTimeoutTimer = null;
@ -2660,7 +2789,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
return delays;
}
///
/// reduce loops and logs
bool _kr_tryUpdateDelayFromActiveGroups() {
try {
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
@ -2670,19 +2799,30 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
return false;
}
// selector
// auto
final targetTag = kr_cutTag.value == "auto" ? "auto" : kr_cutTag.value;
// selector 退
for (var group in activeGroups) {
if (group.type == ProxyType.selector) {
KRLogUtil.kr_d('找到 selector 组: ${group.tag}, 选中: ${group.selected}',
tag: 'HomeController');
try {
// 使 firstWhere
final item = group.items.firstWhere(
(item) => item.tag == targetTag && item.urlTestDelay != 0,
);
for (var item in group.items) {
if (item.tag == kr_cutTag.value && item.urlTestDelay != 0) {
kr_currentNodeLatency.value = item.urlTestDelay;
KRLogUtil.kr_i('✅ 延迟值: ${item.urlTestDelay}ms',
tag: 'HomeController');
return true;
kr_currentNodeLatency.value = item.urlTestDelay;
if (kDebugMode) {
KRLogUtil.kr_d(
'✅ 延迟更新: $targetTag = ${item.urlTestDelay}ms',
tag: 'HomeController',
);
}
return true; //
} catch (_) {
//
continue;
}
}
}

View File

@ -85,8 +85,8 @@ class HttpUtil {
client.findProxy = (url) {
try {
// SingBox
final singBoxStatus = KRSingBoxImp.instance.kr_status;
final isProxyAvailable = singBoxStatus == SingboxStatus.started();
final singBoxStatus = KRSingBoxImp.instance.kr_status.value;
final isProxyAvailable = singBoxStatus is SingboxStarted;;
if (!isProxyAvailable) {
// 使

View File

@ -225,17 +225,21 @@ class KRDeviceInfoService {
}
/// Windows设备ID - 使GUID
/// 🔧 使 flutter_udid wmic
Future<String> _getWindowsDeviceId() async {
try {
final windowsInfo = await _deviceInfo.windowsInfo;
// 使 flutter_udid
String udid = await FlutterUdid.consistentUdid;
// 🔧 使 FlutterUdid.consistentUdid
// Windows "cmd.exe /c wmic csproduct get UUID"
// 使 CREATE_NO_WINDOW
// 使 device_info_plus
// windowsInfo.deviceId
//
final factors = [
udid,
windowsInfo.deviceId, // ID
windowsInfo.deviceId, // ID ( MachineGuid)
windowsInfo.computerName, //
windowsInfo.productName, //
windowsInfo.numberOfCores.toString(), // CPU核心数

File diff suppressed because it is too large Load Diff

View File

@ -15,11 +15,31 @@ class KRThemeService extends GetxService {
final String _key = 'themeOption'; //
late ThemeMode _currentThemeOption = ThemeMode.light; //
// 🔧 P0修复: ,使
bool _isInitialized = false;
///
Future<void> init() async {
_currentThemeOption = await kr_loadThemeOptionFromStorage();
try {
_currentThemeOption = await kr_loadThemeOptionFromStorage();
_isInitialized = true;
} catch (e) {
// 使
_currentThemeOption = ThemeMode.light;
_isInitialized = true;
print('⚠️ 主题初始化失败,使用默认主题: $e');
}
}
/// 🔧 P0修复: (/)
Future<void> reset() async {
_isInitialized = false;
await init();
}
/// 🔧 P0修复:
bool get isInitialized => _isInitialized;
///
ThemeMode get kr_Theme {
switch (_currentThemeOption) {
@ -82,7 +102,7 @@ class KRThemeService extends GetxService {
),
elevatedButtonTheme: ElevatedButtonThemeData(
style:
ElevatedButton.styleFrom(backgroundColor: Colors.green), //
ElevatedButton.styleFrom(backgroundColor: Colors.green), //
),
switchTheme: SwitchThemeData(
thumbColor: WidgetStateProperty.all(Colors.white), //
@ -142,18 +162,6 @@ class KRThemeService extends GetxService {
return Colors.transparent; //
}),
),
snackBarTheme: SnackBarThemeData(
backgroundColor: Colors.black, //
contentTextStyle: TextStyle(
color: Colors.white, //
fontSize: 14,
),
actionTextColor: Colors.white, //
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), //
),
behavior: SnackBarBehavior.floating, //
),
//
);
}

View File

@ -0,0 +1,281 @@
/// 🔧 -
///
/// 使
/// 1. Debounce
/// -
/// 2. Throttle
/// - VPN /
import 'dart:async';
import 'package:get/get.dart';
class KRDebounceThrottleUtil {
//
static final Map<String, Timer> _debounceTimers = {};
//
static final Map<String, DateTime> _throttleTimestamps = {};
/// 🔧 delay
///
///
///
///
/// ```dart
/// KRDebounceThrottleUtil.debounce(
/// key: 'mode_switch',
/// delay: Duration(milliseconds: 300),
/// action: () {
/// controller.kr_updateConnectionType(newType);
/// },
/// );
/// ```
static void debounce({
required String key,
required Duration delay,
required VoidCallback action,
}) {
//
_debounceTimers[key]?.cancel();
//
_debounceTimers[key] = Timer(delay, () {
action();
_debounceTimers.remove(key);
});
}
/// 🔧 Future
///
///
///
///
/// ```dart
/// await KRDebounceThrottleUtil.debounceAsync(
/// key: 'node_switch',
/// delay: Duration(milliseconds: 500),
/// action: () async {
/// await controller.kr_performNodeSwitch(tag);
/// },
/// );
/// ```
static Future<void> debounceAsync({
required String key,
required Duration delay,
required Future<void> Function() action,
}) async {
_debounceTimers[key]?.cancel();
return Future.delayed(delay).then((_) async {
await action();
_debounceTimers.remove(key);
});
}
/// 🔧
///
/// true false
///
/// VPN /
///
///
/// ```dart
/// final canExecute = KRDebounceThrottleUtil.throttle(
/// key: 'refresh',
/// duration: Duration(seconds: 2),
/// );
///
/// if (canExecute) {
/// await controller.kr_refreshAll();
/// } else {
/// showToast('操作过于频繁,请稍后再试');
/// }
/// ```
static bool throttle({
required String key,
required Duration duration,
}) {
final now = DateTime.now();
final lastExecuteTime = _throttleTimestamps[key];
//
if (lastExecuteTime == null ||
now.difference(lastExecuteTime).inMilliseconds >= duration.inMilliseconds) {
_throttleTimestamps[key] = now;
return true; //
}
return false; //
}
/// 🔧
///
/// true false
///
///
/// ```dart
/// final success = await KRDebounceThrottleUtil.throttleAsync(
/// key: 'node_switch',
/// duration: Duration(milliseconds: 2000),
/// action: () async {
/// await controller.kr_performNodeSwitch(tag);
/// },
/// );
/// ```
static Future<bool> throttleAsync({
required String key,
required Duration duration,
required Future<void> Function() action,
}) async {
if (throttle(key: key, duration: duration)) {
try {
await action();
return true;
} catch (e) {
//
_throttleTimestamps.remove(key);
rethrow;
}
}
return false;
}
/// 🔧 key
///
///
/// - 0
/// -
static int getRemainingThrottleTime({
required String key,
required Duration duration,
}) {
final lastExecuteTime = _throttleTimestamps[key];
if (lastExecuteTime == null) return 0;
final remaining = duration.inMilliseconds -
DateTime.now().difference(lastExecuteTime).inMilliseconds;
return remaining > 0 ? remaining : 0;
}
/// 🔧 key
static void clear({String? key}) {
if (key != null) {
_debounceTimers[key]?.cancel();
_debounceTimers.remove(key);
_throttleTimestamps.remove(key);
} else {
//
for (var timer in _debounceTimers.values) {
timer.cancel();
}
_debounceTimers.clear();
_throttleTimestamps.clear();
}
}
/// 🔧
static Map<String, dynamic> getStats() {
return {
'activeDebounces': _debounceTimers.keys.toList(),
'activeThrottles': _throttleTimestamps.keys.toList(),
'totalActiveTimers': _debounceTimers.length + _throttleTimestamps.length,
};
}
}
/// 🔧 - Controller
///
///
/// ```dart
/// class MyController extends GetxController {
/// late final _debouncer = KRDebouncedMethod(
/// key: 'mode_switch',
/// delay: Duration(milliseconds: 300),
/// );
///
/// void kr_updateConnectionType(KRConnectionType type) {
/// _debouncer.call(() async {
/// //
/// });
/// }
/// }
/// ```
class KRDebouncedMethod {
final String key;
final Duration delay;
KRDebouncedMethod({
required this.key,
this.delay = const Duration(milliseconds: 300),
});
void call(VoidCallback action) {
KRDebounceThrottleUtil.debounce(
key: key,
delay: delay,
action: action,
);
}
Future<void> callAsync(Future<void> Function() action) async {
return KRDebounceThrottleUtil.debounceAsync(
key: key,
delay: delay,
action: action,
);
}
void cancel() {
KRDebounceThrottleUtil.clear(key: key);
}
}
/// 🔧 - Controller
///
///
/// ```dart
/// class MyController extends GetxController {
/// late final _throttler = KRThrottledMethod(
/// key: 'refresh',
/// duration: Duration(seconds: 2),
/// );
///
/// void kr_refreshAll() {
/// if (_throttler.canExecute()) {
/// //
/// }
/// }
/// }
/// ```
class KRThrottledMethod {
final String key;
final Duration duration;
KRThrottledMethod({
required this.key,
required this.duration,
});
bool canExecute() {
return KRDebounceThrottleUtil.throttle(key: key, duration: duration);
}
Future<bool> executeAsync(Future<void> Function() action) async {
return KRDebounceThrottleUtil.throttleAsync(
key: key,
duration: duration,
action: action,
);
}
int getRemainingTime() {
return KRDebounceThrottleUtil.getRemainingThrottleTime(
key: key,
duration: duration,
);
}
void reset() {
KRDebounceThrottleUtil.clear(key: key);
}
}

View File

@ -0,0 +1,82 @@
import 'dart:io';
import 'package:path/path.dart' as p;
/// - UI
///
/// .log 便
/// 使
class KRFileLogger {
static const String _logFileName = '日志.log';
/// 🔧 - true
static const bool _enableFileLogging = true; // UI阻塞 // false
static File? _logFile;
///
static Future<void> initialize() async {
if (!_enableFileLogging) return;
try {
// Windows exe
final appDir = Directory.current;
final logPath = p.join(appDir.path, _logFileName);
_logFile = File(logPath);
// 5MB
if (await _logFile!.exists()) {
final stat = await _logFile!.stat();
if (stat.size > 5 * 1024 * 1024) { // 5MB
await _logFile!.writeAsString('');
await _writeRaw('=== 日志文件已清空(超过 5MB===\n');
}
}
await _writeRaw('=== 日志系统初始化 - ${DateTime.now().toIso8601String()} ===\n');
} catch (e) {
//
}
}
///
static Future<void> log(String message) async {
if (!_enableFileLogging || _logFile == null) return;
try {
final timestamp = DateTime.now().toIso8601String();
final logLine = '[$timestamp] $message\n';
await _logFile!.writeAsString(logLine, mode: FileMode.append);
} catch (e) {
//
}
}
///
static Future<void> _writeRaw(String content) async {
if (!_enableFileLogging || _logFile == null) return;
try {
await _logFile!.writeAsString(content, mode: FileMode.append);
} catch (e) {
//
}
}
/// 线
static Future<void> separator() async {
if (!_enableFileLogging) return;
await _writeRaw('\n---\n');
}
///
static Future<void> clear() async {
if (!_enableFileLogging || _logFile == null) return;
try {
await _logFile!.writeAsString('');
await _writeRaw('=== 日志已清空 - ${DateTime.now().toIso8601String()} ===\n');
} catch (e) {
//
}
}
}

View File

@ -26,11 +26,11 @@ class KRWindowManager with WindowListener, TrayListener {
const WindowOptions windowOptions = WindowOptions(
size: Size(800, 668),
minimumSize: Size(400, 334),
minimumSize: Size(800, 668),
center: true,
backgroundColor: Colors.white,
skipTaskbar: false,
title: 'Hi快VPN',
title: 'Kaer VPN',
titleBarStyle: TitleBarStyle.normal,
windowButtonVisibility: true,
);
@ -47,16 +47,17 @@ class KRWindowManager with WindowListener, TrayListener {
await windowManager.setTitleBarStyle(TitleBarStyle.normal);
await windowManager.setTitle('HiFastVPN');
await windowManager.setSize(const Size(800, 668));
await windowManager.setMinimumSize(const Size(400, 334));
await windowManager.setMinimumSize(const Size(800, 668));
await windowManager.center();
await windowManager.show();
//
await windowManager.setPreventClose(true);
} else {
await windowManager.setTitle('HiFastVPN');
await windowManager.setTitle('Kaer VPN');
await windowManager.setSize(const Size(800, 668));
await windowManager.setMinimumSize(const Size(400, 334));
await windowManager.setMinimumSize(const Size(800, 668));
await windowManager.center();
await windowManager.show(); // macOS
}
//
@ -94,7 +95,7 @@ class KRWindowManager with WindowListener, TrayListener {
///
void _initPlatformChannel() {
if (Platform.isMacOS) {
const platform = MethodChannel('hifast_vpn/terminate');
const platform = MethodChannel('kaer_vpn/terminate');
platform.setMethodCallHandler((call) async {
if (call.method == 'onTerminate') {
KRLogUtil.kr_i('收到应用终止通知');
@ -149,8 +150,66 @@ class KRWindowManager with WindowListener, TrayListener {
}
/// 退
///
Future<void> _exitApp() async {
KRLogUtil.kr_i('_exitApp: 退出应用');
//
//
try {
await windowManager.show();
await windowManager.focus();
await windowManager.setAlwaysOnTop(true);
KRLogUtil.kr_i('✅ 窗口已恢复,准备显示对话框', tag: 'WindowManager');
} catch (e) {
KRLogUtil.kr_w('⚠️ 恢复窗口失败(可能已显示): $e', tag: 'WindowManager');
}
// 🔧 VPN
if (KRSingBoxImp.instance.kr_status.value is! SingboxStopped) {
KRLogUtil.kr_w('⚠️ VPN 正在运行,询问用户是否关闭', tag: 'WindowManager');
//
final shouldExit = await Get.dialog<bool>(
AlertDialog(
title: Text('关闭 VPN'),
content: Text("VPN 代理正在运行。\n\n是否现在关闭 VPN 并退出应用?\n\n(应用将等待 VPN 优雅关闭,预计 3-5 秒)"),
actions: [
TextButton(
onPressed: () => Get.back(result: false),
child: Text('取消'),
),
TextButton(
onPressed: () => Get.back(result: true),
child: Text('关闭并退出', style: const TextStyle(color: Colors.red)),
),
],
),
barrierDismissible: false,
) ?? false;
// AlwaysOnTop
try {
await windowManager.setAlwaysOnTop(false);
} catch (e) {
KRLogUtil.kr_w('⚠️ 恢复 AlwaysOnTop 失败: $e', tag: 'WindowManager');
}
if (!shouldExit) {
KRLogUtil.kr_i('_exitApp: 用户取消退出');
return;
}
KRLogUtil.kr_i('_exitApp: 用户确认关闭 VPN 并退出');
} else {
// VPN AlwaysOnTop
try {
await windowManager.setAlwaysOnTop(false);
} catch (e) {
KRLogUtil.kr_w('⚠️ 恢复 AlwaysOnTop 失败: $e', tag: 'WindowManager');
}
}
await _handleTerminate();
await windowManager.destroy();
}
@ -159,9 +218,9 @@ class KRWindowManager with WindowListener, TrayListener {
Future<void> _showWindow() async {
KRLogUtil.kr_i('_showWindow: 开始显示窗口');
try {
await windowManager.setSkipTaskbar(false);
await windowManager.show();
await windowManager.focus();
await windowManager.setSkipTaskbar(false);
await windowManager.setAlwaysOnTop(true);
await Future.delayed(const Duration(milliseconds: 100));
await windowManager.setAlwaysOnTop(false);
@ -180,6 +239,7 @@ class KRWindowManager with WindowListener, TrayListener {
@override
void onWindowClose() async {
if (Platform.isWindows) {
await windowManager.setSkipTaskbar(true);
await windowManager.hide();
} else if (Platform.isMacOS) {
await windowManager.hide();
@ -216,9 +276,29 @@ class KRWindowManager with WindowListener, TrayListener {
///
Future<void> _handleTerminate() async {
KRLogUtil.kr_i('_handleTerminate: 处理应用终止');
if (KRSingBoxImp.instance.kr_status == SingboxStatus.started()) {
await KRSingBoxImp.instance.kr_stop();
// 🔧 BUG VPN Rx
// if (KRSingBoxImp.instance.kr_status == SingboxStatus.started())
// kr_status Rx<SingboxStatus> SingboxStatus.started()
// false kr_stop() VPN
if (KRSingBoxImp.instance.kr_status.value is SingboxStarted) {
KRLogUtil.kr_i('🛑 VPN 正在运行,开始关闭...', tag: 'WindowManager');
try {
await KRSingBoxImp.instance.kr_stop();
KRLogUtil.kr_i('✅ VPN 已关闭', tag: 'WindowManager');
} catch (e) {
KRLogUtil.kr_e('❌ VPN 关闭出错: $e', tag: 'WindowManager');
}
} else {
KRLogUtil.kr_i('✅ VPN 未运行,无需关闭', tag: 'WindowManager');
}
//
try {
await trayManager.destroy();
KRLogUtil.kr_i('✅ 托盘已销毁', tag: 'WindowManager');
} catch (e) {
KRLogUtil.kr_w('⚠️ 销毁托盘出错: $e', tag: 'WindowManager');
}
await trayManager.destroy();
}
}

View File

@ -1,5 +1,6 @@
import 'dart:io';
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
import 'package:kaer_with_panels/app/utils/kr_windows_process_util.dart';
/// Windows DNS
///
@ -38,28 +39,48 @@ class KRWindowsDnsUtil {
}
try {
KRLogUtil.kr_i('📦 开始备份 Windows DNS 设置...', tag: 'WindowsDNS');
// 🔒 5
return await Future.value(() async {
KRLogUtil.kr_i('📦 开始备份 Windows DNS 设置...', tag: 'WindowsDNS');
// 1.
final interfaceName = await _kr_getPrimaryNetworkInterface();
if (interfaceName == null) {
KRLogUtil.kr_e('❌ 无法获取主网络接口', tag: 'WindowsDNS');
return false;
}
_primaryInterfaceName = interfaceName;
KRLogUtil.kr_i('🔍 主网络接口: $_primaryInterfaceName', tag: 'WindowsDNS');
// 1.
final interfaceName = await _kr_getPrimaryNetworkInterface();
if (interfaceName == null) {
KRLogUtil.kr_e('❌ 无法获取主网络接口', tag: 'WindowsDNS');
return false;
}
_primaryInterfaceName = interfaceName;
KRLogUtil.kr_i('🔍 主网络接口: $_primaryInterfaceName', tag: 'WindowsDNS');
// 2. DNS
final dnsServers = await _kr_getCurrentDnsServers(interfaceName);
if (dnsServers.isEmpty) {
KRLogUtil.kr_w('⚠️ 当前 DNS 为空,可能是自动获取', tag: 'WindowsDNS');
_originalDnsServers = []; // DHCP
} else {
_originalDnsServers = dnsServers;
KRLogUtil.kr_i('✅ 已备份 DNS: ${dnsServers.join(", ")}', tag: 'WindowsDNS');
}
// 2. DNS
final dnsServers = await _kr_getCurrentDnsServers(interfaceName);
return true;
// 🔧 P0修复1: 127.0.0.1 (sing-box DNS)
// 127.0.0.1 VPN 127.0.0.1 sing-box DNS
final validDnsServers = dnsServers.where((dns) => !dns.startsWith('127.')).toList();
if (validDnsServers.isEmpty) {
KRLogUtil.kr_w('⚠️ 当前 DNS 为空或全是本地地址,设为 DHCP 自动获取', tag: 'WindowsDNS');
if (dnsServers.isNotEmpty) {
KRLogUtil.kr_i(' (已过滤的本地DNS: ${dnsServers.join(", ")})', tag: 'WindowsDNS');
}
_originalDnsServers = []; // DHCP
} else {
_originalDnsServers = validDnsServers;
KRLogUtil.kr_i('✅ 已备份有效 DNS: ${validDnsServers.join(", ")}', tag: 'WindowsDNS');
if (dnsServers.length != validDnsServers.length) {
KRLogUtil.kr_i(' (已过滤掉 ${dnsServers.length - validDnsServers.length} 个本地地址)', tag: 'WindowsDNS');
}
}
return true;
}()).timeout(
const Duration(seconds: 5),
onTimeout: () {
KRLogUtil.kr_w('⏱️ DNS 备份操作超时5秒跳过备份', tag: 'WindowsDNS');
return false;
},
);
} catch (e) {
KRLogUtil.kr_e('❌ 备份 DNS 设置失败: $e', tag: 'WindowsDNS');
return false;
@ -80,22 +101,33 @@ class KRWindowsDnsUtil {
try {
KRLogUtil.kr_i('🔄 开始恢复 Windows DNS 设置...', tag: 'WindowsDNS');
// 1.
if (_primaryInterfaceName == null) {
KRLogUtil.kr_w('⚠️ 没有备份的网络接口,尝试自动检测', tag: 'WindowsDNS');
_primaryInterfaceName = await _kr_getPrimaryNetworkInterface();
if (_primaryInterfaceName == null) {
KRLogUtil.kr_e('❌ 无法检测网络接口,执行兜底恢复', tag: 'WindowsDNS');
return await _kr_fallbackRestoreDns();
}
// 🔧 P1修复:
final currentInterface = await _kr_getPrimaryNetworkInterface();
if (currentInterface == null) {
KRLogUtil.kr_e('❌ 无法检测当前网络接口,执行兜底恢复', tag: 'WindowsDNS');
return await _kr_fallbackRestoreDns();
}
// 2. DNS
//
if (_primaryInterfaceName != null && _primaryInterfaceName != currentInterface) {
KRLogUtil.kr_w('⚠️ 网络接口已变化: $_primaryInterfaceName$currentInterface', tag: 'WindowsDNS');
KRLogUtil.kr_w(' 执行兜底恢复以确保当前接口DNS正常', tag: 'WindowsDNS');
_primaryInterfaceName = currentInterface; //
return await _kr_fallbackRestoreDns();
}
// 使
_primaryInterfaceName = currentInterface;
KRLogUtil.kr_i('🔍 当前网络接口: $_primaryInterfaceName', tag: 'WindowsDNS');
// 1. DNS
if (_originalDnsServers == null) {
KRLogUtil.kr_w('⚠️ 没有备份的 DNS执行兜底恢复', tag: 'WindowsDNS');
return await _kr_fallbackRestoreDns();
}
// 2. DNS
if (_originalDnsServers!.isEmpty) {
// DHCP
KRLogUtil.kr_i('🔄 恢复为 DHCP 自动获取 DNS', tag: 'WindowsDNS');
@ -130,6 +162,15 @@ class KRWindowsDnsUtil {
return await _kr_fallbackRestoreDns();
}
// 🔧 P2优化: DNS
KRLogUtil.kr_i('🧪 测试 DNS 解析功能...', tag: 'WindowsDNS');
final canResolve = await _kr_testDnsResolution();
if (!canResolve) {
KRLogUtil.kr_w('⚠️ DNS 解析测试失败,执行兜底恢复', tag: 'WindowsDNS');
return await _kr_fallbackRestoreDns();
}
KRLogUtil.kr_i('✅ DNS 解析测试通过', tag: 'WindowsDNS');
return true;
} catch (e) {
KRLogUtil.kr_e('❌ 恢复 DNS 设置失败: $e', tag: 'WindowsDNS');
@ -187,7 +228,7 @@ class KRWindowsDnsUtil {
Future<String?> _kr_getPrimaryNetworkInterface() async {
try {
// 使 netsh
final result = await Process.run('netsh', ['interface', 'show', 'interface']);
final result = await KRWindowsProcessUtil.runHidden('netsh', ['interface', 'show', 'interface']);
if (result.exitCode != 0) {
KRLogUtil.kr_e('❌ 获取网络接口失败: ${result.stderr}', tag: 'WindowsDNS');
@ -271,7 +312,7 @@ class KRWindowsDnsUtil {
/// DNS
Future<List<String>> _kr_getCurrentDnsServers(String interfaceName) async {
try {
final result = await Process.run('netsh', [
final result = await KRWindowsProcessUtil.runHidden('netsh', [
'interface',
'ipv4',
'show',
@ -294,10 +335,9 @@ class KRWindowsDnsUtil {
final ipMatch = RegExp(r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b').firstMatch(line);
if (ipMatch != null) {
final ip = ipMatch.group(0)!;
//
if (!ip.startsWith('127.')) {
dnsServers.add(ip);
}
// 🔧 127.0.0.1便DNS是否还在使用sing-box的本地DNS
// DNS时126127.0.0.1
dnsServers.add(ip);
}
}
@ -325,7 +365,7 @@ class KRWindowsDnsUtil {
// 1. DNS
KRLogUtil.kr_i('🔧 设置主 DNS: ${dnsServers[0]}', tag: 'WindowsDNS');
var result = await Process.run('netsh', [
var result = await KRWindowsProcessUtil.runHidden('netsh', [
'interface',
'ipv4',
'set',
@ -345,7 +385,7 @@ class KRWindowsDnsUtil {
if (dnsServers.length > 1) {
for (int i = 1; i < dnsServers.length; i++) {
KRLogUtil.kr_i('🔧 设置备用 DNS ${i}: ${dnsServers[i]}', tag: 'WindowsDNS');
result = await Process.run('netsh', [
result = await KRWindowsProcessUtil.runHidden('netsh', [
'interface',
'ipv4',
'add',
@ -384,7 +424,7 @@ class KRWindowsDnsUtil {
try {
KRLogUtil.kr_i('🔧 设置 DNS 为自动获取 (DHCP)', tag: 'WindowsDNS');
final result = await Process.run('netsh', [
final result = await KRWindowsProcessUtil.runHidden('netsh', [
'interface',
'ipv4',
'set',
@ -416,7 +456,7 @@ class KRWindowsDnsUtil {
try {
KRLogUtil.kr_i('🔄 刷新 DNS 缓存...', tag: 'WindowsDNS');
final result = await Process.run('ipconfig', ['/flushdns']);
final result = await KRWindowsProcessUtil.runHidden('ipconfig', ['/flushdns']);
if (result.exitCode == 0) {
KRLogUtil.kr_i('✅ DNS 缓存已刷新', tag: 'WindowsDNS');
@ -428,6 +468,51 @@ class KRWindowsDnsUtil {
}
}
/// 🔧 P2优化: DNS
///
/// nslookup
/// true DNS false DNS
Future<bool> _kr_testDnsResolution() async {
try {
//
final testDomains = ['www.baidu.com', 'www.qq.com', 'dns.alidns.com'];
for (var domain in testDomains) {
try {
// 使 nslookup DNS 2
final result = await KRWindowsProcessUtil.runHidden(
'nslookup',
[domain],
).timeout(
const Duration(seconds: 2),
onTimeout: () {
return ProcessResult(0, 1, '', 'Timeout');
},
);
if (result.exitCode == 0) {
final output = result.stdout.toString();
// IP
if (output.contains('Address:') || output.contains('地址:')) {
KRLogUtil.kr_i('✅ DNS 解析测试通过: $domain', tag: 'WindowsDNS');
return true;
}
}
} catch (e) {
//
continue;
}
}
//
KRLogUtil.kr_w('⚠️ 所有测试域名解析均失败', tag: 'WindowsDNS');
return false;
} catch (e) {
KRLogUtil.kr_e('❌ DNS 解析测试异常: $e', tag: 'WindowsDNS');
return false;
}
}
///
///
/// 退

View File

@ -0,0 +1,602 @@
import 'dart:convert';
import 'dart:ffi';
import 'dart:io';
import 'dart:typed_data';
import 'package:ffi/ffi.dart';
import 'kr_file_logger.dart';
class KRWindowsProcessUtil {
/// 🔍 true
static const bool _debugCommandExecution = true; //
static Future<ProcessResult> runHidden(String executable, List<String> arguments) async {
final timestamp = DateTime.now().toString();
// 🔧 使 await
KRFileLogger.log('[黑屏调试] [KRWindowsProcessUtil] [$timestamp] 🔧 runHidden 调用: $executable ${arguments.join(" ")}');
if (!Platform.isWindows) {
KRFileLogger.log('[黑屏调试] [KRWindowsProcessUtil] [$timestamp] ⚠️ 非 Windows使用 Process.run可能黑窗');
return Process.run(executable, arguments);
}
KRFileLogger.log('[黑屏调试] [KRWindowsProcessUtil] [$timestamp] ✅ Windows使用 CreateProcessW无黑窗');
final result = await _runHiddenWindows(executable, arguments);
KRFileLogger.log('[黑屏调试] [KRWindowsProcessUtil] [$timestamp] 📤 执行完成: exitCode=${result.exitCode}');
return result;
}
static Future<int> startHidden(String executable, List<String> arguments) async {
if (!Platform.isWindows) {
final process = await Process.start(executable, arguments);
return process.pid;
}
return _startHiddenWindows(executable, arguments);
}
static String _buildCommandLine(String executable, List<String> arguments) {
final parts = <String>[_quoteArgument(executable)];
for (final arg in arguments) {
parts.add(_quoteArgument(arg));
}
return parts.join(' ');
}
static bool _shouldSearchPath(String executable) {
if (executable.isEmpty) {
return true;
}
return !(executable.contains('\\') || executable.contains('/') || executable.contains(':'));
}
static String _quoteArgument(String value) {
if (value.isEmpty) {
return '""';
}
final needsQuotes = value.contains(' ') || value.contains('\t') || value.contains('"');
if (!needsQuotes) {
return value;
}
final buffer = StringBuffer('"');
var backslashes = 0;
for (var i = 0; i < value.length; i++) {
final char = value[i];
if (char == '\\') {
backslashes++;
continue;
}
if (char == '"') {
buffer.write('\\' * (backslashes * 2 + 1));
buffer.write('"');
backslashes = 0;
continue;
}
if (backslashes > 0) {
buffer.write('\\' * backslashes);
backslashes = 0;
}
buffer.write(char);
}
if (backslashes > 0) {
buffer.write('\\' * (backslashes * 2));
}
buffer.write('"');
return buffer.toString();
}
static Future<ProcessResult> _runHiddenWindows(String executable, List<String> arguments) async {
final stdoutPipe = _createPipe();
final stderrPipe = _createPipe();
final startupInfo = calloc<STARTUPINFO>();
final processInfo = calloc<PROCESS_INFORMATION>();
final commandLine = _buildCommandLine(executable, arguments).toNativeUtf16();
final applicationName = _shouldSearchPath(executable) ? nullptr : executable.toNativeUtf16();
final stdInput = _getStdInputHandle();
startupInfo.ref
..cb = sizeOf<STARTUPINFO>()
..dwFlags = STARTF_USESTDHANDLES
..hStdInput = stdInput
..hStdOutput = stdoutPipe.write
..hStdError = stderrPipe.write;
final created = _CreateProcessW(
applicationName,
commandLine,
nullptr,
nullptr,
TRUE,
CREATE_NO_WINDOW,
nullptr,
nullptr,
startupInfo,
processInfo,
);
calloc.free(commandLine);
if (applicationName != nullptr) {
calloc.free(applicationName);
}
if (created == 0) {
_closeHandle(stdoutPipe.read);
_closeHandle(stdoutPipe.write);
_closeHandle(stderrPipe.read);
_closeHandle(stderrPipe.write);
calloc.free(startupInfo);
calloc.free(processInfo);
throw Exception('CreateProcessW failed: ${_GetLastError()}');
}
_closeHandle(stdoutPipe.write);
_closeHandle(stderrPipe.write);
final output = await _collectOutput(processInfo.ref.hProcess, stdoutPipe.read, stderrPipe.read);
final exitCode = _getExitCode(processInfo.ref.hProcess);
_closeHandle(stdoutPipe.read);
_closeHandle(stderrPipe.read);
_closeHandle(processInfo.ref.hThread);
_closeHandle(processInfo.ref.hProcess);
final pid = processInfo.ref.dwProcessId;
calloc.free(startupInfo);
calloc.free(processInfo);
return ProcessResult(pid, exitCode, output.stdout, output.stderr);
}
static Future<int> _startHiddenWindows(String executable, List<String> arguments) async {
final startupInfo = calloc<STARTUPINFO>();
final processInfo = calloc<PROCESS_INFORMATION>();
final commandLine = _buildCommandLine(executable, arguments).toNativeUtf16();
final applicationName = _shouldSearchPath(executable) ? nullptr : executable.toNativeUtf16();
startupInfo.ref.cb = sizeOf<STARTUPINFO>();
final created = _CreateProcessW(
applicationName,
commandLine,
nullptr,
nullptr,
FALSE,
CREATE_NO_WINDOW,
nullptr,
nullptr,
startupInfo,
processInfo,
);
calloc.free(commandLine);
if (applicationName != nullptr) {
calloc.free(applicationName);
}
if (created == 0) {
calloc.free(startupInfo);
calloc.free(processInfo);
throw Exception('CreateProcessW failed: ${_GetLastError()}');
}
final pid = processInfo.ref.dwProcessId;
_closeHandle(processInfo.ref.hThread);
_closeHandle(processInfo.ref.hProcess);
calloc.free(startupInfo);
calloc.free(processInfo);
return pid;
}
static _Pipe _createPipe() {
final readHandle = calloc<Pointer<Void>>();
final writeHandle = calloc<Pointer<Void>>();
final securityAttributes = calloc<SECURITY_ATTRIBUTES>();
securityAttributes.ref
..nLength = sizeOf<SECURITY_ATTRIBUTES>()
..bInheritHandle = TRUE
..lpSecurityDescriptor = nullptr;
final created = _CreatePipe(readHandle, writeHandle, securityAttributes, 0);
calloc.free(securityAttributes);
if (created == 0) {
calloc.free(readHandle);
calloc.free(writeHandle);
throw Exception('CreatePipe failed: ${_GetLastError()}');
}
final readValue = readHandle.value;
final writeValue = writeHandle.value;
calloc.free(readHandle);
calloc.free(writeHandle);
final infoResult = _SetHandleInformation(readValue, HANDLE_FLAG_INHERIT, 0);
if (infoResult == 0) {
_closeHandle(readValue);
_closeHandle(writeValue);
throw Exception('SetHandleInformation failed: ${_GetLastError()}');
}
return _Pipe(readValue, writeValue);
}
static Pointer<Void> _getStdInputHandle() {
final handle = _GetStdHandle(STD_INPUT_HANDLE);
if (handle == INVALID_HANDLE_VALUE || handle == 0) {
return nullptr;
}
return Pointer<Void>.fromAddress(handle);
}
static Future<_ProcessOutput> _collectOutput(
Pointer<Void> process,
Pointer<Void> stdoutHandle,
Pointer<Void> stderrHandle,
) async {
final stdoutBuilder = BytesBuilder();
final stderrBuilder = BytesBuilder();
while (true) {
final stdoutRead = _drainPipe(stdoutHandle, stdoutBuilder);
final stderrRead = _drainPipe(stderrHandle, stderrBuilder);
final waitResult = _WaitForSingleObject(process, 0);
if (waitResult == WAIT_OBJECT_0) {
break;
}
if (waitResult == WAIT_FAILED) {
throw Exception('WaitForSingleObject failed: ${_GetLastError()}');
}
if (!stdoutRead && !stderrRead) {
await Future.delayed(const Duration(milliseconds: 10));
} else {
await Future<void>.delayed(Duration.zero);
}
}
while (_drainPipe(stdoutHandle, stdoutBuilder) || _drainPipe(stderrHandle, stderrBuilder)) {
await Future<void>.delayed(Duration.zero);
}
return _ProcessOutput(
_decodeOutput(stdoutBuilder),
_decodeOutput(stderrBuilder),
);
}
static bool _drainPipe(Pointer<Void> handle, BytesBuilder builder) {
final buffer = calloc<Uint8>(4096);
final bytesRead = calloc<Uint32>();
final available = calloc<Uint32>();
var didRead = false;
while (true) {
final peekOk = _PeekNamedPipe(handle, nullptr, 0, nullptr, available, nullptr);
if (peekOk == 0 || available.value == 0) {
break;
}
final toRead = available.value < 4096 ? available.value : 4096;
final ok = _ReadFile(handle, buffer.cast<Void>(), toRead, bytesRead, nullptr);
final read = ok == 0 ? 0 : bytesRead.value;
if (read == 0) {
break;
}
builder.add(buffer.asTypedList(read));
didRead = true;
}
calloc.free(buffer);
calloc.free(bytesRead);
calloc.free(available);
return didRead;
}
static String _decodeOutput(BytesBuilder builder) {
if (builder.length == 0) {
return '';
}
final bytes = builder.toBytes();
try {
return systemEncoding.decode(bytes);
} catch (_) {
return utf8.decode(bytes, allowMalformed: true);
}
}
static int _getExitCode(Pointer<Void> process) {
final exitCode = calloc<Uint32>();
final ok = _GetExitCodeProcess(process, exitCode);
final code = ok == 0 ? -1 : exitCode.value;
calloc.free(exitCode);
return code;
}
static void _closeHandle(Pointer<Void> handle) {
if (handle == nullptr) {
return;
}
_CloseHandle(handle);
}
// 🔧 WinINet API helpers for proxy settings
///
static String? queryWindowsProxyServer() {
if (!Platform.isWindows) return null;
try {
final bufferSize = calloc<Uint32>();
bufferSize.value = sizeOf<INTERNET_PROXY_INFO>();
final proxyInfo = calloc<INTERNET_PROXY_INFO>();
final result = _InternetQueryOptionW(nullptr, INTERNET_OPTION_PROXY, proxyInfo.cast<Void>(), bufferSize);
if (result == 0) {
calloc.free(bufferSize);
calloc.free(proxyInfo);
return null;
}
final proxyServer = proxyInfo.ref.lpszProxy.toDartString();
calloc.free(bufferSize);
calloc.free(proxyInfo);
return proxyServer.isEmpty ? null : proxyServer;
} catch (e) {
return null;
}
}
///
static bool setWindowsProxyServer(String? server) {
if (!Platform.isWindows) return false;
try {
final proxyInfo = calloc<INTERNET_PROXY_INFO>();
if (server != null && server.isNotEmpty) {
//
proxyInfo.ref.dwAccessType = INTERNET_OPEN_TYPE_PROXY;
proxyInfo.ref.lpszProxy = server.toNativeUtf16();
proxyInfo.ref.lpszProxyBypass = ''.toNativeUtf16();
} else {
//
proxyInfo.ref.dwAccessType = INTERNET_OPEN_TYPE_DIRECT;
proxyInfo.ref.lpszProxy = nullptr;
proxyInfo.ref.lpszProxyBypass = nullptr;
}
final result = _InternetSetOptionW(
nullptr,
INTERNET_OPTION_PROXY,
proxyInfo.cast<Void>(),
sizeOf<INTERNET_PROXY_INFO>(),
);
if (server != null && server.isNotEmpty) {
calloc.free(proxyInfo.ref.lpszProxy);
calloc.free(proxyInfo.ref.lpszProxyBypass);
}
calloc.free(proxyInfo);
return result != 0;
} catch (e) {
return false;
}
}
///
static bool disableWindowsProxy() {
return setWindowsProxyServer(null);
}
}
class _Pipe {
final Pointer<Void> read;
final Pointer<Void> write;
_Pipe(this.read, this.write);
}
class _ProcessOutput {
final String stdout;
final String stderr;
_ProcessOutput(this.stdout, this.stderr);
}
const int TRUE = 1;
const int FALSE = 0;
const int STARTF_USESTDHANDLES = 0x00000100;
const int CREATE_NO_WINDOW = 0x08000000;
const int HANDLE_FLAG_INHERIT = 0x00000001;
const int WAIT_OBJECT_0 = 0x00000000;
const int WAIT_FAILED = 0xFFFFFFFF;
const int STD_INPUT_HANDLE = -10;
const int INVALID_HANDLE_VALUE = -1;
final class SECURITY_ATTRIBUTES extends Struct {
@Uint32()
external int nLength;
external Pointer<Void> lpSecurityDescriptor;
@Int32()
external int bInheritHandle;
}
final class STARTUPINFO extends Struct {
@Uint32()
external int cb;
external Pointer<Utf16> lpReserved;
external Pointer<Utf16> lpDesktop;
external Pointer<Utf16> lpTitle;
@Uint32()
external int dwX;
@Uint32()
external int dwY;
@Uint32()
external int dwXSize;
@Uint32()
external int dwYSize;
@Uint32()
external int dwXCountChars;
@Uint32()
external int dwYCountChars;
@Uint32()
external int dwFillAttribute;
@Uint32()
external int dwFlags;
@Uint16()
external int wShowWindow;
@Uint16()
external int cbReserved2;
external Pointer<Uint8> lpReserved2;
external Pointer<Void> hStdInput;
external Pointer<Void> hStdOutput;
external Pointer<Void> hStdError;
}
final class PROCESS_INFORMATION extends Struct {
external Pointer<Void> hProcess;
external Pointer<Void> hThread;
@Uint32()
external int dwProcessId;
@Uint32()
external int dwThreadId;
}
final DynamicLibrary _kernel32 = DynamicLibrary.open('kernel32.dll');
final _CreatePipe = _kernel32.lookupFunction<
Int32 Function(Pointer<Pointer<Void>>, Pointer<Pointer<Void>>, Pointer<SECURITY_ATTRIBUTES>, Uint32),
int Function(Pointer<Pointer<Void>>, Pointer<Pointer<Void>>, Pointer<SECURITY_ATTRIBUTES>, int)>(
'CreatePipe',
);
final _SetHandleInformation = _kernel32.lookupFunction<
Int32 Function(Pointer<Void>, Uint32, Uint32),
int Function(Pointer<Void>, int, int)>(
'SetHandleInformation',
);
final _CreateProcessW = _kernel32.lookupFunction<
Int32 Function(
Pointer<Utf16>,
Pointer<Utf16>,
Pointer<SECURITY_ATTRIBUTES>,
Pointer<SECURITY_ATTRIBUTES>,
Int32,
Uint32,
Pointer<Void>,
Pointer<Utf16>,
Pointer<STARTUPINFO>,
Pointer<PROCESS_INFORMATION>,
),
int Function(
Pointer<Utf16>,
Pointer<Utf16>,
Pointer<SECURITY_ATTRIBUTES>,
Pointer<SECURITY_ATTRIBUTES>,
int,
int,
Pointer<Void>,
Pointer<Utf16>,
Pointer<STARTUPINFO>,
Pointer<PROCESS_INFORMATION>,
)>(
'CreateProcessW',
);
final _PeekNamedPipe = _kernel32.lookupFunction<
Int32 Function(Pointer<Void>, Pointer<Void>, Uint32, Pointer<Uint32>, Pointer<Uint32>, Pointer<Uint32>),
int Function(Pointer<Void>, Pointer<Void>, int, Pointer<Uint32>, Pointer<Uint32>, Pointer<Uint32>)>(
'PeekNamedPipe',
);
final _ReadFile = _kernel32.lookupFunction<
Int32 Function(Pointer<Void>, Pointer<Void>, Uint32, Pointer<Uint32>, Pointer<Void>),
int Function(Pointer<Void>, Pointer<Void>, int, Pointer<Uint32>, Pointer<Void>)>(
'ReadFile',
);
final _CloseHandle = _kernel32.lookupFunction<
Int32 Function(Pointer<Void>),
int Function(Pointer<Void>)>(
'CloseHandle',
);
final _WaitForSingleObject = _kernel32.lookupFunction<
Uint32 Function(Pointer<Void>, Uint32),
int Function(Pointer<Void>, int)>(
'WaitForSingleObject',
);
final _GetStdHandle = _kernel32.lookupFunction<
IntPtr Function(Int32),
int Function(int)>(
'GetStdHandle',
);
final _GetExitCodeProcess = _kernel32.lookupFunction<
Int32 Function(Pointer<Void>, Pointer<Uint32>),
int Function(Pointer<Void>, Pointer<Uint32>)>(
'GetExitCodeProcess',
);
final _GetLastError = _kernel32.lookupFunction<
Uint32 Function(),
int Function()>(
'GetLastError',
);
// 🔧 WinINet API for proxy settings - reg
final DynamicLibrary _wininet = DynamicLibrary.open('wininet.dll');
const int INTERNET_OPTION_PROXY = 38;
const int INTERNET_OPEN_TYPE_PROXY = 3;
const int INTERNET_OPEN_TYPE_DIRECT = 1;
final class INTERNET_PROXY_INFO extends Struct {
@Int32()
external int dwAccessType;
external Pointer<Utf16> lpszProxy;
external Pointer<Utf16> lpszProxyBypass;
}
/// WinINet InternetSetOption API -
final _InternetSetOptionW = _wininet.lookupFunction<
Int32 Function(Pointer<Void>, Uint32, Pointer<Void>, Uint32),
int Function(Pointer<Void>, int, Pointer<Void>, int)>(
'InternetSetOptionW',
);
/// WinINet InternetQueryOption API -
final _InternetQueryOptionW = _wininet.lookupFunction<
Int32 Function(Pointer<Void>, Uint32, Pointer<Void>, Pointer<Uint32>),
int Function(Pointer<Void>, int, Pointer<Void>, Pointer<Uint32>)>(
'InternetQueryOptionW',
);

View File

@ -1,6 +1,8 @@
import 'dart:io';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
// import 'package:flutter_easyloading/flutter_easyloading.dart'; //
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter/services.dart';
@ -17,12 +19,16 @@ import 'package:kaer_with_panels/app/routes/app_pages.dart';
import 'package:kaer_with_panels/app/widgets/kr_local_image.dart';
import 'package:kaer_with_panels/app/utils/kr_window_manager.dart';
import 'app/services/singbox_imp/kr_sing_box_imp.dart';
import 'app/common/app_run_data.dart';
import 'app/utils/kr_secure_storage.dart';
import 'app/common/app_config.dart';
import 'app/services/kr_site_config_service.dart';
import 'app/services/global_overlay_service.dart';
import 'app/utils/kr_log_util.dart';
import 'app/utils/kr_secure_storage.dart';
import 'app/utils/kr_init_log_collector.dart';
import 'app/utils/kr_file_logger.dart';
import 'package:kaer_with_panels/app/routes/transitions/slide_transparent_transition.dart';
import 'package:kaer_with_panels/app/routes/transitions/transition_config.dart';
@ -31,173 +37,357 @@ import 'package:kaer_with_panels/app/routes/transitions/transition_config.dart';
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 🔧
runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized();
//
WidgetsBinding.instance.deferFirstFrame();
// 🔧
final logCollector = KRInitLogCollector();
// 🔧 P6修复: await完成初始化,
await logCollector.initialize();
logCollector.log('🚀 应用启动', tag: 'MAIN');
// Flutter
await Future.delayed(const Duration(milliseconds: 100));
// 🔧 :
logCollector.log('平台: ${Platform.operatingSystem}', tag: 'DEVICE');
logCollector.log('系统版本: ${Platform.operatingSystemVersion}', tag: 'DEVICE');
logCollector.log('Dart版本: ${Platform.version}', tag: 'DEVICE');
if (Platform.isAndroid || Platform.isIOS) {
await SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown],
// 🔧 Flutter框架异常捕获
FlutterError.onError = (FlutterErrorDetails details) {
logCollector.logError(
'Flutter框架异常: ${details.exception}',
tag: 'FLUTTER_ERROR',
error: details.exception,
stackTrace: details.stack,
);
FlutterError.presentError(details);
};
// 🔧 PlatformDispatcher
PlatformDispatcher.instance.onError = (error, stack) {
logCollector.logError(
'平台异步异常: $error',
tag: 'PLATFORM_ERROR',
error: error,
stackTrace: stack,
);
return true;
};
await _initializeApp(logCollector);
}, (error, stack) {
// 🔧 Zone捕获的未处理异常
final logCollector = KRInitLogCollector();
logCollector.logError(
'Zone未处理异常: $error',
tag: 'ZONE_ERROR',
error: error,
stackTrace: stack,
);
});
}
Future<void> _initializeApp(KRInitLogCollector logCollector) async {
try {
logCollector.logPhaseStart('初始化核心服务');
// 🔧 UI
logCollector.log('初始化文件日志系统', tag: 'FILE_LOGGER');
await KRFileLogger.initialize();
await KRFileLogger.log('📝 文件日志系统已启动');
logCollector.logSuccess('文件日志系统初始化完成', tag: 'FILE_LOGGER');
// HttpClient
logCollector.log('配置 HTTP 代理策略', tag: 'HTTP');
HttpOverrides.global = KRProxyHttpOverrides();
// Hive
logCollector.log('初始化 Hive 数据库', tag: 'HIVE');
await KRSecureStorage().kr_initHive();
logCollector.logSuccess('Hive 初始化完成', tag: 'HIVE');
//
logCollector.log('初始化主题服务', tag: 'THEME');
await KRThemeService().init();
logCollector.logSuccess('主题初始化完成', tag: 'THEME');
//
logCollector.log('加载多语言翻译', tag: 'I18N');
final translations = GetxTranslations();
await translations.loadAllTranslations();
logCollector.logSuccess('翻译加载完成', tag: 'I18N');
//
final initialLocale = await KRLanguageUtils.getLastSavedLocale();
logCollector.log('使用语言: ${initialLocale.toString()}', tag: 'I18N');
logCollector.logPhaseEnd('初始化核心服务', success: true);
//
logCollector.log('启动域名预检测', tag: 'DOMAIN');
KRDomain.kr_preCheckDomains();
logCollector.log('✅ 核心服务初始化完成,启动应用', tag: 'MAIN');
// 🔧 P9修复: finalize,
// finalize会在应用关闭或_AppLifecycleWrapper的dispose时调用
// Overlay
Get.put(GlobalOverlayService());
// 🔧 runApp() Flutter
// runApp WidgetsBinding.addPostFrameCallback
// Flutter UI
runApp(_myApp(translations, initialLocale));
// 🔧 Flutter
if (Platform.isMacOS || Platform.isWindows) {
// 使 addPostFrameCallback
WidgetsBinding.instance.addPostFrameCallback((_) async {
logCollector.logPhaseStart('初始化窗口管理器');
await KRWindowManager().kr_initWindowManager();
logCollector.logPhaseEnd('初始化窗口管理器', success: true);
});
}
} catch (error, stackTrace) {
logCollector.logError(
'应用初始化失败',
tag: 'INIT_FATAL',
error: error,
stackTrace: stackTrace,
);
await logCollector.finalize();
rethrow;
}
//
KRLogUtil.kr_init();
// Stripe.publishableKey = 'pk_live_51SbuYxAawOMH8rEEkz6f4mnxAUjGC72eQ6qdm5tT6whC4hULkxxdbiPsB4gSCIMnNIGCsIgeASTXBakUcbOuUwQO00jSWjuufx';
// Stripe.merchantIdentifier = 'merchant.com.taw.hifastvpn';
// if (Platform.isIOS) {
// await Stripe.instance.applySettings();
// }
// Hive
await KRSecureStorage().kr_initHive();
//
await KRThemeService().init();
//
final translations = GetxTranslations();
await translations.loadAllTranslations();
//
final initialLocale = await KRLanguageUtils.getLastSavedLocale();
//
if (Platform.isMacOS || Platform.isWindows) {
await KRWindowManager().kr_initWindowManager();
}
//
//
KRDomain.kr_preCheckDomains();
// FMTC
// try {
// if (Platform.isMacOS) {
// final baseDir = await getApplicationSupportDirectory();
// await FMTCObjectBoxBackend().initialise(rootDirectory: baseDir.path);
// } else {
// await FMTCObjectBoxBackend().initialise();
// }
// //
// await FMTCStore(KRFMTC.kr_storeName).manage.create();
// //
// await KRFMTC.kr_initMapCache();
// } catch (error, stackTrace) {
// }
// Overlay
Get.put(GlobalOverlayService());
runApp(_myApp(translations, initialLocale));
WidgetsBinding.instance.allowFirstFrame();
}
Widget _myApp(GetxTranslations translations, Locale initialLocale) {
return GetMaterialApp(
navigatorKey: navigatorKey, // 使
title: "Hi快VPN",
initialRoute: Routes.KR_SPLASH,
getPages: AppPages.routes,
builder: (context, child) {
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
///
ScreenUtil.init(context,
designSize: const Size(868, 668), minTextAdapt: true);
} else {
///
ScreenUtil.init(context,
designSize: const Size(375, 667), minTextAdapt: true);
}
// child = FlutterEasyLoading(child: child); //
//
Widget wrappedChild = Listener(
onPointerDown: (_) async {
//
// try {
// final store = FMTCStore(KRFMTC.kr_storeName);
// if (!await store.manage.ready) {
// await store.manage.create();
// await KRFMTC.kr_initMapCache();
// }
// } catch (e) {
// print('地图缓存初始化失败: $e');
// }
},
child: child,
);
// Mac
if (Platform.isMacOS) {
wrappedChild = MediaQuery(
data: MediaQuery.of(context).copyWith(
padding: MediaQuery.of(context).padding.copyWith(
top: 10.w, // Mac
),
),
child: wrappedChild,
return _AppLifecycleWrapper(
child: GetMaterialApp(
navigatorKey: navigatorKey, // 使
title: "Hi快VPN",
initialRoute: Routes.KR_SPLASH,
getPages: AppPages.routes,
builder: (context, child) {
// 🔧 P2修复: ScreenUtil初始化移到StatefulWidget中,
return _ScreenUtilInitializer(
child: child ?? Container(),
);
}
// 使 Stack
return Stack(
fit: StackFit.expand,
children: [
// 使
const KrLocalImage(
imageName: 'global-bg',
imageType: ImageType.jpg,
fit: BoxFit.cover, //
),
//
wrappedChild,
Overlay(
initialEntries: [
OverlayEntry(builder: (_) => const SizedBox.shrink()), // Overlay
],
),
],
);
},
theme: KRThemeService().kr_lightTheme(),
darkTheme: KRThemeService().kr_darkTheme(),
themeMode: KRThemeService().kr_Theme,
translations: translations,
locale: initialLocale,
fallbackLocale: const Locale('zh', 'CN'),
debugShowCheckedModeBanner: false,
transitionDuration: TransitionConfig.defaultDuration, //
customTransition: TransitionConfig.createDefaultTransition(),
routingCallback: (routing) {
if (routing == null) return;
if(Routes.KR_PURCHASE_MEMBERSHIP.contains(routing.current)) return;
//
const showButtonRoutes = [
Routes.MR_LOGIN,
Routes.HI_MENU,
Routes.KR_HOME,
Routes.HI_USER_INFO,
Routes.KR_ORDER_STATUS,
];
print('routing.current${routing.current}');
GlobalOverlayService.instance.updateSubscriptionButtonColor(null);
if (showButtonRoutes.contains(routing.current)) {
GlobalOverlayService.instance.safeShowSubscriptionButton();
} else {
GlobalOverlayService.instance.hideSubscriptionButton();
}
},
},
theme: KRThemeService().kr_lightTheme(),
darkTheme: KRThemeService().kr_darkTheme(),
themeMode: KRThemeService().kr_Theme,
translations: translations,
locale: initialLocale,
fallbackLocale: const Locale('zh', 'CN'),
debugShowCheckedModeBanner: false,
transitionDuration: TransitionConfig.defaultDuration, //
customTransition: TransitionConfig.createDefaultTransition(),
routingCallback: (routing) {
if (routing == null) return;
if(Routes.KR_PURCHASE_MEMBERSHIP.contains(routing.current)) return;
//
const showButtonRoutes = [
Routes.MR_LOGIN,
Routes.HI_MENU,
Routes.KR_HOME,
Routes.HI_USER_INFO,
Routes.KR_ORDER_STATUS,
];
print('routing.current${routing.current}');
GlobalOverlayService.instance.updateSubscriptionButtonColor(null);
if (showButtonRoutes.contains(routing.current)) {
GlobalOverlayService.instance.safeShowSubscriptionButton();
} else {
GlobalOverlayService.instance.hideSubscriptionButton();
}
},
)
);
}
/// 🔧 P2修复: ScreenUtil初始化包装器,
/// 🔧 P0修复: 使Widget重建导致的状态丢失
class _ScreenUtilInitializer extends StatefulWidget {
final Widget child;
const _ScreenUtilInitializer({required this.child});
@override
State<_ScreenUtilInitializer> createState() => _ScreenUtilInitializerState();
}
class _ScreenUtilInitializerState extends State<_ScreenUtilInitializer> {
// 🔧 P0修复: 使使Widget重建也不会重复初始化
static bool _isGlobalInitialized = false;
// 🔧 P0修复:
static Size? _lastScreenSize;
@override
Widget build(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
final currentSize = mediaQuery.size;
// 🔧 P0修复: :
// 1.
// 2.
final needsInit = !_isGlobalInitialized ||
(_lastScreenSize != null &&
((_lastScreenSize!.width - currentSize.width).abs() > 50 ||
(_lastScreenSize!.height - currentSize.height).abs() > 50));
if (needsInit) {
// 🔧 P9修复: (,)
if (kDebugMode || AppConfig.enableInitLogCollection) {
// ,UI线程空闲时执行
WidgetsBinding.instance.addPostFrameCallback((_) {
final logCollector = KRInitLogCollector();
logCollector.log('━━━ 屏幕信息诊断 ━━━', tag: 'SCREEN');
logCollector.log('屏幕尺寸: ${mediaQuery.size.width} x ${mediaQuery.size.height}', tag: 'SCREEN');
logCollector.log('设备像素比: ${mediaQuery.devicePixelRatio}', tag: 'SCREEN');
logCollector.log('文本缩放: ${mediaQuery.textScaler.scale(1.0)}', tag: 'SCREEN');
logCollector.log('安全区域: top=${mediaQuery.padding.top}, bottom=${mediaQuery.padding.bottom}', tag: 'SCREEN');
});
}
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
// 🔧 800x668UI元素正确缩放
ScreenUtil.init(context,
designSize: const Size(800, 668), minTextAdapt: true);
if (kDebugMode || AppConfig.enableInitLogCollection) {
KRInitLogCollector().log('ScreenUtil初始化: 桌面模式 800x668', tag: 'SCREEN');
}
} else {
ScreenUtil.init(context,
designSize: const Size(375, 667), minTextAdapt: true);
if (kDebugMode || AppConfig.enableInitLogCollection) {
KRInitLogCollector().log('ScreenUtil初始化: 移动模式 375x667', tag: 'SCREEN');
}
}
_isGlobalInitialized = true;
_lastScreenSize = currentSize;
}
// Mac
Widget wrappedChild = widget.child;
if (Platform.isMacOS) {
wrappedChild = MediaQuery(
data: MediaQuery.of(context).copyWith(
padding: MediaQuery.of(context).padding.copyWith(
top: 10.w, // Mac
),
),
child: wrappedChild,
);
}
return Stack(
fit: StackFit.expand,
children: [
// 使
const KrLocalImage(
imageName: 'global-bg',
imageType: ImageType.jpg,
fit: BoxFit.cover, //
),
//
wrappedChild,
Overlay(
initialEntries: [
OverlayEntry(builder: (_) => const SizedBox.shrink()), // Overlay
],
),
],
);
}
}
/// 🔧 P3修复:
class _AppLifecycleWrapper extends StatefulWidget {
final Widget child;
const _AppLifecycleWrapper({required this.child});
@override
State<_AppLifecycleWrapper> createState() => _AppLifecycleWrapperState();
}
class _AppLifecycleWrapperState extends State<_AppLifecycleWrapper>
with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
// 🔧 P9修复: finalize日志文件
KRInitLogCollector().finalize();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (kDebugMode) {
print('🔄 应用生命周期变化: $state');
}
// 🔧 P3修复: ,
if (state == AppLifecycleState.resumed) {
if (kDebugMode) {
print('♻️ 应用恢复,开始重置关键服务...');
}
_resetCriticalServices();
}
}
/// 🔧 P3修复: ,
Future<void> _resetCriticalServices() async {
try {
// 🔧 P1修复:
await KRThemeService().reset();
if (kDebugMode) {
print('✅ 主题服务已重置');
}
// 🔧 P1修复:
await KRAppRunData.getInstance().kr_resetRuntimeState();
if (kDebugMode) {
print('✅ 应用运行时数据已重置');
}
// 🔧 P1修复:
KRDomain.kr_resetDomainState();
if (kDebugMode) {
print('✅ 域名状态已重置');
}
// UI
if (mounted) {
setState(() {});
}
} catch (e) {
if (kDebugMode) {
print('⚠️ 服务重置失败: $e');
}
}
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}
/// HttpOverrides dart:io sing-box
class KRProxyHttpOverrides extends HttpOverrides {
@override
HttpClient createHttpClient(SecurityContext? context) {
final client = super.createHttpClient(context);
client.findProxy = (uri) => KRSingBoxImp.instance.kr_buildProxyRule();
return client;
}
}

View File

@ -3,7 +3,7 @@ import 'dart:convert';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'package:combine/combine.dart';
import 'package:kaer_with_panels/utils/isolate_worker.dart';
import 'package:ffi/ffi.dart';
import 'package:fpdart/fpdart.dart';
import 'package:kaer_with_panels/core/model/directories.dart';
@ -50,6 +50,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
@override
Future<void> init() async {
loggy.debug("initializing");
_box.setupOnce(NativeApi.initializeApiDLData);
_statusReceiver = ReceivePort('service status receiver');
final source = _statusReceiver.asBroadcastStream().map((event) => jsonDecode(event as String)).map(SingboxStatus.fromEvent);
_status = ValueConnectableStream.seeded(
@ -60,162 +61,197 @@ class FFISingboxService with InfraLogger implements SingboxService {
@override
TaskEither<String, Unit> setup(
Directories directories,
bool debug,
) {
Directories directories,
bool debug,
) {
final port = _statusReceiver.sendPort.nativePort;
return TaskEither(
() => CombineWorker().execute(
() {
_box.setupOnce(NativeApi.initializeApiDLData);
final err = _box
.setup(
directories.baseDir.path.toNativeUtf8().cast(),
directories.workingDir.path.toNativeUtf8().cast(),
directories.tempDir.path.toNativeUtf8().cast(),
port,
debug ? 1 : 0,
)
.cast<Utf8>()
.toDartString();
if (err.isNotEmpty) {
return left(err);
}
return right(unit);
},
),
);
final baseDir = directories.baseDir.path;
final workingDir = directories.workingDir.path;
final tempDir = directories.tempDir.path;
final debugFlag = debug ? 1 : 0;
return TaskEither(() async {
try {
final startTime = DateTime.now();
_logger.debug('[黑屏调试] setup() 开始调用 libcore.dll - $startTime');
final err = await IsolateWorker().execute(
() => _ffiSetup(baseDir, workingDir, tempDir, port, debugFlag),
allowSyncFallback: false,
);
final endTime = DateTime.now();
final durationMs = endTime.difference(startTime).inMilliseconds;
_logger.debug('[黑屏调试] setup() 完成(耗时: ${durationMs}ms');
if (err != null && err.isNotEmpty) {
_logger.error('[黑屏调试] setup() 错误: $err');
return left(err);
}
return right(unit);
} catch (e) {
_logger.error('[黑屏调试] setup() 异常: $e');
return left(e.toString());
}
});
}
@override
TaskEither<String, Unit> validateConfigByPath(
String path,
String tempPath,
bool debug,
) {
return TaskEither(
() => CombineWorker().execute(
() {
final err = _box
.parse(
path.toNativeUtf8().cast(),
tempPath.toNativeUtf8().cast(),
debug ? 1 : 0,
)
.cast<Utf8>()
.toDartString();
if (err.isNotEmpty) {
return left(err);
}
return right(unit);
},
),
);
String path,
String tempPath,
bool debug,
) {
final debugFlag = debug ? 1 : 0;
return TaskEither(() async {
try {
final err = await IsolateWorker().execute(
() => _ffiValidateConfig(path, tempPath, debugFlag),
allowSyncFallback: false,
);
if (err != null && err.isNotEmpty) {
return left(err);
}
return right(unit);
} catch (e) {
return left(e.toString());
}
});
}
@override
TaskEither<String, Unit> changeOptions(SingboxConfigOption options) {
return TaskEither(
() => CombineWorker().execute(
() {
final json = jsonEncode(options.toJson());
final err = _box.changeHiddifyOptions(json.toNativeUtf8().cast()).cast<Utf8>().toDartString();
if (err.isNotEmpty) {
return left(err);
}
return right(unit);
},
),
);
final json = jsonEncode(options.toJson());
return TaskEither(() async {
try {
final startTime = DateTime.now();
_logger.debug('[黑屏调试] changeOptions 开始调用 libcore.dll - $startTime');
final err = await IsolateWorker().execute(
() => _ffiChangeOptions(json),
allowSyncFallback: false,
);
final endTime = DateTime.now();
final durationMs = endTime.difference(startTime).inMilliseconds;
_logger.debug('[黑屏调试] changeOptions 完成(耗时: ${durationMs}ms');
if (err != null && err.isNotEmpty) {
_logger.error('[黑屏调试] changeOptions 错误: $err');
return left(err);
}
return right(unit);
} catch (e) {
_logger.error('[黑屏调试] changeOptions 异常: $e');
return left(e.toString());
}
});
}
@override
TaskEither<String, String> generateFullConfigByPath(
String path,
) {
return TaskEither(
() => CombineWorker().execute(
() {
final response = _box
.generateConfig(
path.toNativeUtf8().cast(),
)
.cast<Utf8>()
.toDartString();
if (response.startsWith("error")) {
return left(response.replaceFirst("error", ""));
}
return right(response);
},
),
);
String path,
) {
return TaskEither(() async {
try {
final result = await IsolateWorker().execute(
() => _ffiGenerateFullConfig(path),
allowSyncFallback: false,
);
final ok = result.isNotEmpty && result[0] == true;
final payload = result.length > 1 ? result[1] as String : '';
if (!ok) {
return left(payload);
}
return right(payload);
} catch (e) {
return left(e.toString());
}
});
}
@override
TaskEither<String, Unit> start(
String configPath,
String name,
bool disableMemoryLimit,
) {
String configPath,
String name,
bool disableMemoryLimit,
) {
loggy.debug("starting, memory limit: [${!disableMemoryLimit}]");
return TaskEither(
() => CombineWorker().execute(
() {
final err = _box
.start(
configPath.toNativeUtf8().cast(),
disableMemoryLimit ? 1 : 0,
)
.cast<Utf8>()
.toDartString();
if (err.isNotEmpty) {
return left(err);
}
return right(unit);
},
),
);
return TaskEither(() async {
try {
final startTime = DateTime.now();
_logger.debug('[黑屏调试] start() 开始调用 libcore.dll - $startTime');
final err = await IsolateWorker().execute(
() => _ffiStart(configPath, disableMemoryLimit),
allowSyncFallback: false,
);
final endTime = DateTime.now();
final durationMs = endTime.difference(startTime).inMilliseconds;
_logger.debug('[黑屏调试] start() 完成(耗时: ${durationMs}ms');
if (err != null && err.isNotEmpty) {
_logger.error('[黑屏调试] start() 错误: $err');
return left(err);
}
return right(unit);
} catch (e) {
_logger.error('[黑屏调试] start() 异常: $e');
return left(e.toString());
}
});
}
@override
TaskEither<String, Unit> stop() {
return TaskEither(
() => CombineWorker().execute(
() {
final err = _box.stop().cast<Utf8>().toDartString();
if (err.isNotEmpty) {
return left(err);
}
return right(unit);
},
),
);
return TaskEither(() async {
try {
final startTime = DateTime.now();
_logger.debug('[黑屏调试] stop() 开始调用 libcore.dll - $startTime');
final err = await IsolateWorker().execute(
_ffiStop,
allowSyncFallback: false,
);
final endTime = DateTime.now();
final durationMs = endTime.difference(startTime).inMilliseconds;
_logger.debug('[黑屏调试] stop() 完成(耗时: ${durationMs}ms');
if (err != null && err.isNotEmpty) {
_logger.error('[黑屏调试] stop() 错误: $err');
return left(err);
}
return right(unit);
} catch (e) {
_logger.error('[黑屏调试] stop() 异常: $e');
return left(e.toString());
}
});
}
@override
TaskEither<String, Unit> restart(
String configPath,
String name,
bool disableMemoryLimit,
) {
String configPath,
String name,
bool disableMemoryLimit,
) {
loggy.debug("restarting, memory limit: [${!disableMemoryLimit}]");
return TaskEither(
() => CombineWorker().execute(
() {
final err = _box
.restart(
configPath.toNativeUtf8().cast(),
disableMemoryLimit ? 1 : 0,
)
.cast<Utf8>()
.toDartString();
if (err.isNotEmpty) {
return left(err);
}
return right(unit);
},
),
);
return TaskEither(() async {
try {
final err = await IsolateWorker().execute(
() => _ffiRestart(configPath, disableMemoryLimit),
allowSyncFallback: false,
);
if (err != null && err.isNotEmpty) {
return left(err);
}
return right(unit);
} catch (e) {
return left(e.toString());
}
});
}
@override
@ -243,7 +279,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
_serviceStatsStream = null;
},
).map(
(event) {
(event) {
if (event case String _) {
if (event.startsWith('error:')) {
loggy.error("[service stats client] error received: $event");
@ -283,7 +319,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
}
},
).map(
(event) {
(event) {
if (event case String _) {
if (event.startsWith('error:')) {
logger.error("error received: $event");
@ -327,7 +363,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
}
},
).map(
(event) {
(event) {
if (event case String _) {
if (event.startsWith('error:')) {
logger.error(event);
@ -359,38 +395,38 @@ class FFISingboxService with InfraLogger implements SingboxService {
@override
TaskEither<String, Unit> selectOutbound(String groupTag, String outboundTag) {
return TaskEither(
() => CombineWorker().execute(
() {
final err = _box
.selectOutbound(
groupTag.toNativeUtf8().cast(),
outboundTag.toNativeUtf8().cast(),
)
.cast<Utf8>()
.toDartString();
if (err.isNotEmpty) {
return left(err);
}
return right(unit);
},
),
);
return TaskEither(() async {
try {
final err = await IsolateWorker().execute(
() => _ffiSelectOutbound(groupTag, outboundTag),
allowSyncFallback: false,
);
if (err != null && err.isNotEmpty) {
return left(err);
}
return right(unit);
} catch (e) {
return left(e.toString());
}
});
}
@override
TaskEither<String, Unit> urlTest(String groupTag) {
return TaskEither(
() => CombineWorker().execute(
() {
final err = _box.urlTest(groupTag.toNativeUtf8().cast()).cast<Utf8>().toDartString();
if (err.isNotEmpty) {
return left(err);
}
return right(unit);
},
),
);
return TaskEither(() async {
try {
final err = await IsolateWorker().execute(
() => _ffiUrlTest(groupTag),
allowSyncFallback: false,
);
if (err != null && err.isNotEmpty) {
return left(err);
}
return right(unit);
} catch (e) {
return left(e.toString());
}
});
}
final _logBuffer = <String>[];
@ -409,14 +445,10 @@ class FFISingboxService with InfraLogger implements SingboxService {
@override
TaskEither<String, Unit> clearLogs() {
return TaskEither(
() => CombineWorker().execute(
() {
_logBuffer.clear();
return right(unit);
},
),
);
return TaskEither(() async {
_logBuffer.clear();
return right(unit);
});
}
Future<List<String>> _readLogFile(File file) async {
@ -443,23 +475,165 @@ class FFISingboxService with InfraLogger implements SingboxService {
required String previousAccessToken,
}) {
loggy.debug("generating warp config");
return TaskEither(
() => CombineWorker().execute(
() {
final response = _box
.generateWarpConfig(
licenseKey.toNativeUtf8().cast(),
previousAccountId.toNativeUtf8().cast(),
previousAccessToken.toNativeUtf8().cast(),
)
.cast<Utf8>()
.toDartString();
if (response.startsWith("error:")) {
return left(response.replaceFirst('error:', ""));
}
return right(warpFromJson(jsonDecode(response)));
},
),
);
return TaskEither(() async {
try {
final result = await IsolateWorker().execute(
() => _ffiGenerateWarpConfig(licenseKey, previousAccountId, previousAccessToken),
allowSyncFallback: false,
);
final ok = result.isNotEmpty && result[0] == true;
final payload = result.length > 1 ? result[1] as String : '';
if (!ok) {
return left(payload);
}
return right(warpFromJson(jsonDecode(payload)));
} catch (e) {
return left(e.toString());
}
});
}
}
SingboxNativeLibrary _ffiLoadLibrary() {
String fullPath = "";
if (Platform.environment.containsKey('FLUTTER_TEST')) {
fullPath = "libcore";
}
if (Platform.isWindows) {
fullPath = p.join(fullPath, "libcore.dll");
} else if (Platform.isMacOS) {
fullPath = p.join(fullPath, "libcore.dylib");
} else {
fullPath = p.join(fullPath, "libcore.so");
}
final lib = DynamicLibrary.open(fullPath);
final box = SingboxNativeLibrary(lib);
box.setupOnce(NativeApi.initializeApiDLData);
return box;
}
String? _ffiSetup(
String baseDir,
String workingDir,
String tempDir,
int statusPort,
int debugFlag,
) {
final box = _ffiLoadLibrary();
final err = box
.setup(
baseDir.toNativeUtf8().cast(),
workingDir.toNativeUtf8().cast(),
tempDir.toNativeUtf8().cast(),
statusPort,
debugFlag,
)
.cast<Utf8>()
.toDartString();
return err.isEmpty ? null : err;
}
String? _ffiValidateConfig(
String path,
String tempPath,
int debugFlag,
) {
final box = _ffiLoadLibrary();
final err = box
.parse(
path.toNativeUtf8().cast(),
tempPath.toNativeUtf8().cast(),
debugFlag,
)
.cast<Utf8>()
.toDartString();
return err.isEmpty ? null : err;
}
String? _ffiChangeOptions(String optionsJson) {
final box = _ffiLoadLibrary();
final err = box.changeHiddifyOptions(optionsJson.toNativeUtf8().cast()).cast<Utf8>().toDartString();
return err.isEmpty ? null : err;
}
List<Object?> _ffiGenerateFullConfig(String path) {
final box = _ffiLoadLibrary();
final response = box
.generateConfig(
path.toNativeUtf8().cast(),
)
.cast<Utf8>()
.toDartString();
if (response.startsWith("error")) {
return [false, response.replaceFirst("error", "")];
}
return [true, response];
}
String? _ffiStart(String configPath, bool disableMemoryLimit) {
final box = _ffiLoadLibrary();
final err = box
.start(
configPath.toNativeUtf8().cast(),
disableMemoryLimit ? 1 : 0,
)
.cast<Utf8>()
.toDartString();
return err.isEmpty ? null : err;
}
String? _ffiStop() {
final box = _ffiLoadLibrary();
final err = box.stop().cast<Utf8>().toDartString();
return err.isEmpty ? null : err;
}
String? _ffiRestart(String configPath, bool disableMemoryLimit) {
final box = _ffiLoadLibrary();
final err = box
.restart(
configPath.toNativeUtf8().cast(),
disableMemoryLimit ? 1 : 0,
)
.cast<Utf8>()
.toDartString();
return err.isEmpty ? null : err;
}
String? _ffiSelectOutbound(String groupTag, String outboundTag) {
final box = _ffiLoadLibrary();
final err = box
.selectOutbound(
groupTag.toNativeUtf8().cast(),
outboundTag.toNativeUtf8().cast(),
)
.cast<Utf8>()
.toDartString();
return err.isEmpty ? null : err;
}
String? _ffiUrlTest(String groupTag) {
final box = _ffiLoadLibrary();
final err = box.urlTest(groupTag.toNativeUtf8().cast()).cast<Utf8>().toDartString();
return err.isEmpty ? null : err;
}
List<Object?> _ffiGenerateWarpConfig(
String licenseKey,
String previousAccountId,
String previousAccessToken,
) {
final box = _ffiLoadLibrary();
final response = box
.generateWarpConfig(
licenseKey.toNativeUtf8().cast(),
previousAccountId.toNativeUtf8().cast(),
previousAccessToken.toNativeUtf8().cast(),
)
.cast<Utf8>()
.toDartString();
if (response.startsWith("error:")) {
return [false, response.replaceFirst("error:", "")];
}
return [true, response];
}

View File

@ -0,0 +1,21 @@
import 'dart:async';
import 'dart:isolate';
/// Simple worker that executes functions in a separate isolate.
/// Replacement for combine package's CombineWorker.
class IsolateWorker {
/// Execute a function in a separate isolate and return the result.
///
/// Note: The function must be a top-level function or a static method,
/// and it cannot capture non-sendable objects from the surrounding scope.
Future<T> execute<T>(T Function() computation, {bool allowSyncFallback = false}) async {
try {
return await Isolate.run(computation);
} catch (e) {
if (!allowSyncFallback) {
rethrow;
}
return computation();
}
}
}

View File

@ -1517,6 +1517,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.0"
synchronized:
dependency: "direct main"
description:
name: synchronized
sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225"
url: "https://pub.dev"
source: hosted
version: "3.3.0+3"
term_glyph:
dependency: transitive
description:

View File

@ -58,6 +58,7 @@ dependencies:
fpdart: ^1.1.0
dartx: ^1.2.0
rxdart: ^0.27.7
synchronized: ^3.0.1 # 🔧 P0-4: 用于线程安全的互斥锁保护
combine: 0.5.7 # 精确版本,兼容 Flutter 3.24.3(与 hiddify-app 相同)
encrypt: ^5.0.0
path: ^1.8.3