Compare commits
37 Commits
dev
..
1809c11473
| Author | SHA1 | Date | |
|---|---|---|---|
| 1809c11473 | |||
| 343bb50c77 | |||
| 11afee1d6f | |||
| 96d1a107cf | |||
| 52f241a5a7 | |||
| 5dfb2b7432 | |||
| 85dc810421 | |||
| ee3fdd91fa | |||
| e537775e71 | |||
| f97a21be32 | |||
| d3f48e345e | |||
| ac8a04fd24 | |||
| becc03acfd | |||
| 9123d5f0e4 | |||
| 91185a593a | |||
| d6386ecfa9 | |||
| 62d1af8517 | |||
| 3b05d4ff72 | |||
| bcb4d17236 | |||
| d99f588a5b | |||
| 691dd6e1d6 | |||
| b442289b8a | |||
| 89d13e6237 | |||
| 8fd742a688 | |||
| 325a63d35f | |||
| 679c303457 | |||
| 59aa67d456 | |||
| 2a1ee3018d | |||
| 76e83e0135 | |||
| 89d6ec3c76 | |||
| 670eb7ebc9 | |||
| 46295f4543 | |||
| a47174df56 | |||
| 748ec6bee9 | |||
| a884e6c838 | |||
| 920b86e56a | |||
| a78b40c339 |
+3
-2
@@ -1,3 +1,4 @@
|
||||
[submodule "libcore"]
|
||||
path = libcore
|
||||
url = https://github.com/hiddify/hiddify-next-core
|
||||
path = libcore
|
||||
url = https://github.com/hiddify/hiddify-next-core
|
||||
branch = main
|
||||
|
||||
@@ -57,7 +57,7 @@ android {
|
||||
defaultConfig {
|
||||
applicationId "app.hifastvpn.com"
|
||||
minSdkVersion flutter.minSdkVersion
|
||||
targetSdkVersion 36
|
||||
targetSdkVersion 34
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
multiDexEnabled true
|
||||
@@ -105,8 +105,8 @@ android {
|
||||
debugSymbolLevel 'FULL'
|
||||
}
|
||||
// 禁用代码混淆和资源压缩,解决VPN连接问题
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
minifyEnabled false
|
||||
shrinkResources false
|
||||
// proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,11 @@
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
|
||||
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<!-- 如果 targetSdkVersion >= 33 -->
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<application
|
||||
android:name=".Application"
|
||||
android:banner="@mipmap/ic_banner"
|
||||
|
||||
Executable → Regular
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 5.4 KiB |
@@ -193,7 +193,7 @@
|
||||
"rewardDetails": "Reward Details >",
|
||||
"steps": "Invitation Steps",
|
||||
"inviteFriend": "Invite Friends",
|
||||
"acceptInvite": "Friends accept invitation\nPlace order and register",
|
||||
"acceptInvite": "Friends accept invitationPlace order and register",
|
||||
"getReward": "Get Reward",
|
||||
"shareLink": "Share Link",
|
||||
"shareQR": "Share QR Code",
|
||||
@@ -398,7 +398,7 @@
|
||||
"content": "Would you like to update now?",
|
||||
"updateNow": "Update Now",
|
||||
"updateLater": "Later",
|
||||
"defaultContent": "1. Optimize app performance\n2. Fix known issues\n3. Improve user experience"
|
||||
"defaultContent": "1. Optimize app performance2. Fix known issues3. Improve user experience"
|
||||
},
|
||||
"country": {
|
||||
"cn": "China",
|
||||
|
||||
@@ -256,7 +256,7 @@
|
||||
"rewardDetails": "Detalles de recompensa >",
|
||||
"steps": "Pasos de invitación",
|
||||
"inviteFriend": "Invitar amigo",
|
||||
"acceptInvite": "El amigo acepta la invitación\ny se registra",
|
||||
"acceptInvite": "El amigo acepta la invitacióny se registra",
|
||||
"getReward": "Obtener recompensa",
|
||||
"shareLink": "Compartir por enlace",
|
||||
"shareQR": "Compartir por código QR",
|
||||
@@ -404,7 +404,7 @@
|
||||
"content": "¿Actualizar ahora?",
|
||||
"updateNow": "Actualizar ahora",
|
||||
"updateLater": "Más tarde",
|
||||
"defaultContent": "1. Optimización del rendimiento de la aplicación\n2. Corrección de problemas conocidos\n3. Mejora de la experiencia del usuario"
|
||||
"defaultContent": "1. Optimización del rendimiento de la aplicación2. Corrección de problemas conocidos3. Mejora de la experiencia del usuario"
|
||||
},
|
||||
"kr_invite": {
|
||||
"close": "Cerrar",
|
||||
|
||||
@@ -193,7 +193,7 @@
|
||||
"rewardDetails": "Tasu üksikasjad >",
|
||||
"steps": "Kutse Sammud",
|
||||
"inviteFriend": "Kutsu Sõbrad",
|
||||
"acceptInvite": "Sõbrad aktsepteerivad kutset\nTee tellimus ja registreeru",
|
||||
"acceptInvite": "Sõbrad aktsepteerivad kutsetTee tellimus ja registreeru",
|
||||
"getReward": "Saada Tasu",
|
||||
"shareLink": "Jaga Linki",
|
||||
"shareQR": "Jaga QR-koodi",
|
||||
@@ -395,7 +395,7 @@
|
||||
"content": "Uuendada nüüd?",
|
||||
"updateNow": "Uuenda nüüd",
|
||||
"updateLater": "Hiljem",
|
||||
"defaultContent": "1. Rakenduse jõudluse optimeerimine\n2. Teadaolevate probleemide parandamine\n3. Kasutajamugavuse parandamine"
|
||||
"defaultContent": "1. Rakenduse jõudluse optimeerimine2. Teadaolevate probleemide parandamine3. Kasutajamugavuse parandamine"
|
||||
},
|
||||
"country": {
|
||||
"cn": "Hiina",
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
"rewardDetails": "報酬の詳細 >",
|
||||
"steps": "招待の手順",
|
||||
"inviteFriend": "友達を招待",
|
||||
"acceptInvite": "友達が招待を受け入れ\n注文して登録",
|
||||
"acceptInvite": "友達が招待を受け入れ注文して登録",
|
||||
"getReward": "報酬を獲得",
|
||||
"shareLink": "リンクを共有",
|
||||
"shareQR": "QRコードを共有",
|
||||
@@ -412,7 +412,7 @@
|
||||
"content": "今すぐアップデートしますか?",
|
||||
"updateNow": "今すぐアップデート",
|
||||
"updateLater": "後で",
|
||||
"defaultContent": "1. アプリのパフォーマンス最適化\n2. 既知の問題の修正\n3. ユーザー体験の向上"
|
||||
"defaultContent": "1. アプリのパフォーマンス最適化2. 既知の問題の修正3. ユーザー体験の向上"
|
||||
},
|
||||
"country": {
|
||||
"cn": "中国",
|
||||
|
||||
@@ -387,7 +387,7 @@
|
||||
"content": "Хотите обновить сейчас?",
|
||||
"updateNow": "Обновить сейчас",
|
||||
"later": "Позже",
|
||||
"defaultContent": "1. Оптимизация производительности приложения\n2. Исправление известных проблем\n3. Улучшение пользовательского опыта"
|
||||
"defaultContent": "1. Оптимизация производительности приложения2. Исправление известных проблем3. Улучшение пользовательского опыта"
|
||||
},
|
||||
"country": {
|
||||
"cn": "Китай",
|
||||
|
||||
@@ -232,13 +232,13 @@
|
||||
"longestConnection": "最长连接时间",
|
||||
"days": "{days}天",
|
||||
"daysOfWeek": {
|
||||
"monday": "周\n一",
|
||||
"tuesday": "周\n二",
|
||||
"wednesday": "周\n三",
|
||||
"thursday": "周\n四",
|
||||
"friday": "周\n五",
|
||||
"saturday": "周\n六",
|
||||
"sunday": "周\n日"
|
||||
"monday": "周一",
|
||||
"tuesday": "周二",
|
||||
"wednesday": "周三",
|
||||
"thursday": "周四",
|
||||
"friday": "周五",
|
||||
"saturday": "周六",
|
||||
"sunday": "周日"
|
||||
},
|
||||
"processTrafficFailed": "处理流量日志数据失败"
|
||||
},
|
||||
@@ -256,7 +256,7 @@
|
||||
"rewardDetails": "奖励明细 >",
|
||||
"steps": "邀请步骤",
|
||||
"inviteFriend": "邀请好友",
|
||||
"acceptInvite": "好友接受邀请\n下单并注册",
|
||||
"acceptInvite": "好友接受邀请下单并注册",
|
||||
"getReward": "获得奖励",
|
||||
"shareLink": "分享链接",
|
||||
"shareQR": "分享二维码",
|
||||
@@ -415,7 +415,7 @@
|
||||
"content": "是否立即更新?",
|
||||
"updateNow": "立即更新",
|
||||
"updateLater": "稍后再说",
|
||||
"defaultContent": "1. 优化应用性能\n2. 修复已知问题\n3. 提升用户体验"
|
||||
"defaultContent": "1. 优化应用性能2. 修复已知问题3. 提升用户体验"
|
||||
},
|
||||
"country": {
|
||||
"cn": "中国",
|
||||
|
||||
@@ -170,13 +170,13 @@
|
||||
"longestConnection": "最長連接時間",
|
||||
"days": "{days}天",
|
||||
"daysOfWeek": {
|
||||
"monday": "週\n一",
|
||||
"tuesday": "週\n二",
|
||||
"wednesday": "週\n三",
|
||||
"thursday": "週\n四",
|
||||
"friday": "週\n五",
|
||||
"saturday": "週\n六",
|
||||
"sunday": "週\n日"
|
||||
"monday": "週一",
|
||||
"tuesday": "週二",
|
||||
"wednesday": "週三",
|
||||
"thursday": "週四",
|
||||
"friday": "週五",
|
||||
"saturday": "週六",
|
||||
"sunday": "週日"
|
||||
},
|
||||
"processTrafficFailed": "處理流量日誌資料失敗"
|
||||
},
|
||||
@@ -194,7 +194,7 @@
|
||||
"rewardDetails": "獎勵明細 >",
|
||||
"steps": "邀請步驟",
|
||||
"inviteFriend": "邀請好友",
|
||||
"acceptInvite": "好友接受邀請\n下單並註冊",
|
||||
"acceptInvite": "好友接受邀請下單並註冊",
|
||||
"getReward": "獲得獎勵",
|
||||
"shareLink": "分享連結",
|
||||
"shareQR": "分享二維碼",
|
||||
@@ -332,7 +332,7 @@
|
||||
"content": "是否立即更新?",
|
||||
"updateNow": "立即更新",
|
||||
"updateLater": "稍後",
|
||||
"defaultContent": "1. 優化應用性能\n2. 修復已知問題\n3. 改進用戶體驗"
|
||||
"defaultContent": "1. 優化應用性能2. 修復已知問題3. 改進用戶體驗"
|
||||
},
|
||||
"orderStatus": {
|
||||
"title": "訂單狀態",
|
||||
|
||||
+6
-1
@@ -1,4 +1,5 @@
|
||||
name: BearVPN
|
||||
app_name: BearVPN
|
||||
version: 1.0.0
|
||||
build_number: 1
|
||||
targets:
|
||||
@@ -10,4 +11,8 @@ targets:
|
||||
pkg:
|
||||
enable: true
|
||||
# 不签名
|
||||
sign: false
|
||||
sign: false
|
||||
windows:
|
||||
exe:
|
||||
enable: true
|
||||
icon: assets/images/tray_icon.ico
|
||||
+2
-2
@@ -5,6 +5,6 @@
|
||||
// Created by GFWFighter on 7/24/1402 AP.
|
||||
//
|
||||
|
||||
BASE_BUNDLE_IDENTIFIER=app.hi.com
|
||||
BASE_BUNDLE_IDENTIFIER=com.taw.hifastvpn
|
||||
SERVICE_IDENTIFIER=com.hi.app
|
||||
DEVELOPMENT_TEAM=3UR892FAP3
|
||||
DEVELOPMENT_TEAM=NJRRF427XB
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.app.hi.com</string>
|
||||
<string>group.$(BASE_BUNDLE_IDENTIFIER)</string>
|
||||
</array>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
|
||||
@@ -758,11 +758,11 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = PacketTunnel/HiddifyPacketTunnel.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 3UR892FAP3;
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = NJRRF427XB;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
EXCLUDED_ARCHS = armv7;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -788,7 +788,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).PacketTunnel";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = dev_p;
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "HiFastVPN-iOS-Pord-PacketTunnel";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
@@ -814,9 +814,11 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = PacketTunnel/PacketTunnelRelease.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = Q5PC7SNX27;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = NJRRF427XB;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
EXCLUDED_ARCHS = armv7;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -842,6 +844,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).PacketTunnel";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "HiFastVPN-iOS-Pord-PacketTunnel";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
@@ -865,9 +868,11 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = PacketTunnel/HiddifyPacketTunnel.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = Q5PC7SNX27;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = NJRRF427XB;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
EXCLUDED_ARCHS = armv7;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -893,6 +898,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).PacketTunnel";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "HiFastVPN-iOS-Pord-PacketTunnel";
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
@@ -933,7 +939,7 @@
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
@@ -975,7 +981,7 @@
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 3UR892FAP3;
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = NJRRF427XB;
|
||||
ENABLE_BITCODE = NO;
|
||||
"EXCLUDED_ARCHS[sdk=iphoneos*]" = armv7;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -1004,9 +1010,10 @@
|
||||
"-lresolv",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)";
|
||||
"PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = com.taw.hifastvpn;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = hot;
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "HiFastVPN-iOS-Pord";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@@ -1023,7 +1030,7 @@
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 3UR892FAP3;
|
||||
DEVELOPMENT_TEAM = NJRRF427XB;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 2.5.72.5.52.5.31.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.hiddify.ios.RunnerTests;
|
||||
@@ -1042,7 +1049,7 @@
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 3UR892FAP3;
|
||||
DEVELOPMENT_TEAM = NJRRF427XB;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 2.5.72.5.52.5.31.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.hiddify.ios.RunnerTests;
|
||||
@@ -1059,7 +1066,7 @@
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 3UR892FAP3;
|
||||
DEVELOPMENT_TEAM = NJRRF427XB;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 2.5.72.5.52.5.31.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.hiddify.ios.RunnerTests;
|
||||
@@ -1161,7 +1168,7 @@
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Distribution";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
@@ -1201,11 +1208,11 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 3UR892FAP3;
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = NJRRF427XB;
|
||||
ENABLE_BITCODE = NO;
|
||||
"EXCLUDED_ARCHS[sdk=iphoneos*]" = armv7;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64";
|
||||
@@ -1234,9 +1241,10 @@
|
||||
"-lresolv",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)";
|
||||
"PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = com.taw.hifastvpn;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = dev;
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "HiFastVPN-iOS-Pord";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@@ -1257,9 +1265,11 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = Q5PC7SNX27;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = NJRRF427XB;
|
||||
ENABLE_BITCODE = NO;
|
||||
"EXCLUDED_ARCHS[sdk=iphoneos*]" = armv7;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -1288,8 +1298,10 @@
|
||||
"-lresolv",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)";
|
||||
"PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = com.taw.hifastvpn;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "HiFastVPN-iOS-Pord";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
|
||||
@@ -52,10 +52,10 @@
|
||||
<string>需要相机权限以支持拍照功能</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>需要麦克风权限以支持语音消息功能</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>需要相册权限以支持图片上传功能</string>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>需要相册添加权限以保存图片</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>需要相册权限以支持图片上传功能</string>
|
||||
<key>SERVICE_IDENTIFIER</key>
|
||||
<string>$(SERVICE_IDENTIFIER)</string>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.app.hi.com</string>
|
||||
<string>group.com.taw.hifastvpn</string>
|
||||
</array>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.app.hi.com</string>
|
||||
<string>group.com.taw.hifastvpn</string>
|
||||
</array>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
|
||||
@@ -10,19 +10,20 @@
|
||||
<string>app-store</string>
|
||||
<key>provisioningProfiles</key>
|
||||
<dict>
|
||||
<key>app.myhiddify.com</key>
|
||||
<string>dist.apple.app.myhiddify.com</string>
|
||||
<key>app.myhiddify.com.SingBoxPacketTunnel</key>
|
||||
<string>dist.apple.app.myhiddify.com.singboxpackettunnel</string>
|
||||
<key>com.taw.hifastvpn</key>
|
||||
<string>HiFastVPN-iOS-Pord</string>
|
||||
|
||||
<key>com.taw.hifastvpn.PacketTunnel</key>
|
||||
<string>HiFastVPN-iOS-Pord-PacketTunnel</string>
|
||||
</dict>
|
||||
<key>signingCertificate</key>
|
||||
<string>E2217AF6F3AD11BA09F9FD95E025D7E637F8B081</string>
|
||||
<string>545725EC39FB3B586B4F63F4BF8D9834FDCFE946</string>
|
||||
<key>signingStyle</key>
|
||||
<string>manual</string>
|
||||
<key>stripSwiftSymbols</key>
|
||||
<true/>
|
||||
<key>teamID</key>
|
||||
<string>3UR892FAP3</string>
|
||||
<string>NJRRF427XB</string>
|
||||
<key>uploadSymbols</key>
|
||||
<true/>
|
||||
</dict>
|
||||
|
||||
@@ -1447,7 +1447,7 @@ class AppConfig {
|
||||
kr_official_telegram = config.kr_official_telegram;
|
||||
kr_official_telephone = config.kr_official_telephone;
|
||||
kr_invitation_link = config.kr_invitation_link;
|
||||
kr_website_id = config.kr_website_id;
|
||||
// kr_website_id = config.kr_website_id;
|
||||
if (config.kr_domains.isNotEmpty) {
|
||||
KRDomain.kr_handleDomains(config.kr_domains);
|
||||
}
|
||||
|
||||
@@ -28,14 +28,66 @@ class KROutboundItem {
|
||||
|
||||
/// URL
|
||||
String url = "";
|
||||
@override
|
||||
String toString() {
|
||||
return 'KROutboundItem(tag: $tag, country: $country, delay: ${urlTestDelay.value}ms)';
|
||||
}
|
||||
// ✅ 1. 将传入的 nodeListItem 保存为类的 final 成员变量
|
||||
final KrNodeListItem nodeListItem;
|
||||
/// 服务器类型
|
||||
|
||||
/// 构造函数,接受 KrNodeListItem 对象并初始化 KROutboundItem
|
||||
KROutboundItem(KrNodeListItem nodeListItem) {
|
||||
/// 构造函数,接受 KrItem 对象并初始化 KROutboundItem
|
||||
KROutboundItem(this.nodeListItem) {
|
||||
_initFromNodeListItem();
|
||||
}
|
||||
|
||||
/// 静态工厂:构造虚拟 urltest 节点(用于 ${country}-auto)
|
||||
factory KROutboundItem.fromVirtual(String tag, String country, Map<String, dynamic> config) {
|
||||
// 构造一个虚拟 KrNodeListItem,仅填充必要字段
|
||||
final virtualNode = KrNodeListItem(
|
||||
id: 0,
|
||||
name: tag,
|
||||
protocol: 'urltest',
|
||||
serverAddr: '',
|
||||
port: 0,
|
||||
uuid: '',
|
||||
config: jsonEncode(config),
|
||||
city: '',
|
||||
country: country,
|
||||
tags: [],
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
latitudeCountry: 0,
|
||||
longitudeCountry: 0,
|
||||
relayNode: '',
|
||||
relayMode: 'none',
|
||||
protocols: '',
|
||||
method: '',
|
||||
speedLimit: 0,
|
||||
traffic: 0,
|
||||
trafficRatio: 0,
|
||||
upload: 0,
|
||||
download: 0,
|
||||
startTime: '',
|
||||
expireTime: '',
|
||||
);
|
||||
return KROutboundItem._virtual(virtualNode);
|
||||
}
|
||||
|
||||
/// 私有构造:用于虚拟节点,避免重复解析
|
||||
KROutboundItem._virtual(this.nodeListItem) {
|
||||
// 直接填充虚拟节点所需字段
|
||||
id = nodeListItem.id.toString();
|
||||
protocol = nodeListItem.protocol;
|
||||
tag = nodeListItem.name;
|
||||
serverAddr = nodeListItem.serverAddr;
|
||||
city = nodeListItem.city;
|
||||
country = nodeListItem.country;
|
||||
latitude = nodeListItem.latitude;
|
||||
latitudeCountry = nodeListItem.latitudeCountry;
|
||||
longitude = nodeListItem.longitude;
|
||||
longitudeCountry = nodeListItem.longitudeCountry;
|
||||
config = jsonDecode(nodeListItem.config); // 已知 config 有效
|
||||
}
|
||||
|
||||
/// 初始化逻辑提取,供主构造调用
|
||||
void _initFromNodeListItem() {
|
||||
id = nodeListItem.id.toString();
|
||||
protocol = nodeListItem.protocol;
|
||||
latitude = nodeListItem.latitude;
|
||||
@@ -58,160 +110,163 @@ class KROutboundItem {
|
||||
return;
|
||||
}
|
||||
|
||||
// 兜底:尝试解析 config 字段(旧API格式)
|
||||
if (nodeListItem.config.isEmpty) {
|
||||
if (kDebugMode) {
|
||||
print('❌ 节点 ${nodeListItem.name} 缺少配置信息(无port或config)');
|
||||
}
|
||||
config = {};
|
||||
return;
|
||||
}
|
||||
|
||||
late Map<String, dynamic> json;
|
||||
try {
|
||||
json = jsonDecode(nodeListItem.config) as Map<String, dynamic>;
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('❌ 节点 ${nodeListItem.name} 的 config 解析失败: $e,尝试使用直接字段');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('📄 Config 内容: ${nodeListItem.config}');
|
||||
}
|
||||
_buildConfigFromFields(nodeListItem);
|
||||
return;
|
||||
}
|
||||
switch (nodeListItem.protocol) {
|
||||
case "vless":
|
||||
final securityConfig =
|
||||
json["security_config"] as Map<String, dynamic>? ?? {};
|
||||
|
||||
// 智能设置 server_name
|
||||
String serverName = securityConfig["sni"] ?? "";
|
||||
if (serverName.isEmpty) {
|
||||
serverName = nodeListItem.serverAddr;
|
||||
// 兜底:尝试从 config 字段解析(旧API格式)
|
||||
if (nodeListItem.config.isNotEmpty) {
|
||||
try {
|
||||
final json = jsonDecode(nodeListItem.config) as Map<String, dynamic>;
|
||||
if (kDebugMode) {
|
||||
print('📄 解析到 config JSON: $json');
|
||||
}
|
||||
|
||||
config = {
|
||||
"type": "vless",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": json["port"],
|
||||
"uuid": nodeListItem.uuid,
|
||||
if (json["flow"] != null && json["flow"] != "none")
|
||||
"flow": json["flow"],
|
||||
if (json["transport"] != null && json["transport"] != "tcp")
|
||||
"transport": _buildTransport(json),
|
||||
"tls": {
|
||||
"enabled": json["security"] == "tls",
|
||||
"server_name": serverName,
|
||||
"insecure": securityConfig["allow_insecure"] ?? true,
|
||||
"utls": {
|
||||
"enabled": true,
|
||||
"fingerprint": securityConfig["fingerprint"] ?? "chrome"
|
||||
|
||||
// 提取 transport 配置
|
||||
Map<String, dynamic>? transportConfig;
|
||||
if (json['transport'] != null && json['transport'] != 'tcp') {
|
||||
transportConfig = _buildTransport(json);
|
||||
if (kDebugMode) {
|
||||
print('✅ 找到 transport 配置: $transportConfig');
|
||||
}
|
||||
}
|
||||
|
||||
// 提取 security_config
|
||||
Map<String, dynamic>? securityConfig;
|
||||
if (json['security_config'] != null) {
|
||||
securityConfig = json['security_config'] as Map<String, dynamic>;
|
||||
if (kDebugMode) {
|
||||
print('✅ 找到 security_config: $securityConfig');
|
||||
}
|
||||
}
|
||||
|
||||
// 根据协议类型构建配置
|
||||
switch (nodeListItem.protocol) {
|
||||
case "shadowsocks":
|
||||
config = {
|
||||
"type": "shadowsocks",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": json["port"],
|
||||
"method": json["method"],
|
||||
"password": nodeListItem.uuid
|
||||
};
|
||||
break;
|
||||
case "vless":
|
||||
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
|
||||
.hasMatch(nodeListItem.serverAddr);
|
||||
|
||||
config = {
|
||||
"type": "vless",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": json["port"],
|
||||
"uuid": nodeListItem.uuid,
|
||||
if (transportConfig != null) "transport": transportConfig,
|
||||
if (json["security"] == "tls") "tls": {
|
||||
"enabled": true,
|
||||
if (isDomain) "server_name": securityConfig?["sni"] ?? nodeListItem.serverAddr,
|
||||
"insecure": securityConfig?["allow_insecure"] ?? true,
|
||||
"utls": {
|
||||
"enabled": true,
|
||||
"fingerprint": securityConfig?["fingerprint"] ?? "chrome"
|
||||
}
|
||||
}
|
||||
};
|
||||
break;
|
||||
case "vmess":
|
||||
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
|
||||
.hasMatch(nodeListItem.serverAddr);
|
||||
|
||||
config = {
|
||||
"type": "vmess",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": json["port"],
|
||||
"uuid": nodeListItem.uuid,
|
||||
"alter_id": 0,
|
||||
"security": "auto",
|
||||
if (transportConfig != null) "transport": transportConfig,
|
||||
if (json["security"] == "tls") "tls": {
|
||||
"enabled": true,
|
||||
if (isDomain) "server_name": securityConfig?["sni"] ?? nodeListItem.serverAddr,
|
||||
"insecure": securityConfig?["allow_insecure"] ?? true,
|
||||
"utls": {
|
||||
"enabled": true,
|
||||
"fingerprint": securityConfig?["fingerprint"] ?? "chrome"
|
||||
}
|
||||
}
|
||||
};
|
||||
break;
|
||||
case "trojan":
|
||||
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
|
||||
.hasMatch(nodeListItem.serverAddr);
|
||||
|
||||
config = {
|
||||
"type": "trojan",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": json["port"],
|
||||
"password": nodeListItem.uuid,
|
||||
if (transportConfig != null) "transport": transportConfig,
|
||||
"tls": {
|
||||
"enabled": json["security"] == "tls",
|
||||
if (isDomain) "server_name": securityConfig?["sni"] ?? nodeListItem.serverAddr,
|
||||
"insecure": securityConfig?["allow_insecure"] ?? true,
|
||||
"utls": {
|
||||
"enabled": true,
|
||||
"fingerprint": securityConfig?["fingerprint"] ?? "chrome"
|
||||
}
|
||||
}
|
||||
};
|
||||
break;
|
||||
case "hysteria":
|
||||
case "hysteria2":
|
||||
final securityConfig = json["security_config"] as Map<String, dynamic>? ?? {};
|
||||
config = {
|
||||
"type": "hysteria2",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": json["port"],
|
||||
"password": nodeListItem.uuid,
|
||||
"up_mbps": 100,
|
||||
"down_mbps": 100,
|
||||
"obfs": {
|
||||
"type": "salamander",
|
||||
"password": json["obfs_password"] ?? nodeListItem.uuid
|
||||
},
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": securityConfig["sni"] ?? "",
|
||||
"insecure": securityConfig["allow_insecure"] ?? true
|
||||
}
|
||||
};
|
||||
break;
|
||||
default:
|
||||
if (kDebugMode) {
|
||||
print('⚠️ 不支持的协议类型: ${nodeListItem.protocol}');
|
||||
}
|
||||
}
|
||||
};
|
||||
break;
|
||||
case "vmess":
|
||||
final securityConfig =
|
||||
json["security_config"] as Map<String, dynamic>? ?? {};
|
||||
|
||||
// 智能设置 server_name
|
||||
String serverName = securityConfig["sni"] ?? "";
|
||||
if (serverName.isEmpty) {
|
||||
serverName = nodeListItem.serverAddr;
|
||||
config = {};
|
||||
}
|
||||
|
||||
config = {
|
||||
"type": "vmess",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": json["port"],
|
||||
"uuid": nodeListItem.uuid,
|
||||
"alter_id": 0,
|
||||
"security": "auto",
|
||||
if (json["transport"] != null && json["transport"] != "tcp")
|
||||
"transport": _buildTransport(json),
|
||||
"tls": {
|
||||
"enabled": json["security"] == "tls",
|
||||
"server_name": serverName,
|
||||
"insecure": securityConfig["allow_insecure"] ?? true,
|
||||
"utls": {"enabled": true, "fingerprint": "chrome"}
|
||||
}
|
||||
};
|
||||
break;
|
||||
case "shadowsocks":
|
||||
config = {
|
||||
"type": "shadowsocks",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": json["port"],
|
||||
"method": json["method"],
|
||||
"password": nodeListItem.uuid
|
||||
};
|
||||
break;
|
||||
case "hysteria":
|
||||
case "hysteria2":
|
||||
// 后端的 "hysteria" 实际上是 Hysteria2 协议
|
||||
final securityConfig =
|
||||
json["security_config"] as Map<String, dynamic>? ?? {};
|
||||
config = {
|
||||
"type": "hysteria2",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": json["port"],
|
||||
"password": nodeListItem.uuid,
|
||||
"up_mbps": 100,
|
||||
"down_mbps": 100,
|
||||
"obfs": {
|
||||
"type": "salamander",
|
||||
"password": json["obfs_password"] ?? nodeListItem.uuid
|
||||
},
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": securityConfig["sni"] ?? "",
|
||||
"insecure": securityConfig["allow_insecure"] ?? true
|
||||
}
|
||||
};
|
||||
break;
|
||||
case "trojan":
|
||||
final securityConfig =
|
||||
json["security_config"] as Map<String, dynamic>? ?? {};
|
||||
|
||||
// 智能设置 server_name
|
||||
String serverName = securityConfig["sni"] ?? "";
|
||||
if (serverName.isEmpty) {
|
||||
// 如果没有配置 SNI,使用服务器地址
|
||||
serverName = nodeListItem.serverAddr;
|
||||
}
|
||||
|
||||
config = {
|
||||
"type": "trojan",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": json["port"],
|
||||
"password": nodeListItem.uuid,
|
||||
"tls": {
|
||||
"enabled": json["security"] == "tls",
|
||||
"server_name": serverName,
|
||||
"insecure": securityConfig["allow_insecure"] ?? true,
|
||||
"utls": {"enabled": true, "fingerprint": "chrome"}
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
// 检查 relayNode 是否为 JSON 字符串并解析
|
||||
if (nodeListItem.relayNode.isNotEmpty && nodeListItem.relayMode != "none") {
|
||||
final relayNodeJson = jsonDecode(nodeListItem.relayNode);
|
||||
if (relayNodeJson is List && nodeListItem.relayMode != "none") {
|
||||
// 随机选择一个元素
|
||||
final randomNode = (relayNodeJson..shuffle()).first;
|
||||
config["server"] = randomNode["host"]; // 提取 host
|
||||
config["server_port"] = randomNode["port"]; // 提取 port
|
||||
// 检查 relayNode 是否为 JSON 字符串并解析
|
||||
if (nodeListItem.relayNode.isNotEmpty && nodeListItem.relayMode != "none") {
|
||||
final relayNodeJson = jsonDecode(nodeListItem.relayNode);
|
||||
if (relayNodeJson is List && nodeListItem.relayMode != "none") {
|
||||
// 随机选择一个元素
|
||||
final randomNode = (relayNodeJson..shuffle()).first;
|
||||
config["server"] = randomNode["host"]; // 提取 host
|
||||
config["server_port"] = randomNode["port"]; // 提取 port
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('⚠️ 解析 config 字段失败: $e');
|
||||
}
|
||||
config = {};
|
||||
}
|
||||
}
|
||||
// 解析配置
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'KROutboundItem(name: ${nodeListItem.name}, protocol: ${nodeListItem.protocol}, server: ${nodeListItem.serverAddr}, port: ${nodeListItem.port})';
|
||||
}
|
||||
|
||||
/// 构建传输配置
|
||||
@@ -249,31 +304,11 @@ class KROutboundItem {
|
||||
if (kDebugMode) {
|
||||
print('🔧 开始构建节点配置 - 协议: ${nodeListItem.protocol}, 名称: ${nodeListItem.name}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('📋 节点详细信息:');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - serverAddr: ${nodeListItem.serverAddr}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - port: ${nodeListItem.port}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - uuid: ${nodeListItem.uuid}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - method: ${nodeListItem.method}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - config: ${nodeListItem.config}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - protocols: ${nodeListItem.protocols}');
|
||||
}
|
||||
|
||||
// 🔧 尝试从 config 字段解析 transport 配置
|
||||
Map<String, dynamic>? transportConfig;
|
||||
Map<String, dynamic>? securityConfig;
|
||||
int actualPort = nodeListItem.port; // 🔧 使用局部变量存储实际端口
|
||||
|
||||
// 🔧 关键修复:优先从 protocols 字段解析配置
|
||||
if (nodeListItem.protocols.isNotEmpty) {
|
||||
@@ -307,6 +342,28 @@ class KROutboundItem {
|
||||
}
|
||||
|
||||
if (matchedProtocol != null) {
|
||||
// 🔧 关键修复:只在顶层端口为0时,才使用 protocols 中的端口
|
||||
// 这样可以保留顶层的正确端口(如 53441),不被 protocols 数组中的端口(如 287)覆盖
|
||||
if (actualPort == 0 && matchedProtocol['port'] != null) {
|
||||
// 安全解析端口号
|
||||
int protocolPort = 0;
|
||||
final portValue = matchedProtocol['port'];
|
||||
if (portValue is int) {
|
||||
protocolPort = portValue;
|
||||
} else if (portValue is String) {
|
||||
protocolPort = int.tryParse(portValue) ?? 0;
|
||||
}
|
||||
|
||||
if (protocolPort > 0) {
|
||||
actualPort = protocolPort;
|
||||
if (kDebugMode) {
|
||||
print(' ✅ 从 protocols 使用端口: $protocolPort (顶层端口为0)');
|
||||
}
|
||||
}
|
||||
} else if (kDebugMode && matchedProtocol['port'] != null) {
|
||||
print(' ✅ 保留顶层端口: $actualPort (protocols中的端口: ${matchedProtocol['port']})');
|
||||
}
|
||||
|
||||
// 提取 transport 配置
|
||||
if (matchedProtocol['network'] != null || matchedProtocol['transport'] != null) {
|
||||
final network = matchedProtocol['network'] ?? matchedProtocol['transport'];
|
||||
@@ -402,7 +459,7 @@ class KROutboundItem {
|
||||
|
||||
switch (nodeListItem.protocol) {
|
||||
case "shadowsocks":
|
||||
// 优先使用 protocols 解析出来的 cipher,其次是 method 字段,最后才是默认值
|
||||
// 优先使用 protocols 解析出来的 cipher,其次是 method 字段,最后才是默认值
|
||||
String finalMethod = nodeListItem.method.isNotEmpty
|
||||
? nodeListItem.method
|
||||
: "2022-blake3-aes-256-gcm";
|
||||
@@ -411,22 +468,21 @@ class KROutboundItem {
|
||||
"type": "shadowsocks",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": nodeListItem.port,
|
||||
"server_port": actualPort,
|
||||
"method": finalMethod,
|
||||
"password": nodeListItem.uuid
|
||||
};
|
||||
if (kDebugMode) {
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
print('✅ Shadowsocks 节点配置构建成功: ${nodeListItem.name}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('📄 使用加密方法: $finalMethod');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('📄 完整配置: $config');
|
||||
print('📄 完整配置 JSON:');
|
||||
print(jsonEncode(config));
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
}
|
||||
break;
|
||||
case "vless":
|
||||
// 判断是否为域名(非IP地址)
|
||||
// 判断是否为域名(非IP地址)
|
||||
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
|
||||
.hasMatch(nodeListItem.serverAddr);
|
||||
|
||||
@@ -436,17 +492,25 @@ class KROutboundItem {
|
||||
serverName = securityConfig['sni'].toString();
|
||||
}
|
||||
|
||||
// 🔧 关键修复:根据 security_config 判断是否启用 TLS
|
||||
final bool vlessTlsEnabled = securityConfig?['tls_enabled'] ?? false;
|
||||
// 🔧 关键修复:智能判断是否启用 TLS
|
||||
// 1. 优先使用 securityConfig['tls_enabled']
|
||||
// 2. 如果没有明确配置,根据端口和域名智能判断
|
||||
bool vlessTlsEnabled = securityConfig?['tls_enabled'] ?? true; // 默认启用 TLS
|
||||
|
||||
// 如果端口是标准非TLS端口(80, 8080等),则禁用 TLS
|
||||
if (nodeListItem.port == 80 || nodeListItem.port == 8080) {
|
||||
vlessTlsEnabled = false;
|
||||
}
|
||||
|
||||
if (kDebugMode) {
|
||||
print('🔐 VLESS TLS 状态: enabled=$vlessTlsEnabled');
|
||||
print('🔐 VLESS TLS 状态: enabled=$vlessTlsEnabled (port=${nodeListItem.port}, isDomain=$isDomain)');
|
||||
}
|
||||
|
||||
config = {
|
||||
"type": "vless",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": nodeListItem.port,
|
||||
"server_port": actualPort,
|
||||
"uuid": nodeListItem.uuid,
|
||||
if (transportConfig != null) "transport": transportConfig,
|
||||
if (vlessTlsEnabled) "tls": {
|
||||
@@ -460,14 +524,27 @@ class KROutboundItem {
|
||||
}
|
||||
};
|
||||
if (kDebugMode) {
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
print('✅ VLESS 节点配置构建成功: ${nodeListItem.name}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('📄 完整配置: $config');
|
||||
print('📋 原始节点信息:');
|
||||
print(' - serverAddr: ${nodeListItem.serverAddr}');
|
||||
print(' - port: ${nodeListItem.port}');
|
||||
print(' - uuid: ${nodeListItem.uuid}');
|
||||
print(' - protocols: ${nodeListItem.protocols}');
|
||||
print('🔐 安全配置:');
|
||||
print(' - TLS 启用: $vlessTlsEnabled');
|
||||
print(' - 是域名: $isDomain');
|
||||
print(' - server_name: $serverName');
|
||||
print(' - securityConfig: $securityConfig');
|
||||
print('📡 传输配置:');
|
||||
print(' - transportConfig: $transportConfig');
|
||||
print('📄 最终生成的完整配置 JSON:');
|
||||
print(jsonEncode(config));
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
}
|
||||
break;
|
||||
case "vmess":
|
||||
// 判断是否为域名(非IP地址)
|
||||
// 判断是否为域名(非IP地址)
|
||||
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
|
||||
.hasMatch(nodeListItem.serverAddr);
|
||||
|
||||
@@ -477,17 +554,25 @@ class KROutboundItem {
|
||||
serverName = securityConfig['sni'].toString();
|
||||
}
|
||||
|
||||
// 🔧 关键修复:根据 security_config 判断是否启用 TLS
|
||||
final bool tlsEnabled = securityConfig?['tls_enabled'] ?? false;
|
||||
// 🔧 关键修复:智能判断是否启用 TLS
|
||||
// 1. 优先使用 securityConfig['tls_enabled']
|
||||
// 2. 如果没有明确配置,根据端口和域名智能判断
|
||||
bool tlsEnabled = securityConfig?['tls_enabled'] ?? true; // 默认启用 TLS
|
||||
|
||||
// 如果端口是标准非TLS端口(80, 8080等),则禁用 TLS
|
||||
if (nodeListItem.port == 80 || nodeListItem.port == 8080) {
|
||||
tlsEnabled = false;
|
||||
}
|
||||
|
||||
if (kDebugMode) {
|
||||
print('🔐 TLS 状态: enabled=$tlsEnabled');
|
||||
print('🔐 VMess TLS 状态: enabled=$tlsEnabled (port=${nodeListItem.port}, isDomain=$isDomain)');
|
||||
}
|
||||
|
||||
config = {
|
||||
"type": "vmess",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": nodeListItem.port,
|
||||
"server_port": actualPort,
|
||||
"uuid": nodeListItem.uuid,
|
||||
"alter_id": 0,
|
||||
"security": "auto",
|
||||
@@ -503,14 +588,27 @@ class KROutboundItem {
|
||||
}
|
||||
};
|
||||
if (kDebugMode) {
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
print('✅ VMess 节点配置构建成功: ${nodeListItem.name}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('📄 完整配置: $config');
|
||||
print('📋 原始节点信息:');
|
||||
print(' - serverAddr: ${nodeListItem.serverAddr}');
|
||||
print(' - port: ${nodeListItem.port}');
|
||||
print(' - uuid: ${nodeListItem.uuid}');
|
||||
print(' - protocols: ${nodeListItem.protocols}');
|
||||
print('🔐 安全配置:');
|
||||
print(' - TLS 启用: $tlsEnabled');
|
||||
print(' - 是域名: $isDomain');
|
||||
print(' - server_name: $serverName');
|
||||
print(' - securityConfig: $securityConfig');
|
||||
print('📡 传输配置:');
|
||||
print(' - transportConfig: $transportConfig');
|
||||
print('📄 最终生成的完整配置 JSON:');
|
||||
print(jsonEncode(config));
|
||||
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
}
|
||||
break;
|
||||
case "trojan":
|
||||
// 判断是否为域名(非IP地址)
|
||||
// 判断是否为域名(非IP地址)
|
||||
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
|
||||
.hasMatch(nodeListItem.serverAddr);
|
||||
|
||||
@@ -524,7 +622,7 @@ class KROutboundItem {
|
||||
"type": "trojan",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": nodeListItem.port,
|
||||
"server_port": actualPort,
|
||||
"password": nodeListItem.uuid,
|
||||
if (transportConfig != null) "transport": transportConfig,
|
||||
"tls": {
|
||||
@@ -546,7 +644,7 @@ class KROutboundItem {
|
||||
break;
|
||||
case "hysteria":
|
||||
case "hysteria2":
|
||||
// 后端的 "hysteria" 实际上是 Hysteria2 协议
|
||||
// 后端的 "hysteria" 实际上是 Hysteria2 协议
|
||||
if (kDebugMode) {
|
||||
print('🔍 构建 Hysteria2 节点: ${nodeListItem.name}');
|
||||
}
|
||||
|
||||
@@ -88,6 +88,9 @@ class KrOutboundsList {
|
||||
}
|
||||
}
|
||||
|
||||
// 生成国家自动选择虚拟节点
|
||||
_generateCountryAutoNodes(countryGroups);
|
||||
|
||||
// 将标签分组转换为 KRGroupOutboundList 并添加到 groupOutboundList
|
||||
for (var tag in tagGroups.keys) {
|
||||
final item = KRGroupOutboundList(
|
||||
@@ -108,7 +111,41 @@ class KrOutboundsList {
|
||||
country: country,
|
||||
outboundList: countryGroups[country]!)); // 添加国家分组到列表
|
||||
}
|
||||
}
|
||||
|
||||
/// 生成国家自动选择虚拟节点
|
||||
void _generateCountryAutoNodes(Map<String, List<KROutboundItem>> countryGroups) {
|
||||
for (var entry in countryGroups.entries) {
|
||||
final country = entry.key;
|
||||
final nodes = entry.value;
|
||||
final autoTag = '${country}-auto';
|
||||
|
||||
if (kDebugMode) {
|
||||
print('🤖 生成国家自动选择节点: $autoTag, 包含 ${nodes.length} 个节点');
|
||||
}
|
||||
|
||||
// 构建 urltest 配置
|
||||
final urltestConfig = {
|
||||
'type': 'urltest',
|
||||
'tag': autoTag,
|
||||
'outbounds': nodes.map((node) => node.tag).toList(),
|
||||
'url': 'https://www.google.com/generate_204',
|
||||
'interval': '10m',
|
||||
'tolerance': 50,
|
||||
"interrupt_exist_connections": true,
|
||||
};
|
||||
|
||||
// 创建虚拟节点
|
||||
final virtualNode = KROutboundItem.fromVirtual(autoTag, country, urltestConfig);
|
||||
|
||||
// 添加到各个列表
|
||||
allList.add(virtualNode);
|
||||
keyList[autoTag] = virtualNode;
|
||||
// configJsonList.add(urltestConfig);
|
||||
|
||||
if (kDebugMode) {
|
||||
print('✅ 生成虚拟节点: $autoTag, 配置: ${urltestConfig.toString()}');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,16 +7,16 @@ class KRIsRegister {
|
||||
KRIsRegister({this.kr_isRegister = false});
|
||||
|
||||
KRIsRegister.fromJson(Map<String, dynamic> json) {
|
||||
kr_isRegister = json['Status'] == "true" || json['Status'] == true
|
||||
kr_isRegister = json['exist'] == "true" || json['exist'] == true
|
||||
? true
|
||||
: false || json['status'] == "true" || json['status'] == true
|
||||
: false || json['exist'] == "true" || json['exist'] == true
|
||||
? true
|
||||
: false;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['Status'] = kr_isRegister;
|
||||
data['exist'] = kr_isRegister;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'dart:convert';
|
||||
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
||||
|
||||
class KRNodeList {
|
||||
final List<KrNodeListItem> list;
|
||||
final List<KrNodeListItem> list;
|
||||
final String subscribeId;
|
||||
final String startTime;
|
||||
final String expireTime;
|
||||
@@ -35,8 +35,8 @@ class KRNodeList {
|
||||
// 查找 id 匹配的订阅项
|
||||
try {
|
||||
subscribeData = listData.firstWhere(
|
||||
(item) => (item as Map<String, dynamic>)['id']?.toString() == requestSubscribeId,
|
||||
orElse: () => listData[0] as Map<String, dynamic>
|
||||
(item) => (item as Map<String, dynamic>)['id']?.toString() == requestSubscribeId,
|
||||
orElse: () => listData[0] as Map<String, dynamic>
|
||||
) as Map<String, dynamic>;
|
||||
KRLogUtil.kr_i('✅ 找到匹配的订阅项: id=$requestSubscribeId', tag: 'NodeList');
|
||||
} catch (e) {
|
||||
@@ -70,7 +70,7 @@ class KRNodeList {
|
||||
|
||||
class KrNodeListItem {
|
||||
final int id;
|
||||
String name;
|
||||
String name;
|
||||
final String uuid;
|
||||
final String protocol;
|
||||
final String relayMode;
|
||||
@@ -136,25 +136,68 @@ class KrNodeListItem {
|
||||
String method = json['method']?.toString() ?? ''; // 加密方法(Shadowsocks等)
|
||||
final protocols = json['protocols']?.toString() ?? ''; // 协议配置JSON
|
||||
|
||||
// 🔧 如果有 protocols 字段,从中解析 port 和 cipher
|
||||
// 🔧 打印原始节点 JSON(用于调试)
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'NodeList');
|
||||
KRLogUtil.kr_i('📥 收到节点 API 原始数据:', tag: 'NodeList');
|
||||
KRLogUtil.kr_i('节点名称: ${json['name']}', tag: 'NodeList');
|
||||
KRLogUtil.kr_i('协议类型: ${json['protocol']}', tag: 'NodeList');
|
||||
KRLogUtil.kr_i('完整 JSON:', tag: 'NodeList');
|
||||
KRLogUtil.kr_i(jsonEncode(json), tag: 'NodeList');
|
||||
|
||||
// 🔧 如果有 protocols 字段,从中解析 cipher(但不覆盖顶层的 port)
|
||||
if (protocols.isNotEmpty) {
|
||||
try {
|
||||
final protocolsList = jsonDecode(protocols) as List;
|
||||
final currentProtocol = json['protocol']?.toString().toLowerCase() ?? '';
|
||||
|
||||
KRLogUtil.kr_i('📋 protocols 字段内容 (${protocolsList.length} 个协议):', tag: 'NodeList');
|
||||
for (var i = 0; i < protocolsList.length; i++) {
|
||||
KRLogUtil.kr_i(' 协议 $i: ${jsonEncode(protocolsList[i])}', tag: 'NodeList');
|
||||
}
|
||||
|
||||
if (protocolsList.isNotEmpty) {
|
||||
final firstProtocol = protocolsList[0] as Map<String, dynamic>;
|
||||
// 优先使用 protocols 中的配置
|
||||
if (firstProtocol['port'] != null) {
|
||||
port = _parseIntSafely(firstProtocol['port']);
|
||||
// 🔧 修复:查找与当前协议类型匹配的配置,而不是直接使用第一个
|
||||
Map<String, dynamic>? matchedProtocolConfig;
|
||||
|
||||
// 尝试找到协议类型匹配的配置
|
||||
for (var protocolConfig in protocolsList) {
|
||||
final configMap = protocolConfig as Map<String, dynamic>;
|
||||
// 🔧 修复:API 使用的字段名是 'type',不是 'protocol'
|
||||
final protocolType = (configMap['type'] ?? configMap['protocol'])?.toString().toLowerCase() ?? '';
|
||||
|
||||
// 检查是否匹配(支持 shadowsocks/ss, vmess, vless, trojan 等)
|
||||
if (protocolType == currentProtocol ||
|
||||
(currentProtocol == 'shadowsocks' && protocolType == 'ss') ||
|
||||
(currentProtocol == 'ss' && protocolType == 'shadowsocks')) {
|
||||
matchedProtocolConfig = configMap;
|
||||
KRLogUtil.kr_i('🎯 找到匹配的协议配置: $protocolType', tag: 'NodeList');
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (firstProtocol['cipher'] != null && firstProtocol['cipher'].toString().isNotEmpty) {
|
||||
method = firstProtocol['cipher'].toString();
|
||||
|
||||
// 如果没找到匹配的,使用第一个配置(兼容旧API)
|
||||
final targetProtocol = matchedProtocolConfig ?? (protocolsList[0] as Map<String, dynamic>);
|
||||
|
||||
// 🔧 关键修复:只在顶层没有 port 字段时,才使用 protocols 中的端口
|
||||
// 这样可以保留顶层的正确端口(如 53441),不被 protocols 数组中的端口(如 287)覆盖
|
||||
if (port == 0 && targetProtocol['port'] != null) {
|
||||
port = _parseIntSafely(targetProtocol['port']);
|
||||
KRLogUtil.kr_i('✅ 从 protocols 解析端口: $port', tag: 'NodeList');
|
||||
} else {
|
||||
KRLogUtil.kr_i('✅ 保留顶层端口: $port (protocols中的端口: ${targetProtocol['port']})', tag: 'NodeList');
|
||||
}
|
||||
|
||||
// 提取 cipher(加密方法)
|
||||
if (targetProtocol['cipher'] != null && targetProtocol['cipher'].toString().isNotEmpty) {
|
||||
method = targetProtocol['cipher'].toString();
|
||||
KRLogUtil.kr_i('✅ 从 protocols 解析 cipher: $method', tag: 'NodeList');
|
||||
}
|
||||
KRLogUtil.kr_i('从 protocols 解析: port=$port, cipher=$method', tag: 'NodeList');
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_w('解析 protocols 字段失败: $e', tag: 'NodeList');
|
||||
KRLogUtil.kr_w('⚠️ 解析 protocols 字段失败: $e', tag: 'NodeList');
|
||||
}
|
||||
}
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'NodeList');
|
||||
|
||||
return KrNodeListItem(
|
||||
id: _parseIntSafely(json['id']),
|
||||
|
||||
@@ -59,6 +59,8 @@ class UserInfoCard extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
'ID: $userId',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14.sp,
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
||||
import 'package:kaer_with_panels/app/services/kr_subscribe_service.dart';
|
||||
import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart';
|
||||
import '../../../localization/app_translations.dart';
|
||||
import 'package:kaer_with_panels/app/modules/kr_home/controllers/kr_home_controller.dart';
|
||||
|
||||
class HINodeListController extends GetxController {
|
||||
class HINodeListController extends GetxController with WidgetsBindingObserver {
|
||||
/// 订阅服务
|
||||
final KRSubscribeService kr_subscribeService = KRSubscribeService();
|
||||
|
||||
/// 首页服务
|
||||
final KRHomeController homeController = Get.find<KRHomeController>();
|
||||
|
||||
/// 调试模式状态
|
||||
final RxBool isDebugMode = false.obs;
|
||||
|
||||
/// 模式按钮点击计数器
|
||||
int modeButtonClickCount = 0;
|
||||
|
||||
/// 最后一次点击时间
|
||||
DateTime? lastModeButtonClickTime;
|
||||
|
||||
/// 获取连接类型字符串
|
||||
String kr_getConnectionTypeString() {
|
||||
final connectionType = KRSingBoxImp.instance.kr_connectionType.value;
|
||||
@@ -41,4 +51,84 @@ class HINodeListController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理模式按钮点击(用于激活调试模式)
|
||||
void kr_handleModeButtonClick() {
|
||||
final now = DateTime.now();
|
||||
|
||||
// 检查是否在2秒内连续点击
|
||||
if (lastModeButtonClickTime != null &&
|
||||
now.difference(lastModeButtonClickTime!).inSeconds > 2) {
|
||||
// 超过2秒,重置计数器
|
||||
modeButtonClickCount = 0;
|
||||
}
|
||||
|
||||
modeButtonClickCount++;
|
||||
lastModeButtonClickTime = now;
|
||||
|
||||
KRLogUtil.kr_i('模式按钮点击次数: $modeButtonClickCount', tag: 'HINodeListController');
|
||||
|
||||
if (modeButtonClickCount >= 5) {
|
||||
// 激活调试模式
|
||||
isDebugMode.value = true;
|
||||
modeButtonClickCount = 0; // 重置计数器
|
||||
KRLogUtil.kr_i('🐛 调试模式已激活!', tag: 'HINodeListController');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取要显示的节点列表(根据调试模式过滤)
|
||||
List<dynamic> kr_getFilteredNodeList() {
|
||||
if (isDebugMode.value) {
|
||||
// 调试模式:显示所有节点
|
||||
return kr_subscribeService.allList;
|
||||
} else {
|
||||
// 正常模式:只显示 country-auto 节点
|
||||
return kr_subscribeService.allList.where((node) => node.tag.endsWith('-auto')).toList();
|
||||
}
|
||||
}
|
||||
|
||||
/// 重置调试模式
|
||||
void kr_resetDebugMode() {
|
||||
isDebugMode.value = false;
|
||||
modeButtonClickCount = 0;
|
||||
lastModeButtonClickTime = null;
|
||||
KRLogUtil.kr_i('调试模式已重置', tag: 'HINodeListController');
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
ever(homeController.kr_cutTag, (tag) {
|
||||
if (homeController.kr_isLatency.value) return;
|
||||
KRLogUtil.kr_i('🔄 节点切换成功 - 自动触发延迟测试', tag: 'HINodeListView');
|
||||
homeController.kr_urlTest();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
super.onReady();
|
||||
if (homeController.kr_isLatency.value) return;
|
||||
KRLogUtil.kr_i('🔄 节点列表显示 - 自动触发延迟测试', tag: 'HINodeListView');
|
||||
homeController.kr_urlTest();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
super.didChangeAppLifecycleState(state);
|
||||
// 当应用从后台切换回前台时
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
if (homeController.kr_isLatency.value) return;
|
||||
KRLogUtil.kr_i('🔄 节点列表显示 - 自动触发延迟测试', tag: 'HINodeListView');
|
||||
homeController.kr_urlTest();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// hi_node_list_view.dart
|
||||
|
||||
import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
@@ -48,17 +47,9 @@ class HINodeListView extends GetView<HINodeListController> {
|
||||
) {
|
||||
if (outboundList.isEmpty) return 0;
|
||||
|
||||
// 过滤掉不可用节点(延迟标记为 65535 或负数)
|
||||
final validNodes = outboundList.where((node) {
|
||||
final delay = node.urlTestDelay.value;
|
||||
return delay > 0 && delay < 65535;
|
||||
}).toList();
|
||||
|
||||
if (validNodes.isEmpty) return 0;
|
||||
|
||||
// 返回最小延迟值
|
||||
validNodes.sort((a, b) => a.urlTestDelay.value.compareTo(b.urlTestDelay.value));
|
||||
return validNodes.first.urlTestDelay.value;
|
||||
outboundList.sort((a, b) => a.urlTestDelay.value.compareTo(b.urlTestDelay.value));
|
||||
return outboundList.first.urlTestDelay.value;
|
||||
}
|
||||
|
||||
/// 找出延迟最低(最快)的节点对象
|
||||
@@ -88,21 +79,14 @@ class HINodeListView extends GetView<HINodeListController> {
|
||||
// 并设置透明背景,让父组件的背景可以透出来
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: _buildSubscribeList(context)
|
||||
// child: _kr_buildRegionList(context)
|
||||
child: _buildSubscribeList(context)
|
||||
// child: _kr_buildRegionList(context)
|
||||
);
|
||||
}
|
||||
|
||||
/// 构建国家/地区分组列表
|
||||
/// 构建国家/地区分组列表(废弃不使用)
|
||||
Widget _kr_buildRegionList(BuildContext context) {
|
||||
return Obx(() {
|
||||
// 自动触发延迟测试(仅在未连接状态下)
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!controller.homeController.kr_isConnected.value && !controller.homeController.kr_isLatency.value) {
|
||||
KRLogUtil.kr_i('🔄 节点列表显示 - 自动触发延迟测试', tag: 'HINodeListView');
|
||||
controller.homeController.kr_urlTest();
|
||||
}
|
||||
});
|
||||
return _kr_buildListContainer(
|
||||
context,
|
||||
child: ListView(
|
||||
@@ -199,9 +183,20 @@ class HINodeListView extends GetView<HINodeListController> {
|
||||
),
|
||||
...controller.kr_subscribeService.countryOutboundList.map((country) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
// 自动选择这个国家下的节点延迟中最快的
|
||||
controller.homeController.onCountrySelected(country.country);
|
||||
onTap: () async {
|
||||
try {
|
||||
|
||||
final success =
|
||||
await controller.homeController.kr_performNodeSwitch('${country.country}-auto');
|
||||
print('node 点击 ${country.country} 节点数量${country.outboundList.length} 节点详情 ${country.outboundList}');
|
||||
if (success) {
|
||||
controller.homeController.kr_currentListStatus.value =
|
||||
KRHomeViewsListStatus.kr_none;
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('Auto选项切换异常: $e',
|
||||
tag: 'NodeListView');
|
||||
}
|
||||
},
|
||||
child: _kr_buildCountryListItem(context, country: country),
|
||||
);
|
||||
@@ -215,28 +210,30 @@ class HINodeListView extends GetView<HINodeListController> {
|
||||
/// 构建默认的订阅节点列表
|
||||
Widget _buildSubscribeList(BuildContext context) {
|
||||
return Obx(() {
|
||||
|
||||
// 自动触发延迟测试(仅在未连接状态下)
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!controller.homeController.kr_isConnected.value && !controller.homeController.kr_isLatency.value) {
|
||||
KRLogUtil.kr_i('🔄 节点列表显示 - 自动触发延迟测试', tag: 'HINodeListView');
|
||||
controller.homeController.kr_urlTest();
|
||||
}
|
||||
});
|
||||
return _kr_buildListContainer(
|
||||
context,
|
||||
child: ListView(
|
||||
padding: EdgeInsets.symmetric(vertical: 8.w),
|
||||
// 2. 使用 children 属性,并一次性构建所有列表项
|
||||
children: [
|
||||
if (controller.kr_subscribeService.allList.isEmpty)
|
||||
_buildEmptyListPlaceholder(context, AppTranslations.kr_home.noNodes)
|
||||
if (controller.kr_getFilteredNodeList().isEmpty)
|
||||
_buildEmptyListPlaceholder(context,
|
||||
controller.isDebugMode.value ? '调试模式:无节点数据' : AppTranslations.kr_home.noNodes)
|
||||
else ...[
|
||||
InkWell(
|
||||
onTap: () {
|
||||
controller.homeController.kr_selectNode('auto');
|
||||
controller.homeController.kr_currentListStatus.value =
|
||||
KRHomeViewsListStatus.kr_none;
|
||||
// 🔧 修复:改为 async,等待节点切换完成后再关闭列表
|
||||
onTap: () async {
|
||||
try {
|
||||
final success =
|
||||
await controller.homeController.kr_performNodeSwitch('auto');
|
||||
if (success) {
|
||||
controller.homeController.kr_currentListStatus.value =
|
||||
KRHomeViewsListStatus.kr_none;
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('Auto选项切换异常: $e',
|
||||
tag: 'NodeListView');
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
@@ -282,12 +279,34 @@ class HINodeListView extends GetView<HINodeListController> {
|
||||
),
|
||||
// 2. 第二个 Text: "根据网络IP自动匹配最快线路"
|
||||
Text(
|
||||
'根据网络IP自动匹配最快线路', // 您指定的文本
|
||||
style: KrAppTextStyle(
|
||||
'根据网络IP自动匹配最快线路', // 默认文本
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 10,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
/* Obx(() {
|
||||
// 当选择全局 auto 时,显示当前选中的节点信息
|
||||
if (controller.homeController.kr_cutTag.value == 'auto') {
|
||||
final autoNodeInfo = controller.homeController.kr_getGlobalAutoSelectedNode();
|
||||
if (autoNodeInfo != null) {
|
||||
return Text(
|
||||
'当前: ${autoNodeInfo['tag']} (${autoNodeInfo['delay']}ms)',
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 10,
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return Text(
|
||||
'根据网络IP自动匹配最快线路', // 默认文本
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 10,
|
||||
color: Colors.white,
|
||||
),
|
||||
);
|
||||
}),*/
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -308,9 +327,25 @@ class HINodeListView extends GetView<HINodeListController> {
|
||||
),
|
||||
),
|
||||
),
|
||||
...controller.kr_subscribeService.allList.map((item) {
|
||||
...controller.kr_getFilteredNodeList().map((item) {
|
||||
return InkWell(
|
||||
onTap: () => _onNodeSelected(item),
|
||||
// 🔧 修复:改为 async,等待节点切换完成后再关闭列表
|
||||
onTap: () async {
|
||||
try {
|
||||
KRLogUtil.kr_i(
|
||||
'🔄 用户点击节点: ${item.tag}');
|
||||
final success = await controller.homeController
|
||||
.kr_performNodeSwitch(item.tag);
|
||||
if (success) {
|
||||
controller.homeController.kr_currentListStatus.value =
|
||||
KRHomeViewsListStatus.kr_none;
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e(
|
||||
'节点切换异常: $e',
|
||||
tag: 'NodeListView');
|
||||
}
|
||||
},
|
||||
child: _kr_buildNodeListItem(context, item: item),
|
||||
);
|
||||
}).toList(),
|
||||
@@ -344,15 +379,6 @@ class HINodeListView extends GetView<HINodeListController> {
|
||||
);
|
||||
}
|
||||
|
||||
/// 节点选中时的通用处理逻辑
|
||||
void _onNodeSelected(KROutboundItem item) {
|
||||
KRLogUtil.kr_i('Node selected: ${item.tag}');
|
||||
KRSingBoxImp.instance.kr_selectOutbound(item.tag);
|
||||
controller.homeController.kr_selectNode(item.tag);
|
||||
// 切换回主页的仪表盘视图
|
||||
controller.homeController.kr_currentListStatus.value = KRHomeViewsListStatus.kr_none;
|
||||
}
|
||||
|
||||
/// 构建单个节点列表项的UI
|
||||
Widget _kr_buildNodeListItem(BuildContext context, {required KROutboundItem item}) {
|
||||
return Container(
|
||||
@@ -372,55 +398,75 @@ class HINodeListView extends GetView<HINodeListController> {
|
||||
KRCountryFlag(countryCode: item.country, width: 30.w, height: 20.w, isCircle: false, maintainSize: false),
|
||||
SizedBox(width: 12.w),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
controller.homeController.kr_getCountryFullName(item.country),
|
||||
style: KrAppTextStyle(fontSize: 14, color: Colors.white),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
Obx(() => controller.homeController.kr_cutTag.value == item.tag
|
||||
? Container(
|
||||
margin: EdgeInsets.only(left: 4.w),
|
||||
padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 1.w),
|
||||
decoration: BoxDecoration(
|
||||
color: krModernGreenLight.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(4.w),
|
||||
),
|
||||
child: Text(
|
||||
AppTranslations.kr_home.selected,
|
||||
style: KrAppTextStyle(fontSize: 10, color: krModernGreen, fontWeight: FontWeight.w500),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink()),
|
||||
],
|
||||
child: Obx(() {
|
||||
final isDebug = controller.isDebugMode.value;
|
||||
|
||||
final text = isDebug
|
||||
? '${controller.homeController.kr_getCountryFullName(item.country)} - ${item.tag}'
|
||||
: '${controller.homeController.kr_getCountryFullName(item.country)}';
|
||||
|
||||
return Text(
|
||||
text,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
SizedBox(height: 2.w),
|
||||
Obx(() {
|
||||
// 2. 获取用于显示的延迟值
|
||||
final int delay = _getDisplayDelay(controller, item);
|
||||
return Text(
|
||||
'${delay}ms',
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 10,
|
||||
color: krModernGreen,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
);
|
||||
}),
|
||||
Text(
|
||||
item.city,
|
||||
style: KrAppTextStyle(fontSize: 12, color: Theme.of(context).textTheme.bodySmall?.color),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
Obx(() {
|
||||
// 1. 获取延迟值和测速状态
|
||||
int displayDelay;
|
||||
bool isTesting = controller.homeController.kr_isLatency.value;
|
||||
|
||||
// 极简逻辑:只根据节点类型决定显示方式
|
||||
print('🔄 _kr_buildNodeListItem节点: item.tag=${item.tag}, country=${item.country}');
|
||||
|
||||
// 如果节点本身是auto组节点,显示组内最快节点的速度
|
||||
if (item.tag.endsWith('-auto')) {
|
||||
print('🎯 检测到auto组节点,获取最快节点信息');
|
||||
final autoNodeInfo = controller.homeController.kr_getCountryAutoSelectedNode(item.country);
|
||||
print('📊 _kr_buildNodeListItem获取auto最快节点: $autoNodeInfo');
|
||||
displayDelay = autoNodeInfo?['delay'] ?? 0;
|
||||
print('✅ _kr_buildNodeListItem使用auto最快节点延迟: $displayDelay');
|
||||
} else {
|
||||
// 普通节点,直接显示节点自身的速度
|
||||
displayDelay = item.urlTestDelay.value;
|
||||
print('🔄 _kr_buildNodeListItem使用普通节点延迟: $displayDelay');
|
||||
}
|
||||
|
||||
// 2. 声明文本和颜色变量
|
||||
String delayText;
|
||||
Color delayColor;
|
||||
|
||||
// 3. 根据状态设置文本和颜色
|
||||
if (isTesting && displayDelay == 0) {
|
||||
delayText = '测速中...';
|
||||
delayColor = Colors.grey; // 测速时使用灰色
|
||||
} else if (displayDelay == 0) {
|
||||
delayText = '- ms'; // 未测速或初始状态
|
||||
delayColor = Colors.grey;
|
||||
} else if (displayDelay >= 3000) {
|
||||
delayText = AppTranslations.kr_home.timeout; // "超时"
|
||||
delayColor = Colors.red; // 超时状态使用红色
|
||||
} else {
|
||||
delayText = '${displayDelay}ms';
|
||||
// 正常延迟,根据快慢设置不同颜色
|
||||
delayColor = (displayDelay < 500) ? krModernGreen : Colors.orange;
|
||||
}
|
||||
|
||||
// 4. 返回最终的 Text 组件
|
||||
return Text(
|
||||
delayText,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 10,
|
||||
color: delayColor, // 使用动态计算出的颜色
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
);
|
||||
}),
|
||||
SizedBox(width: 12.w),
|
||||
Obx(() => controller.homeController.kr_cutTag.value == item.tag
|
||||
? KrLocalImage(
|
||||
imageName: 'radio-active-icon',
|
||||
@@ -441,18 +487,7 @@ class HINodeListView extends GetView<HINodeListController> {
|
||||
|
||||
/// 构建国家列表项的UI
|
||||
Widget _kr_buildCountryListItem(BuildContext context, {required country}) {
|
||||
// 获取延迟颜色
|
||||
Color getLatencyColor(int delay) {
|
||||
if (delay == 0) {
|
||||
return Colors.transparent;
|
||||
} else if (delay < 500) {
|
||||
return krModernGreen;
|
||||
} else if (delay < 3000) {
|
||||
return Color(0xFFFFB700); // 使用更容易看清的黄色
|
||||
} else {
|
||||
return Colors.red;
|
||||
}
|
||||
}
|
||||
|
||||
return Container(
|
||||
key: ValueKey(country),
|
||||
decoration: BoxDecoration(
|
||||
@@ -476,22 +511,42 @@ class HINodeListView extends GetView<HINodeListController> {
|
||||
),
|
||||
),
|
||||
Obx(() {
|
||||
final int delay = getFastestNodeDelay(controller, country.outboundList);
|
||||
// 1. 获取延迟值和测速状态
|
||||
int displayDelay = getFastestNodeDelay(controller, country.outboundList);
|
||||
bool isTesting = controller.homeController.kr_isLatency.value;
|
||||
|
||||
// 2. 声明文本和颜色变量
|
||||
String delayText;
|
||||
Color delayColor;
|
||||
|
||||
// 3. 根据状态设置文本和颜色
|
||||
if (isTesting && displayDelay == 0) {
|
||||
delayText = '测速中...';
|
||||
delayColor = Colors.grey; // 测速时使用灰色
|
||||
} else if (displayDelay == 0) {
|
||||
delayText = '- ms'; // 未测速或初始状态
|
||||
delayColor = Colors.grey;
|
||||
} else if (displayDelay >= 3000) {
|
||||
delayText = AppTranslations.kr_home.timeout; // "超时"
|
||||
delayColor = Colors.red; // 超时状态使用红色
|
||||
} else {
|
||||
delayText = '${displayDelay}ms';
|
||||
// 正常延迟,根据快慢设置不同颜色
|
||||
delayColor = (displayDelay < 500) ? krModernGreen : Colors.orange;
|
||||
}
|
||||
|
||||
// 4. 返回最终的 Text 组件
|
||||
return Text(
|
||||
delay == 0
|
||||
? ''
|
||||
: delay >= 3000
|
||||
? AppTranslations.kr_home.timeout
|
||||
: '${delay}ms',
|
||||
delayText,
|
||||
style: KrAppTextStyle(
|
||||
fontSize: 10,
|
||||
color: getLatencyColor(delay),
|
||||
color: delayColor, // 使用动态计算出的颜色
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
);
|
||||
}),
|
||||
SizedBox(width: 12.w),
|
||||
Obx(() => controller.homeController.kr_coutryText.value == country.country
|
||||
Obx(() => controller.homeController.kr_cutTag.value == '${country.country}-auto'
|
||||
? KrLocalImage(
|
||||
imageName: 'radio-active-icon',
|
||||
imageType: ImageType.svg,
|
||||
|
||||
@@ -19,10 +19,47 @@ class HINodePageView extends GetView<HINodeListController> {
|
||||
return HIBaseScaffold(
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
left: 0,
|
||||
child: Obx(() => controller.isDebugMode.value
|
||||
? GestureDetector(
|
||||
onTap: () {
|
||||
controller.kr_resetDebugMode();
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.w),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red.withOpacity(0.8),
|
||||
borderRadius: BorderRadius.circular(12.w),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.bug_report,
|
||||
size: 12.w,
|
||||
color: Colors.white,
|
||||
),
|
||||
SizedBox(width: 3.w),
|
||||
Text(
|
||||
'关闭调试',
|
||||
style: TextStyle(
|
||||
fontSize: 10.sp,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink()),
|
||||
),
|
||||
// 主要内容区域
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
|
||||
// 模式切换器
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 100.w, right: 60.w),
|
||||
@@ -159,7 +196,12 @@ class HINodePageView extends GetView<HINodeListController> {
|
||||
}) {
|
||||
return Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
onTap: () {
|
||||
// 处理模式按钮点击(用于调试模式激活)
|
||||
controller.kr_handleModeButtonClick();
|
||||
// 执行原有的点击逻辑
|
||||
onTap();
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: EdgeInsets.symmetric(vertical: 4.w),
|
||||
|
||||
@@ -69,8 +69,12 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
|
||||
children: [
|
||||
Obx(() {
|
||||
final account = KRAppRunData.getInstance().kr_account.value;
|
||||
final isDeviceLogin = account != null && account.startsWith('9000');
|
||||
|
||||
if (isDeviceLogin) return const SizedBox();
|
||||
|
||||
return Text(
|
||||
(account != null && account.isNotEmpty) ? account : '未绑定',
|
||||
account ?? '',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20.sp,
|
||||
@@ -425,7 +429,7 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'设备:${_extractDeviceModel(userAgent)}',
|
||||
'设备:${_extractDeviceModel(userAgent, deviceType)}',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 10.sp, //
|
||||
@@ -433,7 +437,7 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
|
||||
),
|
||||
SizedBox(height: 4.h),
|
||||
Text(
|
||||
'ID: ${identifier.substring(0, identifier.length > 4 ? 4 : identifier.length)}$id',
|
||||
'SN: ${identifier.substring(0, identifier.length > 4 ? 4 : identifier.length)}$id',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.9),
|
||||
fontSize: 10.sp,
|
||||
@@ -510,21 +514,22 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20.w),
|
||||
Obx(() {
|
||||
final current = controller.kr_subscribeService.kr_currentSubscribe.value;
|
||||
Text(
|
||||
'每个账号最多允许同时使用2台设备同时在线',
|
||||
style: KrAppTextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
)
|
||||
/*Obx(() {
|
||||
// final current = controller.kr_subscribeService.kr_currentSubscribe.value;
|
||||
|
||||
// 如果 current 为 null,显示默认值 0
|
||||
final deviceLimit = current?.deviceLimit ?? 0;
|
||||
// final deviceLimit = current?.deviceLimit ?? 0;
|
||||
|
||||
return Text(
|
||||
'每个账号最多允许同时使用$deviceLimit台设备同时在线',
|
||||
style: KrAppTextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 14.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
);
|
||||
}),
|
||||
return
|
||||
}),*/
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -585,12 +590,12 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
|
||||
);
|
||||
}
|
||||
|
||||
String _extractDeviceModel(String deviceName) {
|
||||
String _extractDeviceModel(String deviceName, String deviceType) {
|
||||
// 匹配括号内内容
|
||||
final RegExp regExp = RegExp(r'\((.*?)\)');
|
||||
final Match? match = regExp.firstMatch(deviceName);
|
||||
|
||||
if (match != null && match.groupCount >= 1) {
|
||||
if (match != null && match.groupCount >= 1 ) {
|
||||
// 获取括号内内容
|
||||
final inside = match.group(1)!; // "Android; google Pixel 9; 15"
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import '../../../common/app_config.dart';
|
||||
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';
|
||||
@@ -133,11 +134,21 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
if (value != null) {
|
||||
isQuickConnectEnabled.value = value;
|
||||
// 保存闪连状态到本地存储
|
||||
await _storage.kr_saveBool(key: _quickConnectKey, value: value);
|
||||
await _saveQuickConnectStatus(value);
|
||||
KRLogUtil.kr_i('闪连状态已更新: $value', tag: 'QuickConnect');
|
||||
}
|
||||
}
|
||||
|
||||
// 保存闪连状态到本地存储
|
||||
Future<void> _saveQuickConnectStatus(bool enabled) async {
|
||||
try {
|
||||
await _storage.kr_saveBool(key: _quickConnectKey, value: enabled);
|
||||
KRLogUtil.kr_i('闪连状态已保存到本地存储: $enabled', tag: 'QuickConnect');
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('保存闪连状态失败: $e', tag: 'QuickConnect');
|
||||
}
|
||||
}
|
||||
|
||||
// 从本地存储加载闪连状态
|
||||
Future<void> _loadQuickConnectStatus() async {
|
||||
try {
|
||||
@@ -839,6 +850,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
if (kDebugMode) {
|
||||
}
|
||||
await KRSingBoxImp.instance.kr_start();
|
||||
|
||||
KRLogUtil.kr_i('✅ 连接命令已发送', tag: 'HomeController');
|
||||
if (kDebugMode) {
|
||||
}
|
||||
@@ -1265,10 +1277,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> onCountrySelected(String countryTag) async {
|
||||
await KRSingBoxImp.instance.kr_selectCountry(countryTag);
|
||||
KRCommonUtil.kr_showToast('已切换到 $countryTag 节点组');
|
||||
}
|
||||
|
||||
/// 🔧 修复:简化的 kr_selectNode 方法
|
||||
/// 现在只是委托给新的 kr_performNodeSwitch 方法
|
||||
@@ -1414,8 +1422,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
|
||||
/// 获取真实连接的节点信息(auto 模式下获取实际连接的节点)
|
||||
Map<String, dynamic> kr_getRealConnectedNodeInfo() {
|
||||
// 如果不是 auto 模式,直接返回当前选中的节点信息
|
||||
if (kr_cutTag.value != 'auto') {
|
||||
// 如果不是 auto 模式,也不是 country-auto 模式,直接返回当前选中的节点信息
|
||||
if (kr_cutTag.value != 'auto' && !kr_cutTag.value.endsWith('-auto')) {
|
||||
final node = kr_subscribeService.keyList[kr_cutSeletedTag.value];
|
||||
return {
|
||||
'nodeName': kr_cutSeletedTag.value,
|
||||
@@ -1424,11 +1432,13 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
};
|
||||
}
|
||||
|
||||
// auto 模式下,获取 urltest 组的实际连接节点
|
||||
// 处理 auto 模式(包括全局 auto 和 country-auto)
|
||||
print('当前活动组----${KRSingBoxImp.instance.kr_activeGroups.length}');
|
||||
for (var group in KRSingBoxImp.instance.kr_activeGroups) {
|
||||
print('当前活动组----$group}');
|
||||
if (group.type == ProxyType.urltest) {
|
||||
|
||||
// 处理全局 auto 模式
|
||||
if (kr_cutTag.value.endsWith('auto') && group.type == ProxyType.urltest && group.tag == 'auto') {
|
||||
final selectedNode = group.selected;
|
||||
final node = kr_subscribeService.keyList[selectedNode];
|
||||
return {
|
||||
@@ -1438,11 +1448,24 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
};
|
||||
}
|
||||
}
|
||||
print('hhhhhh${kr_subscribeService.keyList}', );
|
||||
|
||||
// 处理 country-auto 模式的备用方案(当 SingBox 组数据不可用时)
|
||||
if (kr_cutTag.value.endsWith('-auto')) {
|
||||
final countryCode = kr_cutTag.value.replaceAll('-auto', '');
|
||||
KRLogUtil.kr_i('🔄 kr_getRealConnectedNodeInfo 使用备用方案获取 country-auto 信息: $countryCode', tag: 'HomeController');
|
||||
final autoNodeInfo = kr_getCountryAutoSelectedNode(countryCode);
|
||||
if (autoNodeInfo != null) {
|
||||
return {
|
||||
'nodeName': autoNodeInfo['tag'],
|
||||
'delay': autoNodeInfo['delay'] ?? -2,
|
||||
'country': autoNodeInfo['country'] ?? countryCode,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到 urltest 组,返回默认值
|
||||
return {
|
||||
'nodeName': 'auto',
|
||||
'nodeName': kr_cutTag.value,
|
||||
'delay': -2,
|
||||
'country': '',
|
||||
};
|
||||
@@ -1463,9 +1486,10 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
/// 获取真实连接的节点国家
|
||||
String kr_getRealConnectedNodeCountry() {
|
||||
final info = kr_getRealConnectedNodeInfo();
|
||||
final delay = kr_currentNodeLatency.value;
|
||||
final delay = info['delay'] as int; // 使用真实连接的节点延迟,而不是 kr_currentNodeLatency.value
|
||||
final country1 = kr_getCurrentNodeCountry();
|
||||
print('country----$country1');
|
||||
print('kr_getRealConnectedNodeCountry - delay from info: $delay, country from info: ${info['country']}');
|
||||
final country = kr_getCountryFullName(info['country']);
|
||||
if (delay == -2) {
|
||||
return '--';
|
||||
@@ -1932,7 +1956,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
final results = await KRLatencyTester.testMultipleNodes(
|
||||
nodes: nodeAddresses,
|
||||
concurrency: 10, // 每批10个并发
|
||||
timeout: 5000, // 超时时间5秒(毫秒)
|
||||
timeout: const Duration(seconds: 5),
|
||||
);
|
||||
|
||||
// 更新节点延迟
|
||||
@@ -2173,6 +2197,226 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
});
|
||||
}
|
||||
|
||||
/// 获取国家-auto组的当前选中子节点信息
|
||||
Map<String, dynamic>? kr_getCountryAutoSelectedNode(String countryCode) {
|
||||
try {
|
||||
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
|
||||
final allGroups = KRSingBoxImp.instance.kr_allGroups;
|
||||
final autoGroupTag = '${countryCode}-auto';
|
||||
|
||||
KRLogUtil.kr_i('🔍 开始获取国家-auto选中节点: countryCode=$countryCode, autoGroupTag=$autoGroupTag', tag: 'HomeController');
|
||||
KRLogUtil.kr_i('📊 活跃组数量: ${activeGroups.length}, 所有组数量: ${allGroups.length}', tag: 'HomeController');
|
||||
|
||||
// 优先检查活跃组
|
||||
if (activeGroups.isNotEmpty) {
|
||||
KRLogUtil.kr_i('✅ 活跃组不为空,优先检查活跃组', tag: 'HomeController');
|
||||
for (var group in activeGroups) {
|
||||
KRLogUtil.kr_i('🔄 检查活跃组: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'HomeController');
|
||||
|
||||
if (group.tag == autoGroupTag && group.type == ProxyType.urltest) {
|
||||
KRLogUtil.kr_i('✅ 在活跃组中找到匹配的urltest组: $autoGroupTag', tag: 'HomeController');
|
||||
return _kr_extractSelectedNodeInfo(group, countryCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果活跃组中没有,检查所有组
|
||||
if (allGroups.isNotEmpty) {
|
||||
KRLogUtil.kr_i('🔍 活跃组中未找到,检查所有组', tag: 'HomeController');
|
||||
for (var group in allGroups) {
|
||||
KRLogUtil.kr_i('🔄 检查所有组: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'HomeController');
|
||||
|
||||
if (group.tag == autoGroupTag && group.type == ProxyType.urltest) {
|
||||
KRLogUtil.kr_i('✅ 在所有组中找到匹配的urltest组: $autoGroupTag', tag: 'HomeController');
|
||||
return _kr_extractSelectedNodeInfo(group, countryCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果组数据都为空,使用备用方案:从订阅服务中找该国家最快的节点
|
||||
KRLogUtil.kr_i('🔄 组数据为空,使用备用方案从订阅服务获取最快节点', tag: 'HomeController');
|
||||
return _kr_getFastestNodeFromSubscribeService(countryCode);
|
||||
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('💥 获取国家-auto选中节点异常: $e', tag: 'HomeController');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 从订阅服务获取该国家最快节点的备用方案
|
||||
Map<String, dynamic>? _kr_getFastestNodeFromSubscribeService(String countryCode) {
|
||||
try {
|
||||
KRLogUtil.kr_i('🔄 使用订阅服务查找国家最快节点: $countryCode', tag: 'HomeController');
|
||||
|
||||
// 从订阅服务中获取该国家的所有节点
|
||||
final allNodes = kr_subscribeService.allList.where((node) =>
|
||||
node.country == countryCode && !node.tag.endsWith('-auto')
|
||||
).toList();
|
||||
|
||||
KRLogUtil.kr_i('📊 找到 ${allNodes.length} 个该国家的普通节点', tag: 'HomeController');
|
||||
|
||||
if (allNodes.isEmpty) {
|
||||
KRLogUtil.kr_w('⚠️ 未找到该国家的任何普通节点: $countryCode', tag: 'HomeController');
|
||||
return null;
|
||||
}
|
||||
|
||||
// 找出延迟最小的节点(排除0和超时)
|
||||
KROutboundItem? fastestNode;
|
||||
int fastestDelay = 999999;
|
||||
|
||||
for (var node in allNodes) {
|
||||
final delay = node.urlTestDelay.value;
|
||||
KRLogUtil.kr_w('🔍 检查节点 ${node.tag} 的延迟: $delay', tag: 'HomeController');
|
||||
if (delay > 0 && delay < fastestDelay) {
|
||||
fastestDelay = delay;
|
||||
fastestNode = node;
|
||||
}
|
||||
}
|
||||
|
||||
if (fastestNode != null) {
|
||||
KRLogUtil.kr_i('✅ 找到最快节点: ${fastestNode.tag}, 延迟: ${fastestDelay}ms', tag: 'HomeController');
|
||||
return {
|
||||
'tag': fastestNode.tag,
|
||||
'delay': fastestDelay,
|
||||
'country': countryCode,
|
||||
};
|
||||
} else {
|
||||
KRLogUtil.kr_w('⚠️ 该国家的节点都没有有效延迟数据', tag: 'HomeController');
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('💥 获取订阅服务最快节点异常: $e', tag: 'HomeController');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// 提取选中节点信息的辅助方法
|
||||
Map<String, dynamic>? _kr_extractSelectedNodeInfo(dynamic group, String countryCode) {
|
||||
try {
|
||||
// 获取当前选中的节点
|
||||
final selectedNode = group.selected;
|
||||
KRLogUtil.kr_i('🎯 当前选中节点: $selectedNode', tag: 'HomeController');
|
||||
KRLogUtil.kr_i('📋 组内项目数量: ${group.items.length}', tag: 'HomeController');
|
||||
|
||||
if (selectedNode != null && selectedNode.isNotEmpty) {
|
||||
KRLogUtil.kr_i('✅ 选中节点有效,开始查找节点详情', tag: 'HomeController');
|
||||
|
||||
// 打印所有节点信息用于调试
|
||||
for (var item in group.items) {
|
||||
KRLogUtil.kr_i('📄 组内节点: tag=${item.tag}, delay=${item.urlTestDelay}', tag: 'HomeController');
|
||||
}
|
||||
|
||||
// 在组内查找选中的节点
|
||||
try {
|
||||
final selectedItem = group.items.firstWhere(
|
||||
(item) => item.tag == selectedNode,
|
||||
);
|
||||
|
||||
KRLogUtil.kr_i('🎉 成功找到选中节点: tag=${selectedItem.tag}, delay=${selectedItem.urlTestDelay}', tag: 'HomeController');
|
||||
|
||||
return {
|
||||
'tag': selectedNode,
|
||||
'delay': selectedItem.urlTestDelay,
|
||||
'country': countryCode,
|
||||
};
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ 在组内未找到选中节点: $selectedNode', tag: 'HomeController');
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
KRLogUtil.kr_w('⚠️ 选中节点为空或无效', tag: 'HomeController');
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('💥 提取选中节点信息异常: $e', tag: 'HomeController');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取全局 auto 组的当前选中子节点信息
|
||||
Map<String, dynamic>? kr_getGlobalAutoSelectedNode() {
|
||||
try {
|
||||
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
|
||||
|
||||
KRLogUtil.kr_i('🌍 开始获取全局auto选中节点信息', tag: 'HomeController');
|
||||
KRLogUtil.kr_i('📊 活跃组数量: ${activeGroups.length}', tag: 'HomeController');
|
||||
|
||||
for (var group in activeGroups) {
|
||||
KRLogUtil.kr_i('🔄 检查全局组: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'HomeController');
|
||||
KRLogUtil.kr_i('📋 全局组内项目数量: ${group.items.length}', tag: 'HomeController');
|
||||
|
||||
if (group.tag == 'auto' && group.type == ProxyType.urltest) {
|
||||
KRLogUtil.kr_i('✅ 找到全局auto urltest组', tag: 'HomeController');
|
||||
|
||||
// 获取当前选中的节点
|
||||
final selectedNode = group.selected;
|
||||
KRLogUtil.kr_i('🎯 全局auto当前选中节点: $selectedNode', tag: 'HomeController');
|
||||
|
||||
if (selectedNode != null && selectedNode.isNotEmpty) {
|
||||
KRLogUtil.kr_i('✅ 全局auto选中节点有效,开始查找详情', tag: 'HomeController');
|
||||
|
||||
// 打印所有节点信息用于调试
|
||||
for (var item in group.items) {
|
||||
KRLogUtil.kr_i('📄 全局auto组内节点: tag=${item.tag}, delay=${item.urlTestDelay}', tag: 'HomeController');
|
||||
}
|
||||
|
||||
// 在组内查找选中的节点
|
||||
try {
|
||||
final selectedItem = group.items.firstWhere(
|
||||
(item) => item.tag == selectedNode,
|
||||
);
|
||||
|
||||
KRLogUtil.kr_i('🎉 成功找到全局auto选中节点: tag=${selectedItem.tag}, delay=${selectedItem.urlTestDelay}', tag: 'HomeController');
|
||||
|
||||
return {
|
||||
'tag': selectedNode,
|
||||
'delay': selectedItem.urlTestDelay,
|
||||
'country': '',
|
||||
};
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ 在全局auto组内未找到选中节点: $selectedNode', tag: 'HomeController');
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
KRLogUtil.kr_w('⚠️ 全局auto选中节点为空或无效', tag: 'HomeController');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
KRLogUtil.kr_w('❌ 未找到全局auto组', tag: 'HomeController');
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('💥 获取全局auto选中节点异常: $e', tag: 'HomeController');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 获取指定国家的所有真实节点延迟列表
|
||||
List<Map<String, dynamic>> kr_getCountryRealNodeDelays(String countryCode) {
|
||||
final delays = <Map<String, dynamic>>[];
|
||||
|
||||
try {
|
||||
// 从订阅服务中获取该国家的节点列表
|
||||
final countryNodes = kr_subscribeService.keyList.values
|
||||
.where((item) => item.country == countryCode && !item.tag.endsWith('-auto'))
|
||||
.toList();
|
||||
|
||||
for (final node in countryNodes) {
|
||||
delays.add({
|
||||
'tag': node.tag,
|
||||
'delay': node.urlTestDelay.value,
|
||||
'city': node.city,
|
||||
});
|
||||
}
|
||||
|
||||
// 按延迟排序
|
||||
delays.sort((a, b) => a['delay'].compareTo(b['delay']));
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('获取国家真实节点延迟列表失败: $e', tag: 'HomeController');
|
||||
}
|
||||
|
||||
return delays;
|
||||
}
|
||||
|
||||
/// 尝试从活动组更新延迟值
|
||||
bool _kr_tryUpdateDelayFromActiveGroups() {
|
||||
try {
|
||||
@@ -2189,7 +2433,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
|
||||
KRLogUtil.kr_d('找到 selector 组: ${group.tag}, 选中: ${group.selected}', tag: 'HomeController');
|
||||
|
||||
// 如果是auto模式,从urltest组获取延迟
|
||||
if (kr_cutTag.value == "auto") {
|
||||
if (kr_cutTag.value.endsWith('auto')) {
|
||||
for (var item in group.items) {
|
||||
if (item.tag == "auto" && item.urlTestDelay != 0) {
|
||||
kr_currentNodeLatency.value = item.urlTestDelay;
|
||||
|
||||
@@ -305,19 +305,27 @@ class KRLoginController extends GetxController
|
||||
|
||||
/// 发送验证码(仅支持邮箱)
|
||||
void kr_sendCode() async {
|
||||
final either = await KRAuthApi().kr_sendCode(
|
||||
accountController.text,
|
||||
2
|
||||
); // 重置密码验证码类型为3
|
||||
/*
|
||||
*
|
||||
* kr_loginStatus.value == KRLoginProgressStatus.kr_registerSendCode
|
||||
? 2 // 注册验证码类型为2
|
||||
: 3*/
|
||||
if (accountController.text.isEmpty) {
|
||||
KRCommonUtil.kr_showToast(AppTranslations.kr_login.enterAccount);
|
||||
return;
|
||||
}
|
||||
|
||||
int type;
|
||||
final check = await KRAuthApi().kr_isRegister(accountController.text);
|
||||
final result = check.fold((l) {
|
||||
KRCommonUtil.kr_showToast(l.msg);
|
||||
return null;
|
||||
}, (isRegistered) {
|
||||
print('isRegistered $isRegistered');
|
||||
return isRegistered ? 2 : 1;
|
||||
});
|
||||
if (result == null) return;
|
||||
type = result;
|
||||
|
||||
final either = await KRAuthApi().kr_sendCode(accountController.text, type);
|
||||
either.fold((l) {
|
||||
KRCommonUtil.kr_showToast(l.msg);
|
||||
}, (r) async {
|
||||
/// 开始倒计时
|
||||
_startCountdown();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -76,10 +76,13 @@ class KRLoginView extends GetView<KRLoginController> {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Obx(() {
|
||||
final _ = KRAppRunData.getInstance().kr_isLogin.value;
|
||||
final email = KRAppRunData.getInstance().kr_account.value ?? '';
|
||||
final account = KRAppRunData.getInstance().kr_account.value;
|
||||
final isDeviceLogin = account != null && account.startsWith('9000');
|
||||
|
||||
if (isDeviceLogin) return const SizedBox();
|
||||
|
||||
return Text(
|
||||
email.isNotEmpty ? email : '',
|
||||
account ?? '',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20.sp,
|
||||
|
||||
@@ -285,7 +285,7 @@ class KRSplashController extends GetxController {
|
||||
KRLogUtil.kr_e('⏱️ 初始化超时: $e', tag: 'SplashController');
|
||||
print('⏱️ 初始化超时,直接跳转到主页');
|
||||
// 超时后直接跳转到主页,让用户可以手动重试
|
||||
// Get.offAllNamed(Routes.KR_MAIN);
|
||||
// Get.offAllNamed(Routes.KR_HOME);
|
||||
HIDialog.show(
|
||||
message: '初始化超时,请检查网络或重试',
|
||||
confirmText: '重试',
|
||||
@@ -484,7 +484,7 @@ class KRSplashController extends GetxController {
|
||||
_initLog.logWarning('网络权限检查失败或超时,执行降级初始化', tag: 'Init');
|
||||
KRLogUtil.kr_w('⚠️ 网络权限检查失败或超时,执行降级初始化', tag: 'SplashController');
|
||||
await _executeMinimalInitialization();
|
||||
Get.offAllNamed(Routes.KR_MAIN);
|
||||
Get.offAllNamed(Routes.KR_HOME);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -493,7 +493,7 @@ class KRSplashController extends GetxController {
|
||||
_initLog.logError('网络权限检查异常,执行降级初始化', tag: 'Init', error: e);
|
||||
KRLogUtil.kr_w('⚠️ 网络权限检查异常: $e,执行降级初始化', tag: 'SplashController');
|
||||
await _executeMinimalInitialization();
|
||||
Get.offAllNamed(Routes.KR_MAIN);
|
||||
Get.offAllNamed(Routes.KR_HOME);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -582,7 +582,7 @@ class KRSplashController extends GetxController {
|
||||
_initLog.logError('启动页初始化异常,执行降级策略', tag: 'Continue', error: e);
|
||||
KRLogUtil.kr_w('⚠️ 启动页初始化异常,执行降级策略: $e', tag: 'SplashController');
|
||||
await _executeMinimalInitialization();
|
||||
Get.offAllNamed(Routes.KR_MAIN);
|
||||
Get.offAllNamed(Routes.KR_HOME);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -726,7 +726,7 @@ class KRSplashController extends GetxController {
|
||||
kr_isLoading.value = false;
|
||||
|
||||
// 直接跳转到主页
|
||||
Get.offAllNamed(Routes.KR_MAIN);
|
||||
Get.offAllNamed(Routes.KR_HOME);
|
||||
}
|
||||
|
||||
/// 🔧 修复1.1:清理旧的本地存储数据(DEBUG模式专用)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/// 接口名称
|
||||
abstract class Api {
|
||||
/// 游客登录查看是否已经注册
|
||||
static const String kr_isRegister = "/v1/app/auth/check";
|
||||
static const String kr_isRegister = "/v1/auth/check";
|
||||
/// 判断邮箱和当前设备是否已存在订阅
|
||||
static const String kr_checkSubscription = "/v1/public/user/subscribe_status";
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class KRAuthApi {
|
||||
|
||||
BaseResponse<KRIsRegister> baseResponse = await HttpUtil.getInstance()
|
||||
.request<KRIsRegister>(Api.kr_isRegister, data,
|
||||
method: HttpMethod.POST, isShowLoading: true);
|
||||
method: HttpMethod.GET, isShowLoading: true);
|
||||
|
||||
if (!baseResponse.isSuccess) {
|
||||
return left(
|
||||
@@ -298,7 +298,7 @@ class KRAuthApi {
|
||||
|
||||
BaseResponse<dynamic> baseResponse = await HttpUtil.getInstance()
|
||||
.request<dynamic>(Api.kr_deleteAccount, data,
|
||||
method: HttpMethod.DELETE, isShowLoading: true);
|
||||
method: HttpMethod.POST, isShowLoading: true);
|
||||
if (!baseResponse.isSuccess) {
|
||||
return left(
|
||||
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
|
||||
|
||||
@@ -309,6 +309,7 @@ class KRSubscribeService {
|
||||
|
||||
// 保存配置
|
||||
KRSingBoxImp.instance.kr_saveOutbounds(listModel.configJsonList);
|
||||
KRSingBoxImp.instance.kr_saveAllOutbounds(listModel.countryOutboundList);
|
||||
|
||||
// 更新试用和订阅状态
|
||||
_kr_updateSubscribeStatus();
|
||||
@@ -661,6 +662,8 @@ class KRSubscribeService {
|
||||
|
||||
// 保存配置
|
||||
KRSingBoxImp.instance.kr_saveOutbounds(listModel.configJsonList);
|
||||
KRSingBoxImp.instance.kr_saveAllOutbounds(listModel.countryOutboundList);
|
||||
|
||||
// 更新试用和订阅状态
|
||||
_kr_updateSubscribeStatus();
|
||||
|
||||
@@ -722,6 +725,8 @@ class KRSubscribeService {
|
||||
|
||||
// 保存配置
|
||||
KRSingBoxImp.instance.kr_saveOutbounds([]);
|
||||
KRSingBoxImp.instance.kr_saveAllOutbounds([]);
|
||||
|
||||
}
|
||||
|
||||
/// 获取当前订阅
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,8 @@ enum KRCountry {
|
||||
ru('俄罗斯'),
|
||||
id('印度尼西亚'),
|
||||
tr('土耳其'),
|
||||
br('巴西');
|
||||
br('巴西'),
|
||||
other('全局代理'); // ✅ 新增:全局代理模式(所有流量走代理,不添加国家直连规则)
|
||||
|
||||
final String kr_name;
|
||||
const KRCountry(this.kr_name);
|
||||
@@ -30,7 +31,7 @@ enum KRCountry {
|
||||
static KRCountry? kr_fromCode(String code) {
|
||||
try {
|
||||
return KRCountry.values.firstWhere(
|
||||
(country) => country.kr_code == code.toLowerCase(),
|
||||
(country) => country.kr_code == code.toLowerCase(),
|
||||
);
|
||||
} catch (e) {
|
||||
return null;
|
||||
@@ -53,7 +54,7 @@ class KRCountryUtil {
|
||||
static Future<void> kr_init() async {
|
||||
try {
|
||||
final String? kr_savedCountry =
|
||||
await _kr_storage.kr_readData(key: _kr_countryKey);
|
||||
await _kr_storage.kr_readData(key: _kr_countryKey);
|
||||
if (kr_savedCountry != null) {
|
||||
final KRCountry? kr_country = KRCountry.kr_fromCode(kr_savedCountry);
|
||||
if (kr_country != null) {
|
||||
@@ -66,7 +67,7 @@ class KRCountryUtil {
|
||||
} else {
|
||||
kr_currentCountry.value = KRCountry.cn;
|
||||
}
|
||||
|
||||
|
||||
} catch (err) {
|
||||
KRLogUtil.kr_e('初始化国家设置失败: $err', tag: 'CountryUtil');
|
||||
kr_currentCountry.value = KRCountry.cn;
|
||||
@@ -106,9 +107,9 @@ class KRCountryUtil {
|
||||
return kr_currentCountry.value.kr_code;
|
||||
}
|
||||
|
||||
static String kr_getCurrentCountryName() {
|
||||
static String kr_getCurrentCountryName() {
|
||||
return kr_getCountryName(kr_currentCountry.value);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// 获取国家名称
|
||||
@@ -131,12 +132,14 @@ class KRCountryUtil {
|
||||
return AppTranslations.kr_country.tr;
|
||||
case KRCountry.br:
|
||||
return AppTranslations.kr_country.br;
|
||||
case KRCountry.other:
|
||||
return '全局代理'; // ✅ 新增:全局代理
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取所有支持的国家列表
|
||||
static List<KRCountry> kr_getSupportedCountries() {
|
||||
if (AppConfig().kr_is_daytime == false) {
|
||||
if (AppConfig().kr_is_daytime == false) {
|
||||
return KRCountry.values.where((element) => element != KRCountry.cn).toList();
|
||||
}
|
||||
return KRCountry.values;
|
||||
@@ -146,9 +149,9 @@ class KRCountryUtil {
|
||||
static List<Map<String, String>> kr_getCountryInfoList() {
|
||||
return KRCountry.values
|
||||
.map((country) => {
|
||||
'code': country.kr_code,
|
||||
'name': country.kr_countryName,
|
||||
})
|
||||
'code': country.kr_code,
|
||||
'name': country.kr_countryName,
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,127 +1,151 @@
|
||||
import 'dart:io';
|
||||
import 'dart:async';
|
||||
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
||||
import 'kr_log_util.dart';
|
||||
|
||||
/// 延迟测试工具类
|
||||
/// 提供真实的 TCP 连接延迟测试功能
|
||||
/// 真正的节点延迟测试工具
|
||||
class KRLatencyTester {
|
||||
/// 测试单个节点的延迟
|
||||
///
|
||||
/// 参数:
|
||||
/// - host: 主机地址
|
||||
/// - port: 端口号
|
||||
/// - timeout: 超时时间(毫秒)
|
||||
///
|
||||
/// 返回:
|
||||
/// - 延迟时间(毫秒),如果失败返回 65535
|
||||
static Future<int> testNode({
|
||||
/// TCP 连接测试延迟(真实测试)
|
||||
/// 返回延迟毫秒数,失败返回 65535
|
||||
static Future<int> testTcpLatency({
|
||||
required String host,
|
||||
required int port,
|
||||
int timeout = 5000,
|
||||
Duration timeout = const Duration(seconds: 5),
|
||||
}) async {
|
||||
Socket? socket;
|
||||
final stopwatch = Stopwatch();
|
||||
|
||||
try {
|
||||
final stopwatch = Stopwatch()..start();
|
||||
|
||||
final socket = await Socket.connect(
|
||||
KRLogUtil.kr_i('🔌 开始测试: $host:$port', tag: 'LatencyTester');
|
||||
|
||||
stopwatch.start();
|
||||
|
||||
// 尝试 TCP 连接
|
||||
socket = await Socket.connect(
|
||||
host,
|
||||
port,
|
||||
timeout: Duration(milliseconds: timeout),
|
||||
).timeout(Duration(milliseconds: timeout));
|
||||
|
||||
timeout: timeout,
|
||||
);
|
||||
|
||||
stopwatch.stop();
|
||||
|
||||
// 立即关闭连接
|
||||
await socket.close();
|
||||
socket.destroy();
|
||||
|
||||
|
||||
final latency = stopwatch.elapsedMilliseconds;
|
||||
KRLogUtil.kr_i('✅ 延迟测试成功: $host:$port = ${latency}ms', tag: 'KRLatencyTester');
|
||||
|
||||
|
||||
KRLogUtil.kr_i('✅ 测试成功: $host:$port - ${latency}ms', tag: 'LatencyTester');
|
||||
|
||||
return latency;
|
||||
|
||||
} on SocketException catch (e) {
|
||||
stopwatch.stop();
|
||||
KRLogUtil.kr_w('❌ 连接失败: $host:$port - ${e.message}', tag: 'LatencyTester');
|
||||
return 65535;
|
||||
|
||||
} on TimeoutException catch (e) {
|
||||
stopwatch.stop();
|
||||
KRLogUtil.kr_w('⏱️ 连接超时: $host:$port - $e', tag: 'LatencyTester');
|
||||
return 65535;
|
||||
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_w('❌ 延迟测试失败: $host:$port - $e', tag: 'KRLatencyTester');
|
||||
return 65535; // 测试失败返回最大值
|
||||
stopwatch.stop();
|
||||
KRLogUtil.kr_e('❌ 测试异常: $host:$port - $e', tag: 'LatencyTester');
|
||||
return 65535;
|
||||
|
||||
} finally {
|
||||
// 确保关闭连接
|
||||
try {
|
||||
await socket?.close();
|
||||
} catch (e) {
|
||||
// 忽略关闭错误
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 批量测试多个节点的延迟
|
||||
///
|
||||
/// 参数:
|
||||
/// - nodes: 节点列表,格式为 [{"host": "example.com", "port": 443}]
|
||||
/// - concurrency: 并发数量
|
||||
/// - timeout: 超时时间(毫秒)
|
||||
///
|
||||
/// 返回:
|
||||
/// - 测试结果映射,键为 "host:port",值为延迟时间
|
||||
/// 批量测试多个节点延迟(并发测试)
|
||||
/// 返回 Map<节点tag, 延迟ms>
|
||||
static Future<Map<String, int>> testMultipleNodes({
|
||||
required List<MapEntry<String, SocketAddress>> nodes,
|
||||
int concurrency = 10,
|
||||
int timeout = 5000,
|
||||
int concurrency = 10, // 并发数
|
||||
Duration timeout = const Duration(seconds: 5),
|
||||
}) async {
|
||||
final results = <String, int>{};
|
||||
final semaphore = Completer<void>();
|
||||
var activeCount = 0;
|
||||
var completedCount = 0;
|
||||
final List<List<MapEntry<String, SocketAddress>>> batches = [];
|
||||
|
||||
KRLogUtil.kr_i('🚀 开始批量延迟测试,共 ${nodes.length} 个节点,并发数: $concurrency', tag: 'KRLatencyTester');
|
||||
// 分批处理
|
||||
for (int i = 0; i < nodes.length; i += concurrency) {
|
||||
batches.add(
|
||||
nodes.sublist(i, i + concurrency > nodes.length ? nodes.length : i + concurrency)
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> processNode(MapEntry<String, SocketAddress> node) async {
|
||||
try {
|
||||
final host = node.value.address;
|
||||
final port = node.value.port;
|
||||
final key = node.key;
|
||||
|
||||
final latency = await testNode(
|
||||
host: host,
|
||||
port: port,
|
||||
KRLogUtil.kr_i('📊 开始批量测试: ${nodes.length} 个节点,分 ${batches.length} 批,每批 $concurrency 个', tag: 'LatencyTester');
|
||||
|
||||
int completedCount = 0;
|
||||
|
||||
// 逐批测试
|
||||
for (int batchIndex = 0; batchIndex < batches.length; batchIndex++) {
|
||||
final batch = batches[batchIndex];
|
||||
|
||||
KRLogUtil.kr_i('📦 测试第 ${batchIndex + 1}/${batches.length} 批(${batch.length} 个节点)', tag: 'LatencyTester');
|
||||
|
||||
// 并发测试当前批次
|
||||
final futures = batch.map((node) async {
|
||||
final tag = node.key;
|
||||
final address = node.value;
|
||||
|
||||
final latency = await testTcpLatency(
|
||||
host: address.host,
|
||||
port: address.port,
|
||||
timeout: timeout,
|
||||
);
|
||||
|
||||
results[key] = latency;
|
||||
} catch (e) {
|
||||
results[node.key] = 65535;
|
||||
KRLogUtil.kr_e('❌ 节点测试异常: ${node.key} - $e', tag: 'KRLatencyTester');
|
||||
} finally {
|
||||
|
||||
completedCount++;
|
||||
activeCount--;
|
||||
|
||||
if (completedCount >= nodes.length) {
|
||||
semaphore.complete();
|
||||
|
||||
if (completedCount % 5 == 0 || completedCount == nodes.length) {
|
||||
KRLogUtil.kr_i('📈 测试进度: $completedCount/${nodes.length}', tag: 'LatencyTester');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 分批处理节点
|
||||
for (var i = 0; i < nodes.length; i += concurrency) {
|
||||
final batch = nodes.skip(i).take(concurrency);
|
||||
|
||||
for (final node in batch) {
|
||||
activeCount++;
|
||||
processNode(node);
|
||||
}
|
||||
|
||||
return MapEntry(tag, latency);
|
||||
}).toList();
|
||||
|
||||
// 等待当前批次完成
|
||||
if (i + concurrency < nodes.length) {
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
final batchResults = await Future.wait(futures);
|
||||
|
||||
// 收集结果
|
||||
for (final result in batchResults) {
|
||||
results[result.key] = result.value;
|
||||
}
|
||||
}
|
||||
|
||||
// 统计结果
|
||||
final successCount = results.values.where((latency) => latency < 65535).length;
|
||||
final failCount = results.length - successCount;
|
||||
|
||||
KRLogUtil.kr_i('✅ 批量测试完成', tag: 'LatencyTester');
|
||||
KRLogUtil.kr_i('📊 成功: $successCount, 失败: $failCount', tag: 'LatencyTester');
|
||||
|
||||
// 显示延迟最低的前3个
|
||||
final successNodes = results.entries
|
||||
.where((e) => e.value < 65535)
|
||||
.toList()
|
||||
..sort((a, b) => a.value.compareTo(b.value));
|
||||
|
||||
if (successNodes.isNotEmpty) {
|
||||
KRLogUtil.kr_i('🏆 延迟最低的前3个节点:', tag: 'LatencyTester');
|
||||
for (int i = 0; i < 3 && i < successNodes.length; i++) {
|
||||
KRLogUtil.kr_i(' ${i + 1}. ${successNodes[i].key}: ${successNodes[i].value}ms', tag: 'LatencyTester');
|
||||
}
|
||||
}
|
||||
|
||||
// 等待所有任务完成
|
||||
await semaphore.future;
|
||||
|
||||
KRLogUtil.kr_i('✅ 批量延迟测试完成,成功: ${results.length} 个', tag: 'KRLatencyTester');
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
/// Socket 地址类
|
||||
/// 表示网络地址和端口的组合
|
||||
/// 节点地址信息
|
||||
class SocketAddress {
|
||||
final String address;
|
||||
final String host;
|
||||
final int port;
|
||||
|
||||
SocketAddress(this.address, this.port);
|
||||
SocketAddress(this.host, this.port);
|
||||
|
||||
@override
|
||||
String toString() => '$address:$port';
|
||||
}
|
||||
String toString() => '$host:$port';
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ class KRWindowManager with WindowListener, TrayListener {
|
||||
|
||||
const WindowOptions windowOptions = WindowOptions(
|
||||
size: Size(800, 668),
|
||||
minimumSize: Size(800, 668),
|
||||
minimumSize: Size(400, 334),
|
||||
center: true,
|
||||
backgroundColor: Colors.white,
|
||||
skipTaskbar: false,
|
||||
@@ -47,7 +47,7 @@ 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(800, 668));
|
||||
await windowManager.setMinimumSize(const Size(400, 334));
|
||||
await windowManager.center();
|
||||
await windowManager.show();
|
||||
// 阻止窗口关闭
|
||||
@@ -55,7 +55,7 @@ class KRWindowManager with WindowListener, TrayListener {
|
||||
} else {
|
||||
await windowManager.setTitle('Kaer VPN');
|
||||
await windowManager.setSize(const Size(800, 668));
|
||||
await windowManager.setMinimumSize(const Size(800, 668));
|
||||
await windowManager.setMinimumSize(const Size(400, 334));
|
||||
await windowManager.center();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,439 @@
|
||||
import 'dart:io';
|
||||
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
||||
|
||||
/// Windows DNS 管理工具类
|
||||
///
|
||||
/// 用于在 Windows 平台上管理系统 DNS 设置
|
||||
/// 主要功能:
|
||||
/// 1. 备份原始 DNS 设置
|
||||
/// 2. 恢复 DNS 设置
|
||||
/// 3. 兜底设置为国内公共 DNS (223.5.5.5 和 114.114.114.114)
|
||||
class KRWindowsDnsUtil {
|
||||
/// 私有构造函数
|
||||
KRWindowsDnsUtil._();
|
||||
|
||||
/// 单例实例
|
||||
static final KRWindowsDnsUtil _instance = KRWindowsDnsUtil._();
|
||||
|
||||
/// 工厂构造函数
|
||||
factory KRWindowsDnsUtil() => _instance;
|
||||
|
||||
/// 获取实例的静态方法
|
||||
static KRWindowsDnsUtil get instance => _instance;
|
||||
|
||||
/// 原始 DNS 服务器地址(连接前备份)
|
||||
List<String>? _originalDnsServers;
|
||||
|
||||
/// 主网络接口名称
|
||||
String? _primaryInterfaceName;
|
||||
|
||||
/// 备份当前 DNS 设置
|
||||
///
|
||||
/// 在连接 VPN 之前调用,保存原始 DNS 配置
|
||||
/// 返回:true-成功,false-失败
|
||||
Future<bool> kr_backupDnsSettings() async {
|
||||
if (!Platform.isWindows) {
|
||||
KRLogUtil.kr_w('❌ 非 Windows 平台,跳过 DNS 备份', tag: 'WindowsDNS');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
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');
|
||||
|
||||
// 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');
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ 备份 DNS 设置失败: $e', tag: 'WindowsDNS');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 恢复原始 DNS 设置
|
||||
///
|
||||
/// 在断开 VPN 后调用,恢复备份的 DNS 配置
|
||||
/// 如果恢复失败,会自动调用兜底机制设置为公共DNS
|
||||
/// 返回:true-成功,false-失败
|
||||
Future<bool> kr_restoreDnsSettings() async {
|
||||
if (!Platform.isWindows) {
|
||||
KRLogUtil.kr_w('❌ 非 Windows 平台,跳过 DNS 恢复', tag: 'WindowsDNS');
|
||||
return false;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 恢复原始 DNS
|
||||
if (_originalDnsServers == null) {
|
||||
KRLogUtil.kr_w('⚠️ 没有备份的 DNS,执行兜底恢复', tag: 'WindowsDNS');
|
||||
return await _kr_fallbackRestoreDns();
|
||||
}
|
||||
|
||||
if (_originalDnsServers!.isEmpty) {
|
||||
// 原本是 DHCP 自动获取
|
||||
KRLogUtil.kr_i('🔄 恢复为 DHCP 自动获取 DNS', tag: 'WindowsDNS');
|
||||
final success = await _kr_setDnsToAuto(_primaryInterfaceName!);
|
||||
if (!success) {
|
||||
KRLogUtil.kr_w('⚠️ 恢复 DHCP 失败,执行兜底恢复', tag: 'WindowsDNS');
|
||||
return await _kr_fallbackRestoreDns();
|
||||
}
|
||||
} else {
|
||||
// 恢复指定的 DNS 服务器
|
||||
KRLogUtil.kr_i('🔄 恢复原始 DNS: ${_originalDnsServers!.join(", ")}', tag: 'WindowsDNS');
|
||||
final success = await _kr_setDnsServers(
|
||||
_primaryInterfaceName!,
|
||||
_originalDnsServers!,
|
||||
);
|
||||
if (!success) {
|
||||
KRLogUtil.kr_w('⚠️ 恢复原始 DNS 失败,执行兜底恢复', tag: 'WindowsDNS');
|
||||
return await _kr_fallbackRestoreDns();
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 验证 DNS 是否恢复成功
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
final currentDns = await _kr_getCurrentDnsServers(_primaryInterfaceName!);
|
||||
KRLogUtil.kr_i('✅ 当前 DNS: ${currentDns.join(", ")}', tag: 'WindowsDNS');
|
||||
|
||||
// 4. 额外验证:确认 DNS 不再指向 127.0.0.1(sing-box 的本地 DNS)
|
||||
final hasLocalhost = currentDns.any((dns) => dns.startsWith('127.'));
|
||||
if (hasLocalhost) {
|
||||
KRLogUtil.kr_w('⚠️ DNS 仍包含 127.0.0.1,可能未完全恢复', tag: 'WindowsDNS');
|
||||
KRLogUtil.kr_w('⚠️ 执行兜底恢复', tag: 'WindowsDNS');
|
||||
return await _kr_fallbackRestoreDns();
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ 恢复 DNS 设置失败: $e', tag: 'WindowsDNS');
|
||||
KRLogUtil.kr_w('⚠️ 执行兜底恢复', tag: 'WindowsDNS');
|
||||
return await _kr_fallbackRestoreDns();
|
||||
}
|
||||
}
|
||||
|
||||
/// 兜底恢复:强制设置为国内公共 DNS
|
||||
///
|
||||
/// 当正常恢复失败时,设置为安全的公共 DNS 服务器
|
||||
/// - 主 DNS: 223.5.5.5 (阿里云)
|
||||
/// - 备用 DNS: 114.114.114.114 (114DNS)
|
||||
Future<bool> _kr_fallbackRestoreDns() async {
|
||||
try {
|
||||
KRLogUtil.kr_w('🆘 执行 DNS 兜底恢复机制', tag: 'WindowsDNS');
|
||||
KRLogUtil.kr_i('🔧 设置为国内公共 DNS: 223.5.5.5, 114.114.114.114', tag: 'WindowsDNS');
|
||||
|
||||
// 1. 获取主网络接口(如果还没有)
|
||||
if (_primaryInterfaceName == null) {
|
||||
_primaryInterfaceName = await _kr_getPrimaryNetworkInterface();
|
||||
if (_primaryInterfaceName == null) {
|
||||
KRLogUtil.kr_e('❌ 无法检测网络接口,兜底恢复失败', tag: 'WindowsDNS');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 设置为公共 DNS
|
||||
final fallbackDns = ['223.5.5.5', '114.114.114.114'];
|
||||
final success = await _kr_setDnsServers(_primaryInterfaceName!, fallbackDns);
|
||||
|
||||
if (success) {
|
||||
KRLogUtil.kr_i('✅ 兜底 DNS 设置成功', tag: 'WindowsDNS');
|
||||
|
||||
// 3. 验证设置
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
final currentDns = await _kr_getCurrentDnsServers(_primaryInterfaceName!);
|
||||
KRLogUtil.kr_i('✅ 验证当前 DNS: ${currentDns.join(", ")}', tag: 'WindowsDNS');
|
||||
|
||||
return true;
|
||||
} else {
|
||||
KRLogUtil.kr_e('❌ 兜底 DNS 设置失败', tag: 'WindowsDNS');
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ 兜底恢复失败: $e', tag: 'WindowsDNS');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取主网络接口名称
|
||||
///
|
||||
/// 通过 netsh 命令查找处于"已连接"状态的主网络接口
|
||||
/// 返回:接口名称,失败返回 null
|
||||
Future<String?> _kr_getPrimaryNetworkInterface() async {
|
||||
try {
|
||||
// 使用 netsh 获取接口列表
|
||||
final result = await Process.run('netsh', ['interface', 'show', 'interface']);
|
||||
|
||||
if (result.exitCode != 0) {
|
||||
KRLogUtil.kr_e('❌ 获取网络接口失败: ${result.stderr}', tag: 'WindowsDNS');
|
||||
return null;
|
||||
}
|
||||
|
||||
final output = result.stdout.toString();
|
||||
final lines = output.split('\n');
|
||||
|
||||
// 收集所有已连接的接口
|
||||
final connectedInterfaces = <String>[];
|
||||
|
||||
// 查找所有"已连接"的接口
|
||||
// 支持中文和英文 Windows 系统
|
||||
for (var line in lines) {
|
||||
// 中文: "已连接", 英文: "Connected", "Enabled"
|
||||
if (line.contains('已连接') ||
|
||||
line.contains('Connected') ||
|
||||
line.toLowerCase().contains('enabled')) {
|
||||
// 跳过表头行
|
||||
if (line.contains('Admin') ||
|
||||
line.contains('管理') ||
|
||||
line.contains('State') ||
|
||||
line.contains('状态')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 解析接口名称(最后一列)
|
||||
final parts = line.trim().split(RegExp(r'\s{2,}'));
|
||||
if (parts.length >= 4) {
|
||||
final interfaceName = parts.last.trim();
|
||||
// 排除空接口名
|
||||
if (interfaceName.isNotEmpty && interfaceName.length > 1) {
|
||||
connectedInterfaces.add(interfaceName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (connectedInterfaces.isEmpty) {
|
||||
KRLogUtil.kr_w('⚠️ 未找到已连接的网络接口', tag: 'WindowsDNS');
|
||||
return null;
|
||||
}
|
||||
|
||||
// 🔧 优化:优先选择有线网络(以太网),然后才是 Wi-Fi
|
||||
// 有线网络通常更稳定
|
||||
String? selectedInterface;
|
||||
for (var interface in connectedInterfaces) {
|
||||
final lowerName = interface.toLowerCase();
|
||||
// 优先选择以太网
|
||||
if (lowerName.contains('ethernet') ||
|
||||
lowerName.contains('以太网') ||
|
||||
lowerName.contains('lan') ||
|
||||
lowerName.contains('local')) {
|
||||
selectedInterface = interface;
|
||||
KRLogUtil.kr_i('🔍 选择有线网络接口: $interface', tag: 'WindowsDNS');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有有线网络,选择第一个(通常是 Wi-Fi)
|
||||
selectedInterface ??= connectedInterfaces.first;
|
||||
if (selectedInterface != connectedInterfaces.first) {
|
||||
KRLogUtil.kr_d('🔍 选择网络接口: $selectedInterface', tag: 'WindowsDNS');
|
||||
} else {
|
||||
KRLogUtil.kr_i('🔍 选择网络接口: $selectedInterface', tag: 'WindowsDNS');
|
||||
}
|
||||
|
||||
return selectedInterface;
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ 获取网络接口异常: $e', tag: 'WindowsDNS');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取指定接口的当前 DNS 服务器
|
||||
///
|
||||
/// 参数:
|
||||
/// - interfaceName: 网络接口名称
|
||||
///
|
||||
/// 返回:DNS 服务器列表
|
||||
Future<List<String>> _kr_getCurrentDnsServers(String interfaceName) async {
|
||||
try {
|
||||
final result = await Process.run('netsh', [
|
||||
'interface',
|
||||
'ipv4',
|
||||
'show',
|
||||
'dnsservers',
|
||||
'name="$interfaceName"',
|
||||
]);
|
||||
|
||||
if (result.exitCode != 0) {
|
||||
KRLogUtil.kr_e('❌ 获取 DNS 失败: ${result.stderr}', tag: 'WindowsDNS');
|
||||
return [];
|
||||
}
|
||||
|
||||
final output = result.stdout.toString();
|
||||
final dnsServers = <String>[];
|
||||
|
||||
// 解析 DNS 服务器地址
|
||||
final lines = output.split('\n');
|
||||
for (var line in lines) {
|
||||
// 查找 IP 地址格式的行
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
KRLogUtil.kr_d('🔍 当前 DNS: ${dnsServers.join(", ")}', tag: 'WindowsDNS');
|
||||
return dnsServers;
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ 获取 DNS 异常: $e', tag: 'WindowsDNS');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// 设置指定接口的 DNS 服务器
|
||||
///
|
||||
/// 参数:
|
||||
/// - interfaceName: 网络接口名称
|
||||
/// - dnsServers: DNS 服务器列表
|
||||
///
|
||||
/// 返回:true-成功,false-失败
|
||||
Future<bool> _kr_setDnsServers(String interfaceName, List<String> dnsServers) async {
|
||||
try {
|
||||
if (dnsServers.isEmpty) {
|
||||
KRLogUtil.kr_w('⚠️ DNS 列表为空,无法设置', tag: 'WindowsDNS');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1. 设置主 DNS
|
||||
KRLogUtil.kr_i('🔧 设置主 DNS: ${dnsServers[0]}', tag: 'WindowsDNS');
|
||||
var result = await Process.run('netsh', [
|
||||
'interface',
|
||||
'ipv4',
|
||||
'set',
|
||||
'dnsservers',
|
||||
'name="$interfaceName"',
|
||||
'source=static',
|
||||
'address=${dnsServers[0]}',
|
||||
'validate=no',
|
||||
]);
|
||||
|
||||
if (result.exitCode != 0) {
|
||||
KRLogUtil.kr_e('❌ 设置主 DNS 失败: ${result.stderr}', tag: 'WindowsDNS');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 设置备用 DNS(如果有)
|
||||
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', [
|
||||
'interface',
|
||||
'ipv4',
|
||||
'add',
|
||||
'dnsservers',
|
||||
'name="$interfaceName"',
|
||||
'address=${dnsServers[i]}',
|
||||
'index=${i + 1}',
|
||||
'validate=no',
|
||||
]);
|
||||
|
||||
if (result.exitCode != 0) {
|
||||
KRLogUtil.kr_w('⚠️ 设置备用 DNS $i 失败: ${result.stderr}', tag: 'WindowsDNS');
|
||||
// 继续设置下一个,不中断
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 刷新 DNS 缓存
|
||||
await _kr_flushDnsCache();
|
||||
|
||||
KRLogUtil.kr_i('✅ DNS 服务器设置完成', tag: 'WindowsDNS');
|
||||
return true;
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ 设置 DNS 异常: $e', tag: 'WindowsDNS');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 设置 DNS 为自动获取(DHCP)
|
||||
///
|
||||
/// 参数:
|
||||
/// - interfaceName: 网络接口名称
|
||||
///
|
||||
/// 返回:true-成功,false-失败
|
||||
Future<bool> _kr_setDnsToAuto(String interfaceName) async {
|
||||
try {
|
||||
KRLogUtil.kr_i('🔧 设置 DNS 为自动获取 (DHCP)', tag: 'WindowsDNS');
|
||||
|
||||
final result = await Process.run('netsh', [
|
||||
'interface',
|
||||
'ipv4',
|
||||
'set',
|
||||
'dnsservers',
|
||||
'name="$interfaceName"',
|
||||
'source=dhcp',
|
||||
]);
|
||||
|
||||
if (result.exitCode != 0) {
|
||||
KRLogUtil.kr_e('❌ 设置 DHCP 失败: ${result.stderr}', tag: 'WindowsDNS');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 刷新 DNS 缓存
|
||||
await _kr_flushDnsCache();
|
||||
|
||||
KRLogUtil.kr_i('✅ DNS 已设置为自动获取', tag: 'WindowsDNS');
|
||||
return true;
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ 设置 DHCP 异常: $e', tag: 'WindowsDNS');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 刷新 DNS 缓存
|
||||
///
|
||||
/// 执行 ipconfig /flushdns 命令清空 DNS 解析缓存
|
||||
Future<void> _kr_flushDnsCache() async {
|
||||
try {
|
||||
KRLogUtil.kr_i('🔄 刷新 DNS 缓存...', tag: 'WindowsDNS');
|
||||
|
||||
final result = await Process.run('ipconfig', ['/flushdns']);
|
||||
|
||||
if (result.exitCode == 0) {
|
||||
KRLogUtil.kr_i('✅ DNS 缓存已刷新', tag: 'WindowsDNS');
|
||||
} else {
|
||||
KRLogUtil.kr_w('⚠️ 刷新 DNS 缓存失败: ${result.stderr}', tag: 'WindowsDNS');
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_w('⚠️ 刷新 DNS 缓存异常: $e', tag: 'WindowsDNS');
|
||||
}
|
||||
}
|
||||
|
||||
/// 清除备份数据
|
||||
///
|
||||
/// 在应用退出或不需要时调用
|
||||
void kr_clearBackup() {
|
||||
_originalDnsServers = null;
|
||||
_primaryInterfaceName = null;
|
||||
KRLogUtil.kr_d('🗑️ 已清除 DNS 备份数据', tag: 'WindowsDNS');
|
||||
}
|
||||
}
|
||||
@@ -571,10 +571,11 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = NJRRF427XB;
|
||||
ENABLE_HARDENED_RUNTIME = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN;
|
||||
@@ -583,7 +584,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.hi.com;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.taw.hifastvpn.mac;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
@@ -704,10 +705,11 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = NJRRF427XB;
|
||||
ENABLE_HARDENED_RUNTIME = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN;
|
||||
@@ -716,7 +718,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.hi.com;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.taw.hifastvpn.mac;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -731,10 +733,11 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = NJRRF427XB;
|
||||
ENABLE_HARDENED_RUNTIME = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN;
|
||||
@@ -743,7 +746,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.hi.com;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.taw.hifastvpn.mac;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
PRODUCT_NAME = HiFastVPN
|
||||
|
||||
// The application's bundle identifier
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.hi.com
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.taw.hifastvpn.mac
|
||||
|
||||
// The copyright displayed in application information
|
||||
PRODUCT_COPYRIGHT = Copyright © 2025 HiFastVPN.com. All rights reserved.
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
// 用于发布版本的签名设置
|
||||
|
||||
// 使用 Developer ID Application 证书进行签名(用于 App Store 外分发)
|
||||
CODE_SIGN_IDENTITY = Developer ID Application: Civil Rights Corps (3UR892FAP3)
|
||||
CODE_SIGN_IDENTITY = Developer ID Application: Civil Rights Corps (NJRRF427XB)
|
||||
|
||||
// 开发团队 ID
|
||||
DEVELOPMENT_TEAM = 3UR892FAP3
|
||||
DEVELOPMENT_TEAM = NJRRF427XB
|
||||
|
||||
// 启用代码签名
|
||||
CODE_SIGN_STYLE = Manual
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 0.0.1+1
|
||||
version: 0.0.1
|
||||
|
||||
environment:
|
||||
sdk: '>=3.5.0 <4.0.0'
|
||||
|
||||
@@ -126,6 +126,11 @@ install(FILES "../libcore/bin/HiFastVPNCli.exe"
|
||||
COMPONENT Runtime
|
||||
OPTIONAL)
|
||||
|
||||
install(FILES "runner/resources/app_icon.ico"
|
||||
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
|
||||
COMPONENT Runtime
|
||||
OPTIONAL)
|
||||
|
||||
|
||||
if(PLUGIN_BUNDLED_LIBRARIES)
|
||||
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
|
||||
|
||||
@@ -7,6 +7,7 @@ echo 正在复制必要的 DLL 文件...
|
||||
copy "%VC_REDIST_DIR%\msvcp140.dll" "%DLL_DIR%"
|
||||
copy "%VC_REDIST_DIR%\vcruntime140.dll" "%DLL_DIR%"
|
||||
copy "%VC_REDIST_DIR%\vcruntime140_1.dll" "%DLL_DIR%"
|
||||
copy "%~dp0..\libcore\libcore.dll" "%DLL_DIR%"
|
||||
|
||||
echo DLL 文件复制完成!
|
||||
pause
|
||||
pause
|
||||
@@ -14,6 +14,8 @@ Compression=lzma
|
||||
SolidCompression=yes
|
||||
SetupIconFile={{SETUP_ICON_FILE}}
|
||||
WizardStyle=modern
|
||||
LanguageDetectionMethod=uilanguage
|
||||
UsePreviousTasks=no
|
||||
PrivilegesRequired={{PRIVILEGES_REQUIRED}}
|
||||
ArchitecturesAllowed=x64
|
||||
ArchitecturesInstallIn64BitMode=x64
|
||||
@@ -26,6 +28,8 @@ CloseApplications=force
|
||||
{% if locale == 'bg' %}Name: "bulgarian"; MessagesFile: "compiler:Languages\\Bulgarian.isl"{% endif %}
|
||||
{% if locale == 'ca' %}Name: "catalan"; MessagesFile: "compiler:Languages\\Catalan.isl"{% endif %}
|
||||
{% if locale == 'zh' %}Name: "chinesesimplified"; MessagesFile: "compiler:Languages\\ChineseSimplified.isl"{% endif %}
|
||||
{% if locale == 'zh-cn' %}Name: "chinesesimplified"; MessagesFile: "compiler:Languages\\ChineseSimplified.isl"{% endif %}
|
||||
{% if locale == 'zh_CN' %}Name: "chinesesimplified"; MessagesFile: "compiler:Languages\\ChineseSimplified.isl"{% endif %}
|
||||
{% if locale == 'co' %}Name: "corsican"; MessagesFile: "compiler:Languages\\Corsican.isl"{% endif %}
|
||||
{% if locale == 'cs' %}Name: "czech"; MessagesFile: "compiler:Languages\\Czech.isl"{% endif %}
|
||||
{% if locale == 'da' %}Name: "danish"; MessagesFile: "compiler:Languages\\Danish.isl"{% endif %}
|
||||
@@ -50,7 +54,7 @@ CloseApplications=force
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkedonce
|
||||
Name: "launchAtStartup"; Description: "{cm:AutoStartProgram,{{DISPLAY_NAME}}}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: {% if LAUNCH_AT_STARTUP != true %}unchecked{% else %}checkedonce{% endif %}
|
||||
Name: "launchAtStartup"; Description: "{cm:AutoStartProgram,{{DISPLAY_NAME}}}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkedonce
|
||||
[Files]
|
||||
Source: "{{SOURCE_DIR}}\\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||
|
||||
@@ -6,8 +6,11 @@ executable_name: HiFastVPN.exe
|
||||
output_base_file_name: HiFastVPN.exe
|
||||
create_desktop_icon: true
|
||||
install_dir_name: "{autopf64}\\HiFastVPN"
|
||||
setup_icon_file: ..\..\windows\runner\resources\app_icon.ico
|
||||
setup_icon_file: C:\Users\xprotocal\Downloads\hi-client\windows\runner\resources\app_icon.ico
|
||||
locales:
|
||||
- zh
|
||||
- zh-cn
|
||||
- zh_CN
|
||||
- ar
|
||||
- en
|
||||
- fa
|
||||
@@ -15,3 +18,4 @@ locales:
|
||||
- pt
|
||||
- tr
|
||||
script_template: inno_setup.sas
|
||||
languages_dir: compiler:Languages
|
||||
|
||||
Executable → Regular
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 5.3 KiB |
Reference in New Issue
Block a user