Compare commits

51 Commits

Author SHA1 Message Date
sepakeloudest 1b1b38aba5 feat: 修复auto情况和连接中... 2025-11-30 02:13:16 -08:00
sepakeloudest beae70edb7 feat: 修复auto情况和连接中... 2025-11-30 02:12:39 -08:00
sepakeloudest d2ea8436f9 feat: 13. 国家节点列表用4・4方块展示节点连接状态
14.禁止横屏
2025-11-30 01:36:26 -08:00
sepakeloudest 95f8f3afbe feat: 简单处理unknow提示 2025-11-28 18:00:21 -08:00
sepakeloudest a17fa079bc feat: 修改城市auto模式下节点显示问题 2025-11-28 17:27:01 -08:00
sepakeloudest 9302764802 feat: 修改macos兼容 2025-11-28 06:41:05 -08:00
sepakeloudest c058c2cea6 feat: 增加发送验证码的样式,调试设备限制数量 2025-11-28 01:25:20 -08:00
sepakeloudest 55d7508807 feat: 尚未购买的弹窗 2025-11-27 22:58:42 -08:00
sepakeloudest adcde623c7 feat: bugfix 2025-11-27 22:05:44 -08:00
sepakeloudest 83e742a9dc feat: 处理macos 2025-11-27 18:46:09 -08:00
sepakeloudest 569f9f6d05 feat: 修改bug 2025-11-27 03:10:47 -08:00
sepakeloudest 4fdf4da4f3 feat: 节点测速auto调整 2025-11-26 22:57:52 -08:00
sepakeloudest b059d01556 feat: 节点测速调整 2025-11-26 19:23:15 -08:00
sepakeloudest 909020654f feat: 节点测速调整 2025-11-25 23:06:20 -08:00
sepakeloudest 1809c11473 feat: macos配置和ios配置 2025-11-25 22:17:04 -08:00
shanshanzhong147 343bb50c77 fix: 调整窗口最小尺寸为400x334以改善用户体验 2025-11-24 18:38:23 -08:00
shanshanzhong147 11afee1d6f fix: 调整窗口最小尺寸并更新版本号
将窗口最小尺寸从800x668调整为400x334以提升用户体验
移除版本号中的build后缀以简化版本管理
2025-11-24 18:22:37 -08:00
shanshanzhong147 96d1a107cf fix(windows打包): 修改安装程序任务选项默认勾选状态
将桌面图标和开机启动任务的默认勾选状态从unchecked改为checkedonce
2025-11-24 03:48:35 -08:00
shanshanzhong147 52f241a5a7 fix(打包): 修改安装程序默认不创建桌面图标和开机启动
将桌面图标和开机启动选项的默认状态从选中改为未选中,以提供更合理的默认安装体验
2025-11-24 03:34:25 -08:00
shanshanzhong147 5dfb2b7432 fix(打包): 禁用Inno Setup的UsePreviousTasks选项
防止安装程序继承之前安装的任务选择
2025-11-24 03:17:05 -08:00
shanshanzhong147 85dc810421 fix(windows打包): 修改桌面图标创建任务为仅首次安装时选中
将桌面图标创建任务的Flags从checked改为checkedonce,使其仅在首次安装时默认选中
2025-11-24 03:07:44 -08:00
shanshanzhong147 ee3fdd91fa fix(windows打包): 修改桌面图标创建任务默认选中状态
移除CurPageChanged过程中强制选中桌面图标的逻辑,改为直接在Tasks中设置默认选中
2025-11-24 02:52:16 -08:00
shanshanzhong147 e537775e71 fix(打包配置): 添加语言目录配置并修复桌面图标任务选择
在make_config.yaml中添加languages_dir配置项,用于指定语言文件目录
修改inno_setup.sas脚本,在任务选择页面自动勾选桌面图标选项
2025-11-24 02:36:03 -08:00
shanshanzhong147 f97a21be32 feat(打包配置): 添加中文语言支持并更新图标路径
添加简体中文语言支持(zh, zh-cn, zh_CN)到安装配置
更新应用图标路径为绝对路径
在安装脚本中添加语言检测方法设置
2025-11-24 01:39:58 -08:00
shanshanzhong147 d3f48e345e build(windows): 添加应用图标文件并更新安装配置
将应用图标文件添加到安装包中,并更新安装配置中的图标路径引用
2025-11-24 01:25:46 -08:00
shanshanzhong147 ac8a04fd24 merge: 合并 dev 分支到 main 并保留本地重命名 2025-11-24 01:02:00 -08:00
sepakeloudest becc03acfd feat: 更改节点测速
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Successful in 20m53s
Build Windows / build (push) Has been cancelled
2025-11-21 02:52:23 -08:00
sepakeloudest 9123d5f0e4 feat: 登录接口修改
Build Windows / build (push) Has been cancelled
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Successful in 20m33s
2025-11-21 01:24:44 -08:00
sepakeloudest 91185a593a Revert "feat: 修改win的配置"
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Successful in 20m11s
Build Windows / build (push) Has been cancelled
This reverts commit d99f588a5b.
2025-11-20 19:22:45 -08:00
sepakeloudest d6386ecfa9 Revert "feat: ai修改win 名称信息"
This reverts commit bcb4d17236.
2025-11-20 19:21:17 -08:00
sepakeloudest 62d1af8517 feat: 修改接口方式
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Successful in 20m26s
Build Windows / build (push) Has been cancelled
2025-11-20 18:11:29 -08:00
sepakeloudest 3b05d4ff72 feat: 修改国家选择匹配问题 2025-11-20 09:08:23 -08:00
sepakeloudest bcb4d17236 feat: ai修改win 名称信息 2025-11-20 09:07:40 -08:00
sepakeloudest d99f588a5b feat: 修改win的配置
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Successful in 21m20s
Build Windows / build (push) Has been cancelled
2025-11-20 07:02:10 -08:00
sepakeloudest 691dd6e1d6 feat: 修改bug
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Successful in 21m44s
Build Windows / build (push) Has been cancelled
2025-11-20 06:34:12 -08:00
sepakeloudest b442289b8a feat: 修改bug
Build Windows / build (push) Has been cancelled
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Has started running
2025-11-20 06:19:05 -08:00
sepakeloudest 89d13e6237 feat: 修改兼容 2025-11-20 00:51:14 -08:00
sepakeloudest 8fd742a688 feat: 修改兼容 2025-11-19 04:06:46 -08:00
sepakeloudest 325a63d35f feat: 完成国家配置
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Successful in 20m34s
Build Windows / build (push) Has been cancelled
2025-11-19 01:06:48 -08:00
sepakeloudest 679c303457 feat: 1.新增在 VPN 配置中添加了一条规则,2.修复全局代理无法使用 2025-11-18 05:15:06 -08:00
sepakeloudest 59aa67d456 feat: 提交国家分组功能和debug功能
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Successful in 20m13s
Build Windows / build (push) Has been cancelled
2025-11-16 22:51:30 -08:00
sepakeloudest 2a1ee3018d feat: 提交国家分组功能和debug功能 2025-11-16 22:50:09 -08:00
sepakeloudest 76e83e0135 feat: 先改变节点选择方法
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Successful in 20m11s
Build Windows / build (push) Has been cancelled
2025-11-14 20:19:10 -08:00
sepakeloudest 89d6ec3c76 feat: 先改变节点选择方法
Build Windows / build (push) Has been cancelled
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Has been cancelled
2025-11-14 20:18:38 -08:00
sepakeloudest 670eb7ebc9 feat: 保存节点调试进度
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Successful in 20m7s
Build Windows / build (push) Has been cancelled
2025-11-13 22:43:27 -08:00
sepakeloudest 46295f4543 feat: 修改windows构建
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Successful in 29m17s
Build Windows / build (push) Successful in 7h8m10s
2025-11-13 05:24:35 -08:00
sepakeloudest a47174df56 feat: 初始化并发问题处理
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Has been cancelled
Build Windows / build (push) Has been cancelled
2025-11-13 05:15:18 -08:00
sepakeloudest 748ec6bee9 feat: 提交国家选择
Build Windows / build (push) Successful in 7h7m43s
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Successful in 20m1s
2025-11-11 01:29:16 -08:00
sepakeloudest a884e6c838 feat: 修改id和sn显示
Build Windows / build (push) Has been cancelled
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Has been cancelled
2025-11-09 22:55:01 -08:00
sepakeloudest 920b86e56a feat: 修改代码冲突
Build Windows / build (push) Has been cancelled
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Has been cancelled
2025-11-09 22:46:53 -08:00
sepakeloudest a78b40c339 feat: 修改代码冲突
Build Windows / build (push) Has been cancelled
Build Windows / 编译 libcore (Windows) (20.15.1) (push) Has been cancelled
2025-11-09 22:43:57 -08:00
73 changed files with 3160 additions and 2196 deletions
+3 -2
View File
@@ -1,3 +1,4 @@
[submodule "libcore"] [submodule "libcore"]
path = libcore path = libcore
url = https://github.com/hiddify/hiddify-next-core url = https://github.com/hiddify/hiddify-next-core
branch = main
+1 -1
View File
@@ -169,7 +169,7 @@ linux-release:
flutter_distributor package --flutter-build-args=verbose --platform linux --targets deb,rpm,appimage $(DISTRIBUTOR_ARGS) flutter_distributor package --flutter-build-args=verbose --platform linux --targets deb,rpm,appimage $(DISTRIBUTOR_ARGS)
macos-release: macos-release:
flutter_distributor package --platform macos --targets dmg,pkg $(DISTRIBUTOR_ARGS) flutter_distributor package --platform macos --targets dmg $(DISTRIBUTOR_ARGS)
ios-release: #not tested ios-release: #not tested
flutter_distributor package --platform ios --targets ipa --build-export-options-plist ios/exportOptions.plist $(DISTRIBUTOR_ARGS) flutter_distributor package --platform ios --targets ipa --build-export-options-plist ios/exportOptions.plist $(DISTRIBUTOR_ARGS)
+3 -3
View File
@@ -57,7 +57,7 @@ android {
defaultConfig { defaultConfig {
applicationId "app.hifastvpn.com" applicationId "app.hifastvpn.com"
minSdkVersion flutter.minSdkVersion minSdkVersion flutter.minSdkVersion
targetSdkVersion 36 targetSdkVersion 34
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
multiDexEnabled true multiDexEnabled true
@@ -105,8 +105,8 @@ android {
debugSymbolLevel 'FULL' debugSymbolLevel 'FULL'
} }
// 禁用代码混淆和资源压缩,解决VPN连接问题 // 禁用代码混淆和资源压缩,解决VPN连接问题
minifyEnabled true minifyEnabled false
shrinkResources true shrinkResources false
// proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' // proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
} }
+5
View File
@@ -34,6 +34,11 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_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 <application
android:name=".Application" android:name=".Application"
android:banner="@mipmap/ic_banner" android:banner="@mipmap/ic_banner"
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

+2 -2
View File
@@ -193,7 +193,7 @@
"rewardDetails": "Reward Details >", "rewardDetails": "Reward Details >",
"steps": "Invitation Steps", "steps": "Invitation Steps",
"inviteFriend": "Invite Friends", "inviteFriend": "Invite Friends",
"acceptInvite": "Friends accept invitation\nPlace order and register", "acceptInvite": "Friends accept invitationPlace order and register",
"getReward": "Get Reward", "getReward": "Get Reward",
"shareLink": "Share Link", "shareLink": "Share Link",
"shareQR": "Share QR Code", "shareQR": "Share QR Code",
@@ -398,7 +398,7 @@
"content": "Would you like to update now?", "content": "Would you like to update now?",
"updateNow": "Update Now", "updateNow": "Update Now",
"updateLater": "Later", "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": { "country": {
"cn": "China", "cn": "China",
+2 -2
View File
@@ -256,7 +256,7 @@
"rewardDetails": "Detalles de recompensa >", "rewardDetails": "Detalles de recompensa >",
"steps": "Pasos de invitación", "steps": "Pasos de invitación",
"inviteFriend": "Invitar amigo", "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", "getReward": "Obtener recompensa",
"shareLink": "Compartir por enlace", "shareLink": "Compartir por enlace",
"shareQR": "Compartir por código QR", "shareQR": "Compartir por código QR",
@@ -404,7 +404,7 @@
"content": "¿Actualizar ahora?", "content": "¿Actualizar ahora?",
"updateNow": "Actualizar ahora", "updateNow": "Actualizar ahora",
"updateLater": "Más tarde", "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": { "kr_invite": {
"close": "Cerrar", "close": "Cerrar",
+2 -2
View File
@@ -193,7 +193,7 @@
"rewardDetails": "Tasu üksikasjad >", "rewardDetails": "Tasu üksikasjad >",
"steps": "Kutse Sammud", "steps": "Kutse Sammud",
"inviteFriend": "Kutsu Sõbrad", "inviteFriend": "Kutsu Sõbrad",
"acceptInvite": "Sõbrad aktsepteerivad kutset\nTee tellimus ja registreeru", "acceptInvite": "Sõbrad aktsepteerivad kutsetTee tellimus ja registreeru",
"getReward": "Saada Tasu", "getReward": "Saada Tasu",
"shareLink": "Jaga Linki", "shareLink": "Jaga Linki",
"shareQR": "Jaga QR-koodi", "shareQR": "Jaga QR-koodi",
@@ -395,7 +395,7 @@
"content": "Uuendada nüüd?", "content": "Uuendada nüüd?",
"updateNow": "Uuenda nüüd", "updateNow": "Uuenda nüüd",
"updateLater": "Hiljem", "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": { "country": {
"cn": "Hiina", "cn": "Hiina",
+2 -2
View File
@@ -209,7 +209,7 @@
"rewardDetails": "報酬の詳細 >", "rewardDetails": "報酬の詳細 >",
"steps": "招待の手順", "steps": "招待の手順",
"inviteFriend": "友達を招待", "inviteFriend": "友達を招待",
"acceptInvite": "友達が招待を受け入れ\n注文して登録", "acceptInvite": "友達が招待を受け入れ注文して登録",
"getReward": "報酬を獲得", "getReward": "報酬を獲得",
"shareLink": "リンクを共有", "shareLink": "リンクを共有",
"shareQR": "QRコードを共有", "shareQR": "QRコードを共有",
@@ -412,7 +412,7 @@
"content": "今すぐアップデートしますか?", "content": "今すぐアップデートしますか?",
"updateNow": "今すぐアップデート", "updateNow": "今すぐアップデート",
"updateLater": "後で", "updateLater": "後で",
"defaultContent": "1. アプリのパフォーマンス最適化\n2. 既知の問題の修正\n3. ユーザー体験の向上" "defaultContent": "1. アプリのパフォーマンス最適化2. 既知の問題の修正3. ユーザー体験の向上"
}, },
"country": { "country": {
"cn": "中国", "cn": "中国",
+1 -1
View File
@@ -387,7 +387,7 @@
"content": "Хотите обновить сейчас?", "content": "Хотите обновить сейчас?",
"updateNow": "Обновить сейчас", "updateNow": "Обновить сейчас",
"later": "Позже", "later": "Позже",
"defaultContent": "1. Оптимизация производительности приложения\n2. Исправление известных проблем\n3. Улучшение пользовательского опыта" "defaultContent": "1. Оптимизация производительности приложения2. Исправление известных проблем3. Улучшение пользовательского опыта"
}, },
"country": { "country": {
"cn": "Китай", "cn": "Китай",
+10 -10
View File
@@ -2,7 +2,7 @@
"home": { "home": {
"welcome": "欢迎使用 Hi快VPN", "welcome": "欢迎使用 Hi快VPN",
"disconnected": "未连接", "disconnected": "未连接",
"connecting": "正在连接", "connecting": "正在连接...",
"connected": "已连接", "connected": "已连接",
"disconnecting": "正在断开", "disconnecting": "正在断开",
"currentConnectionTitle": "当前连接", "currentConnectionTitle": "当前连接",
@@ -232,13 +232,13 @@
"longestConnection": "最长连接时间", "longestConnection": "最长连接时间",
"days": "{days}天", "days": "{days}天",
"daysOfWeek": { "daysOfWeek": {
"monday": "周\n一", "monday": "周一",
"tuesday": "周\n二", "tuesday": "周二",
"wednesday": "周\n三", "wednesday": "周三",
"thursday": "周\n四", "thursday": "周四",
"friday": "周\n五", "friday": "周五",
"saturday": "周\n六", "saturday": "周六",
"sunday": "周\n日" "sunday": "周日"
}, },
"processTrafficFailed": "处理流量日志数据失败" "processTrafficFailed": "处理流量日志数据失败"
}, },
@@ -256,7 +256,7 @@
"rewardDetails": "奖励明细 >", "rewardDetails": "奖励明细 >",
"steps": "邀请步骤", "steps": "邀请步骤",
"inviteFriend": "邀请好友", "inviteFriend": "邀请好友",
"acceptInvite": "好友接受邀请\n下单并注册", "acceptInvite": "好友接受邀请下单并注册",
"getReward": "获得奖励", "getReward": "获得奖励",
"shareLink": "分享链接", "shareLink": "分享链接",
"shareQR": "分享二维码", "shareQR": "分享二维码",
@@ -415,7 +415,7 @@
"content": "是否立即更新?", "content": "是否立即更新?",
"updateNow": "立即更新", "updateNow": "立即更新",
"updateLater": "稍后再说", "updateLater": "稍后再说",
"defaultContent": "1. 优化应用性能\n2. 修复已知问题\n3. 提升用户体验" "defaultContent": "1. 优化应用性能2. 修复已知问题3. 提升用户体验"
}, },
"country": { "country": {
"cn": "中国", "cn": "中国",
@@ -170,13 +170,13 @@
"longestConnection": "最長連接時間", "longestConnection": "最長連接時間",
"days": "{days}天", "days": "{days}天",
"daysOfWeek": { "daysOfWeek": {
"monday": "週\n一", "monday": "週一",
"tuesday": "週\n二", "tuesday": "週二",
"wednesday": "週\n三", "wednesday": "週三",
"thursday": "週\n四", "thursday": "週四",
"friday": "週\n五", "friday": "週五",
"saturday": "週\n六", "saturday": "週六",
"sunday": "週\n日" "sunday": "週日"
}, },
"processTrafficFailed": "處理流量日誌資料失敗" "processTrafficFailed": "處理流量日誌資料失敗"
}, },
@@ -194,7 +194,7 @@
"rewardDetails": "獎勵明細 >", "rewardDetails": "獎勵明細 >",
"steps": "邀請步驟", "steps": "邀請步驟",
"inviteFriend": "邀請好友", "inviteFriend": "邀請好友",
"acceptInvite": "好友接受邀請\n下單並註冊", "acceptInvite": "好友接受邀請下單並註冊",
"getReward": "獲得獎勵", "getReward": "獲得獎勵",
"shareLink": "分享連結", "shareLink": "分享連結",
"shareQR": "分享二維碼", "shareQR": "分享二維碼",
@@ -332,7 +332,7 @@
"content": "是否立即更新?", "content": "是否立即更新?",
"updateNow": "立即更新", "updateNow": "立即更新",
"updateLater": "稍後", "updateLater": "稍後",
"defaultContent": "1. 優化應用性能\n2. 修復已知問題\n3. 改進用戶體驗" "defaultContent": "1. 優化應用性能2. 修復已知問題3. 改進用戶體驗"
}, },
"orderStatus": { "orderStatus": {
"title": "訂單狀態", "title": "訂單狀態",
+2
View File
@@ -5,6 +5,8 @@ targets:
options: options:
explicit_to_json: true explicit_to_json: true
drift_dev: drift_dev:
generate_for:
- lib/**.drift
options: options:
store_date_time_values_as_text: true store_date_time_values_as_text: true
slang_build_runner: slang_build_runner:
+6 -1
View File
@@ -1,4 +1,5 @@
name: BearVPN name: BearVPN
app_name: BearVPN
version: 1.0.0 version: 1.0.0
build_number: 1 build_number: 1
targets: targets:
@@ -10,4 +11,8 @@ targets:
pkg: pkg:
enable: true enable: true
# 不签名 # 不签名
sign: false sign: false
windows:
exe:
enable: true
icon: assets/images/tray_icon.ico
+2 -2
View File
@@ -5,6 +5,6 @@
// Created by GFWFighter on 7/24/1402 AP. // 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 SERVICE_IDENTIFIER=com.hi.app
DEVELOPMENT_TEAM=3UR892FAP3 DEVELOPMENT_TEAM=NJRRF427XB
@@ -10,7 +10,7 @@
<true/> <true/>
<key>com.apple.security.application-groups</key> <key>com.apple.security.application-groups</key>
<array> <array>
<string>group.app.hi.com</string> <string>group.$(BASE_BUNDLE_IDENTIFIER)</string>
</array> </array>
<key>com.apple.security.network.client</key> <key>com.apple.security.network.client</key>
<true/> <true/>
+31 -19
View File
@@ -758,11 +758,11 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = PacketTunnel/HiddifyPacketTunnel.entitlements; CODE_SIGN_ENTITLEMENTS = PacketTunnel/HiddifyPacketTunnel.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 3UR892FAP3; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = NJRRF427XB;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
EXCLUDED_ARCHS = armv7; EXCLUDED_ARCHS = armv7;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -788,7 +788,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).PacketTunnel"; PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).PacketTunnel";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = dev_p; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "HiFastVPN-iOS-Pord-PacketTunnel";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;
@@ -814,9 +814,11 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = PacketTunnel/PacketTunnelRelease.entitlements; CODE_SIGN_ENTITLEMENTS = PacketTunnel/PacketTunnelRelease.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = Q5PC7SNX27; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = NJRRF427XB;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
EXCLUDED_ARCHS = armv7; EXCLUDED_ARCHS = armv7;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -842,6 +844,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).PacketTunnel"; PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).PacketTunnel";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "HiFastVPN-iOS-Pord-PacketTunnel";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;
@@ -865,9 +868,11 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = PacketTunnel/HiddifyPacketTunnel.entitlements; CODE_SIGN_ENTITLEMENTS = PacketTunnel/HiddifyPacketTunnel.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = Q5PC7SNX27; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = NJRRF427XB;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
EXCLUDED_ARCHS = armv7; EXCLUDED_ARCHS = armv7;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -893,6 +898,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).PacketTunnel"; PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).PacketTunnel";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "HiFastVPN-iOS-Pord-PacketTunnel";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;
@@ -933,7 +939,7 @@
CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = 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; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
@@ -975,7 +981,7 @@
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 3UR892FAP3; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = NJRRF427XB;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphoneos*]" = armv7; "EXCLUDED_ARCHS[sdk=iphoneos*]" = armv7;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
@@ -1004,9 +1010,10 @@
"-lresolv", "-lresolv",
); );
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)"; PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)";
"PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = com.taw.hifastvpn;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = hot; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "HiFastVPN-iOS-Pord";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -1023,7 +1030,7 @@
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 3UR892FAP3; DEVELOPMENT_TEAM = NJRRF427XB;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 2.5.72.5.52.5.31.0; MARKETING_VERSION = 2.5.72.5.52.5.31.0;
PRODUCT_BUNDLE_IDENTIFIER = com.hiddify.ios.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = com.hiddify.ios.RunnerTests;
@@ -1042,7 +1049,7 @@
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 3UR892FAP3; DEVELOPMENT_TEAM = NJRRF427XB;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 2.5.72.5.52.5.31.0; MARKETING_VERSION = 2.5.72.5.52.5.31.0;
PRODUCT_BUNDLE_IDENTIFIER = com.hiddify.ios.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = com.hiddify.ios.RunnerTests;
@@ -1059,7 +1066,7 @@
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 3UR892FAP3; DEVELOPMENT_TEAM = NJRRF427XB;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 2.5.72.5.52.5.31.0; MARKETING_VERSION = 2.5.72.5.52.5.31.0;
PRODUCT_BUNDLE_IDENTIFIER = com.hiddify.ios.RunnerTests; PRODUCT_BUNDLE_IDENTIFIER = com.hiddify.ios.RunnerTests;
@@ -1161,7 +1168,7 @@
CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = 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; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
@@ -1201,11 +1208,11 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 3UR892FAP3; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = NJRRF427XB;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphoneos*]" = armv7; "EXCLUDED_ARCHS[sdk=iphoneos*]" = armv7;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64"; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64";
@@ -1234,9 +1241,10 @@
"-lresolv", "-lresolv",
); );
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)"; PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)";
"PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = com.taw.hifastvpn;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = dev; "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "HiFastVPN-iOS-Pord";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -1257,9 +1265,11 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = Q5PC7SNX27; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = NJRRF427XB;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphoneos*]" = armv7; "EXCLUDED_ARCHS[sdk=iphoneos*]" = armv7;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
@@ -1288,8 +1298,10 @@
"-lresolv", "-lresolv",
); );
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)"; PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER)";
"PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = com.taw.hifastvpn;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "HiFastVPN-iOS-Pord";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+2 -2
View File
@@ -52,10 +52,10 @@
<string>需要相机权限以支持拍照功能</string> <string>需要相机权限以支持拍照功能</string>
<key>NSMicrophoneUsageDescription</key> <key>NSMicrophoneUsageDescription</key>
<string>需要麦克风权限以支持语音消息功能</string> <string>需要麦克风权限以支持语音消息功能</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要相册权限以支持图片上传功能</string>
<key>NSPhotoLibraryAddUsageDescription</key> <key>NSPhotoLibraryAddUsageDescription</key>
<string>需要相册添加权限以保存图片</string> <string>需要相册添加权限以保存图片</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要相册权限以支持图片上传功能</string>
<key>SERVICE_IDENTIFIER</key> <key>SERVICE_IDENTIFIER</key>
<string>$(SERVICE_IDENTIFIER)</string> <string>$(SERVICE_IDENTIFIER)</string>
<key>UIApplicationSupportsIndirectInputEvents</key> <key>UIApplicationSupportsIndirectInputEvents</key>
+1 -1
View File
@@ -12,7 +12,7 @@
<true/> <true/>
<key>com.apple.security.application-groups</key> <key>com.apple.security.application-groups</key>
<array> <array>
<string>group.app.hi.com</string> <string>group.com.taw.hifastvpn</string>
</array> </array>
<key>com.apple.security.network.client</key> <key>com.apple.security.network.client</key>
<true/> <true/>
+1 -1
View File
@@ -12,7 +12,7 @@
<true/> <true/>
<key>com.apple.security.application-groups</key> <key>com.apple.security.application-groups</key>
<array> <array>
<string>group.app.hi.com</string> <string>group.com.taw.hifastvpn</string>
</array> </array>
<key>com.apple.security.network.client</key> <key>com.apple.security.network.client</key>
<true/> <true/>
+7 -6
View File
@@ -10,19 +10,20 @@
<string>app-store</string> <string>app-store</string>
<key>provisioningProfiles</key> <key>provisioningProfiles</key>
<dict> <dict>
<key>app.myhiddify.com</key> <key>com.taw.hifastvpn</key>
<string>dist.apple.app.myhiddify.com</string> <string>HiFastVPN-iOS-Pord</string>
<key>app.myhiddify.com.SingBoxPacketTunnel</key>
<string>dist.apple.app.myhiddify.com.singboxpackettunnel</string> <key>com.taw.hifastvpn.PacketTunnel</key>
<string>HiFastVPN-iOS-Pord-PacketTunnel</string>
</dict> </dict>
<key>signingCertificate</key> <key>signingCertificate</key>
<string>E2217AF6F3AD11BA09F9FD95E025D7E637F8B081</string> <string>545725EC39FB3B586B4F63F4BF8D9834FDCFE946</string>
<key>signingStyle</key> <key>signingStyle</key>
<string>manual</string> <string>manual</string>
<key>stripSwiftSymbols</key> <key>stripSwiftSymbols</key>
<true/> <true/>
<key>teamID</key> <key>teamID</key>
<string>3UR892FAP3</string> <string>NJRRF427XB</string>
<key>uploadSymbols</key> <key>uploadSymbols</key>
<true/> <true/>
</dict> </dict>
+4 -1
View File
@@ -1309,6 +1309,9 @@ class AppConfig {
/// 网站ID /// 网站ID
String kr_website_id = ""; String kr_website_id = "";
/// 设备限制数量
String device_limit = '0';
/// 是否为白天模式 /// 是否为白天模式
bool kr_is_daytime = true; bool kr_is_daytime = true;
@@ -1447,7 +1450,7 @@ class AppConfig {
kr_official_telegram = config.kr_official_telegram; kr_official_telegram = config.kr_official_telegram;
kr_official_telephone = config.kr_official_telephone; kr_official_telephone = config.kr_official_telephone;
kr_invitation_link = config.kr_invitation_link; 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) { if (config.kr_domains.isNotEmpty) {
KRDomain.kr_handleDomains(config.kr_domains); KRDomain.kr_handleDomains(config.kr_domains);
} }
+99 -38
View File
@@ -28,13 +28,10 @@ class KROutboundItem {
/// URL /// URL
String url = ""; String url = "";
@override
String toString() {
return 'KROutboundItem(tag: $tag, country: $country, delay: ${urlTestDelay.value}ms)';
}
/// 服务器类型 /// 服务器类型
/// 构造函数,接受 KrNodeListItem 对象并初始化 KROutboundItem /// 构造函数,接受 KrItem 对象并初始化 KROutboundItem
KROutboundItem(KrNodeListItem nodeListItem) { KROutboundItem(KrNodeListItem nodeListItem) {
id = nodeListItem.id.toString(); id = nodeListItem.id.toString();
protocol = nodeListItem.protocol; protocol = nodeListItem.protocol;
@@ -84,13 +81,13 @@ class KROutboundItem {
case "vless": case "vless":
final securityConfig = final securityConfig =
json["security_config"] as Map<String, dynamic>? ?? {}; json["security_config"] as Map<String, dynamic>? ?? {};
// 智能设置 server_name // 智能设置 server_name
String serverName = securityConfig["sni"] ?? ""; String serverName = securityConfig["sni"] ?? "";
if (serverName.isEmpty) { if (serverName.isEmpty) {
serverName = nodeListItem.serverAddr; serverName = nodeListItem.serverAddr;
} }
config = { config = {
"type": "vless", "type": "vless",
"tag": nodeListItem.name, "tag": nodeListItem.name,
@@ -115,13 +112,13 @@ class KROutboundItem {
case "vmess": case "vmess":
final securityConfig = final securityConfig =
json["security_config"] as Map<String, dynamic>? ?? {}; json["security_config"] as Map<String, dynamic>? ?? {};
// 智能设置 server_name // 智能设置 server_name
String serverName = securityConfig["sni"] ?? ""; String serverName = securityConfig["sni"] ?? "";
if (serverName.isEmpty) { if (serverName.isEmpty) {
serverName = nodeListItem.serverAddr; serverName = nodeListItem.serverAddr;
} }
config = { config = {
"type": "vmess", "type": "vmess",
"tag": nodeListItem.name, "tag": nodeListItem.name,
@@ -152,7 +149,7 @@ class KROutboundItem {
break; break;
case "hysteria": case "hysteria":
case "hysteria2": case "hysteria2":
// 后端的 "hysteria" 实际上是 Hysteria2 协议 // 后端的 "hysteria" 实际上是 Hysteria2 协议
final securityConfig = final securityConfig =
json["security_config"] as Map<String, dynamic>? ?? {}; json["security_config"] as Map<String, dynamic>? ?? {};
config = { config = {
@@ -177,14 +174,14 @@ class KROutboundItem {
case "trojan": case "trojan":
final securityConfig = final securityConfig =
json["security_config"] as Map<String, dynamic>? ?? {}; json["security_config"] as Map<String, dynamic>? ?? {};
// 智能设置 server_name // 智能设置 server_name
String serverName = securityConfig["sni"] ?? ""; String serverName = securityConfig["sni"] ?? "";
if (serverName.isEmpty) { if (serverName.isEmpty) {
// 如果没有配置 SNI,使用服务器地址 // 如果没有配置 SNI,使用服务器地址
serverName = nodeListItem.serverAddr; serverName = nodeListItem.serverAddr;
} }
config = { config = {
"type": "trojan", "type": "trojan",
"tag": nodeListItem.name, "tag": nodeListItem.name,
@@ -274,6 +271,7 @@ class KROutboundItem {
// 🔧 尝试从 config 字段解析 transport 配置 // 🔧 尝试从 config 字段解析 transport 配置
Map<String, dynamic>? transportConfig; Map<String, dynamic>? transportConfig;
Map<String, dynamic>? securityConfig; Map<String, dynamic>? securityConfig;
int actualPort = nodeListItem.port; // 🔧 使用局部变量存储实际端口
// 🔧 关键修复:优先从 protocols 字段解析配置 // 🔧 关键修复:优先从 protocols 字段解析配置
if (nodeListItem.protocols.isNotEmpty) { if (nodeListItem.protocols.isNotEmpty) {
@@ -307,6 +305,28 @@ class KROutboundItem {
} }
if (matchedProtocol != null) { 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 配置 // 提取 transport 配置
if (matchedProtocol['network'] != null || matchedProtocol['transport'] != null) { if (matchedProtocol['network'] != null || matchedProtocol['transport'] != null) {
final network = matchedProtocol['network'] ?? matchedProtocol['transport']; final network = matchedProtocol['network'] ?? matchedProtocol['transport'];
@@ -402,7 +422,7 @@ class KROutboundItem {
switch (nodeListItem.protocol) { switch (nodeListItem.protocol) {
case "shadowsocks": case "shadowsocks":
// 优先使用 protocols 解析出来的 cipher,其次是 method 字段,最后才是默认值 // 优先使用 protocols 解析出来的 cipher,其次是 method 字段,最后才是默认值
String finalMethod = nodeListItem.method.isNotEmpty String finalMethod = nodeListItem.method.isNotEmpty
? nodeListItem.method ? nodeListItem.method
: "2022-blake3-aes-256-gcm"; : "2022-blake3-aes-256-gcm";
@@ -411,22 +431,21 @@ class KROutboundItem {
"type": "shadowsocks", "type": "shadowsocks",
"tag": nodeListItem.name, "tag": nodeListItem.name,
"server": nodeListItem.serverAddr, "server": nodeListItem.serverAddr,
"server_port": nodeListItem.port, "server_port": actualPort,
"method": finalMethod, "method": finalMethod,
"password": nodeListItem.uuid "password": nodeListItem.uuid
}; };
if (kDebugMode) { if (kDebugMode) {
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
print('✅ Shadowsocks 节点配置构建成功: ${nodeListItem.name}'); print('✅ Shadowsocks 节点配置构建成功: ${nodeListItem.name}');
}
if (kDebugMode) {
print('📄 使用加密方法: $finalMethod'); print('📄 使用加密方法: $finalMethod');
} print('📄 完整配置 JSON:');
if (kDebugMode) { print(jsonEncode(config));
print('📄 完整配置: $config'); print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
} }
break; break;
case "vless": case "vless":
// 判断是否为域名(非IP地址) // 判断是否为域名(非IP地址)
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$') final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
.hasMatch(nodeListItem.serverAddr); .hasMatch(nodeListItem.serverAddr);
@@ -436,17 +455,25 @@ class KROutboundItem {
serverName = securityConfig['sni'].toString(); serverName = securityConfig['sni'].toString();
} }
// 🔧 关键修复:根据 security_config 判断是否启用 TLS // 🔧 关键修复:智能判断是否启用 TLS
final bool vlessTlsEnabled = securityConfig?['tls_enabled'] ?? false; // 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) { if (kDebugMode) {
print('🔐 VLESS TLS 状态: enabled=$vlessTlsEnabled'); print('🔐 VLESS TLS 状态: enabled=$vlessTlsEnabled (port=${nodeListItem.port}, isDomain=$isDomain)');
} }
config = { config = {
"type": "vless", "type": "vless",
"tag": nodeListItem.name, "tag": nodeListItem.name,
"server": nodeListItem.serverAddr, "server": nodeListItem.serverAddr,
"server_port": nodeListItem.port, "server_port": actualPort,
"uuid": nodeListItem.uuid, "uuid": nodeListItem.uuid,
if (transportConfig != null) "transport": transportConfig, if (transportConfig != null) "transport": transportConfig,
if (vlessTlsEnabled) "tls": { if (vlessTlsEnabled) "tls": {
@@ -460,14 +487,27 @@ class KROutboundItem {
} }
}; };
if (kDebugMode) { if (kDebugMode) {
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
print('✅ VLESS 节点配置构建成功: ${nodeListItem.name}'); print('✅ VLESS 节点配置构建成功: ${nodeListItem.name}');
} print('📋 原始节点信息:');
if (kDebugMode) { print(' - serverAddr: ${nodeListItem.serverAddr}');
print('📄 完整配置: $config'); 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; break;
case "vmess": case "vmess":
// 判断是否为域名(非IP地址) // 判断是否为域名(非IP地址)
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$') final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
.hasMatch(nodeListItem.serverAddr); .hasMatch(nodeListItem.serverAddr);
@@ -477,17 +517,25 @@ class KROutboundItem {
serverName = securityConfig['sni'].toString(); serverName = securityConfig['sni'].toString();
} }
// 🔧 关键修复:根据 security_config 判断是否启用 TLS // 🔧 关键修复:智能判断是否启用 TLS
final bool tlsEnabled = securityConfig?['tls_enabled'] ?? false; // 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) { if (kDebugMode) {
print('🔐 TLS 状态: enabled=$tlsEnabled'); print('🔐 VMess TLS 状态: enabled=$tlsEnabled (port=${nodeListItem.port}, isDomain=$isDomain)');
} }
config = { config = {
"type": "vmess", "type": "vmess",
"tag": nodeListItem.name, "tag": nodeListItem.name,
"server": nodeListItem.serverAddr, "server": nodeListItem.serverAddr,
"server_port": nodeListItem.port, "server_port": actualPort,
"uuid": nodeListItem.uuid, "uuid": nodeListItem.uuid,
"alter_id": 0, "alter_id": 0,
"security": "auto", "security": "auto",
@@ -503,14 +551,27 @@ class KROutboundItem {
} }
}; };
if (kDebugMode) { if (kDebugMode) {
print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
print('✅ VMess 节点配置构建成功: ${nodeListItem.name}'); print('✅ VMess 节点配置构建成功: ${nodeListItem.name}');
} print('📋 原始节点信息:');
if (kDebugMode) { print(' - serverAddr: ${nodeListItem.serverAddr}');
print('📄 完整配置: $config'); 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; break;
case "trojan": case "trojan":
// 判断是否为域名(非IP地址) // 判断是否为域名(非IP地址)
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$') final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
.hasMatch(nodeListItem.serverAddr); .hasMatch(nodeListItem.serverAddr);
@@ -524,7 +585,7 @@ class KROutboundItem {
"type": "trojan", "type": "trojan",
"tag": nodeListItem.name, "tag": nodeListItem.name,
"server": nodeListItem.serverAddr, "server": nodeListItem.serverAddr,
"server_port": nodeListItem.port, "server_port": actualPort,
"password": nodeListItem.uuid, "password": nodeListItem.uuid,
if (transportConfig != null) "transport": transportConfig, if (transportConfig != null) "transport": transportConfig,
"tls": { "tls": {
@@ -546,7 +607,7 @@ class KROutboundItem {
break; break;
case "hysteria": case "hysteria":
case "hysteria2": case "hysteria2":
// 后端的 "hysteria" 实际上是 Hysteria2 协议 // 后端的 "hysteria" 实际上是 Hysteria2 协议
if (kDebugMode) { if (kDebugMode) {
print('🔍 构建 Hysteria2 节点: ${nodeListItem.name}'); print('🔍 构建 Hysteria2 节点: ${nodeListItem.name}');
} }
@@ -8,7 +8,7 @@ import 'package:flutter/foundation.dart';
/// 表示出站项列表的模型类 /// 表示出站项列表的模型类
class KrOutboundsList { class KrOutboundsList {
/// 服务器分组 /// 服务器分组
final List<KRGroupOutboundList> groupOutboundList = []; // 存储服务器分组的列表 final List<KRGroupOutboundList> groupOutboundList = []; // 存储服务器分组的列表
@@ -18,20 +18,20 @@ class KrOutboundsList {
/// 全部列表 /// 全部列表
final List<KROutboundItem> allList = []; // 存储国家分组的列表 final List<KROutboundItem> allList = []; // 存储国家分组的列表
// 配置json // 配置json
final List<Map<String,dynamic>> configJsonList = []; final List<Map<String,dynamic>> configJsonList = [];
/// 标签列表 /// 标签列表
final Map<String,KROutboundItem> keyList = {}; // 存储国家分组的列表 final Map<String,KROutboundItem> keyList = {}; // 存储国家分组的列表
/// 处理出站项并将其分组 /// 处理出站项并将其分组
/// [list] 是要处理的出站项列表 /// [list] 是要处理的出站项列表
void processOutboundItems(List<KrNodeListItem> list,List<KRNodeGroupListItem> groupList) { void processOutboundItems(List<KrNodeListItem> list,List<KRNodeGroupListItem> groupList) {
final Map<String, List<KROutboundItem>> tagGroups = {}; final Map<String, List<KROutboundItem>> tagGroups = {};
final Map<String, List<KROutboundItem>> countryGroups = {}; final Map<String, List<KROutboundItem>> countryGroups = {};
// 用于追踪已使用的标签 // 用于追踪已使用的标签
final Map<String, int> tagCounter = {}; final Map<String, int> tagCounter = {};
@@ -90,9 +90,9 @@ class KrOutboundsList {
// 将标签分组转换为 KRGroupOutboundList 并添加到 groupOutboundList // 将标签分组转换为 KRGroupOutboundList 并添加到 groupOutboundList
for (var tag in tagGroups.keys) { for (var tag in tagGroups.keys) {
final item = KRGroupOutboundList( final item = KRGroupOutboundList(
tag: tag, outboundList: tagGroups[tag]!); tag: tag, outboundList: tagGroups[tag]!);
for (var group in groupList) { for (var group in groupList) {
if (item.tag == group.name) { if (item.tag == group.name) {
item.icon = group.icon; item.icon = group.icon;
+3 -3
View File
@@ -7,16 +7,16 @@ class KRIsRegister {
KRIsRegister({this.kr_isRegister = false}); KRIsRegister({this.kr_isRegister = false});
KRIsRegister.fromJson(Map<String, dynamic> json) { KRIsRegister.fromJson(Map<String, dynamic> json) {
kr_isRegister = json['Status'] == "true" || json['Status'] == true kr_isRegister = json['exist'] == "true" || json['exist'] == true
? true ? true
: false || json['status'] == "true" || json['status'] == true : false || json['exist'] == "true" || json['exist'] == true
? true ? true
: false; : false;
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{}; final Map<String, dynamic> data = <String, dynamic>{};
data['Status'] = kr_isRegister; data['exist'] = kr_isRegister;
return data; return data;
} }
} }
+56 -13
View File
@@ -2,7 +2,7 @@ import 'dart:convert';
import 'package:kaer_with_panels/app/utils/kr_log_util.dart'; import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
class KRNodeList { class KRNodeList {
final List<KrNodeListItem> list; final List<KrNodeListItem> list;
final String subscribeId; final String subscribeId;
final String startTime; final String startTime;
final String expireTime; final String expireTime;
@@ -35,8 +35,8 @@ class KRNodeList {
// 查找 id 匹配的订阅项 // 查找 id 匹配的订阅项
try { try {
subscribeData = listData.firstWhere( subscribeData = listData.firstWhere(
(item) => (item as Map<String, dynamic>)['id']?.toString() == requestSubscribeId, (item) => (item as Map<String, dynamic>)['id']?.toString() == requestSubscribeId,
orElse: () => listData[0] as Map<String, dynamic> orElse: () => listData[0] as Map<String, dynamic>
) as Map<String, dynamic>; ) as Map<String, dynamic>;
KRLogUtil.kr_i('✅ 找到匹配的订阅项: id=$requestSubscribeId', tag: 'NodeList'); KRLogUtil.kr_i('✅ 找到匹配的订阅项: id=$requestSubscribeId', tag: 'NodeList');
} catch (e) { } catch (e) {
@@ -70,7 +70,7 @@ class KRNodeList {
class KrNodeListItem { class KrNodeListItem {
final int id; final int id;
String name; String name;
final String uuid; final String uuid;
final String protocol; final String protocol;
final String relayMode; final String relayMode;
@@ -136,25 +136,68 @@ class KrNodeListItem {
String method = json['method']?.toString() ?? ''; // 加密方法(Shadowsocks等) String method = json['method']?.toString() ?? ''; // 加密方法(Shadowsocks等)
final protocols = json['protocols']?.toString() ?? ''; // 协议配置JSON 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) { if (protocols.isNotEmpty) {
try { try {
final protocolsList = jsonDecode(protocols) as List; 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) { if (protocolsList.isNotEmpty) {
final firstProtocol = protocolsList[0] as Map<String, dynamic>; // 🔧 修复:查找与当前协议类型匹配的配置,而不是直接使用第一个
// 优先使用 protocols 中的配置 Map<String, dynamic>? matchedProtocolConfig;
if (firstProtocol['port'] != null) {
port = _parseIntSafely(firstProtocol['port']); // 尝试找到协议类型匹配的配置
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) { } catch (e) {
KRLogUtil.kr_w('解析 protocols 字段失败: $e', tag: 'NodeList'); KRLogUtil.kr_w('⚠️ 解析 protocols 字段失败: $e', tag: 'NodeList');
} }
} }
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'NodeList');
return KrNodeListItem( return KrNodeListItem(
id: _parseIntSafely(json['id']), id: _parseIntSafely(json['id']),
@@ -63,6 +63,7 @@ class KRSiteInfo {
final String customHtml; final String customHtml;
final String customData; final String customData;
final String crispId; final String crispId;
final String deviceLimit;
KRSiteInfo({ KRSiteInfo({
required this.host, required this.host,
@@ -73,10 +74,12 @@ class KRSiteInfo {
required this.customHtml, required this.customHtml,
required this.customData, required this.customData,
required this.crispId, required this.crispId,
required this.deviceLimit,
}); });
factory KRSiteInfo.fromJson(Map<String, dynamic> json) { factory KRSiteInfo.fromJson(Map<String, dynamic> json) {
String crispId = '0'; String crispId = '0';
String deviceLimit = '0';
// custom_data kr_website_id // custom_data kr_website_id
try { try {
@@ -89,6 +92,16 @@ class KRSiteInfo {
// 使 // 使
} }
// custom_data deviceLimit
try {
final customDataStr = json['custom_data'] ?? '';
if (customDataStr.isNotEmpty) {
final customDataJson = jsonDecode(customDataStr) as Map<String, dynamic>;
deviceLimit = (customDataJson['deviceLimit'] ?? 0).toString();
}
} catch (e) {
// 使
}
return KRSiteInfo( return KRSiteInfo(
host: json['host'] ?? '', host: json['host'] ?? '',
siteName: json['site_name'] ?? '', siteName: json['site_name'] ?? '',
@@ -98,6 +111,7 @@ class KRSiteInfo {
customHtml: json['custom_html'] ?? '', customHtml: json['custom_html'] ?? '',
customData: json['custom_data'] ?? '', customData: json['custom_data'] ?? '',
crispId: crispId, crispId: crispId,
deviceLimit: deviceLimit,
); );
} }
@@ -37,10 +37,12 @@ class HIMenuView extends GetView<HIMenuController> {
mainAxisSize: MainAxisSize.min, // Column mainAxisSize: MainAxisSize.min, // Column
children: [ children: [
Obx(() { Obx(() {
final account = KRAppRunData.getInstance().kr_account.value ?? '未登录'; final account = KRAppRunData.getInstance().kr_account.value;
final isDeviceLogin = account != null && account.startsWith('9000');
final accountText = isDeviceLogin ? '待绑定' : '${KRAppRunData.getInstance().kr_account.value.toString()}';
return UserInfoCard( return UserInfoCard(
controller: controller, controller: controller,
userId: account, userId: accountText,
); );
}), }),
@@ -6,6 +6,7 @@ import 'package:get/get.dart';
import 'package:kaer_with_panels/app/modules/hi_menu/controllers/hi_menu_controller.dart'; import 'package:kaer_with_panels/app/modules/hi_menu/controllers/hi_menu_controller.dart';
import 'package:kaer_with_panels/app/routes/app_pages.dart'; import 'package:kaer_with_panels/app/routes/app_pages.dart';
import 'package:kaer_with_panels/app/widgets/kr_local_image.dart'; import 'package:kaer_with_panels/app/widgets/kr_local_image.dart';
import 'package:kaer_with_panels/app/widgets/kr_subscription_expiry_text.dart';
/// Widget /// Widget
/// ///
@@ -58,58 +59,19 @@ class UserInfoCard extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( Text(
'ID: $userId', userId,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 14.sp, fontSize: 14.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
Obx(() { KRSubscriptionExpiryText(
final currentSubscribe = expireTimeProvider: () => controller.kr_subscribeService
controller.kr_subscribeService.kr_currentSubscribe.value; .kr_currentSubscribe.value?.expireTime,
String expiryText; ),
if (currentSubscribe == null) {
expiryText = '尚未购买套餐';
} else {
final now = DateTime.now();
DateTime? expireDateTime;
try {
expireDateTime =
DateTime.parse(currentSubscribe.expireTime);
} catch (e) {
expireDateTime = null;
}
if (expireDateTime == null) {
expiryText = '套餐信息无效';
} else if (expireDateTime.isBefore(now)) {
final formattedExpireDate =
'${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')}';
expiryText = '已于 $formattedExpireDate 到期';
} else {
final year = expireDateTime.year;
final month = expireDateTime.month.toString().padLeft(2, '0');
final day = expireDateTime.day.toString().padLeft(2, '0');
final hour = expireDateTime.hour.toString().padLeft(2, '0');
final minute = expireDateTime.minute.toString().padLeft(2, '0');
final second = expireDateTime.second.toString().padLeft(2, '0');
// 2.
final formattedDateTime = '$year/$month/$day $hour:$minute:$second';
expiryText = '到期时间:$formattedDateTime';
}
}
return Text(
expiryText,
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
),
);
}),
], ],
), ),
), ),
@@ -124,4 +86,4 @@ class UserInfoCard extends StatelessWidget {
), ),
); );
} }
} }
@@ -1,17 +1,28 @@
import 'package:get/get.dart'; 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/utils/kr_log_util.dart';
import 'package:kaer_with_panels/app/services/kr_subscribe_service.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 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart';
import 'package:kaer_with_panels/app/utils/kr_secure_storage.dart';
import '../../../localization/app_translations.dart'; import '../../../localization/app_translations.dart';
import 'package:kaer_with_panels/app/modules/kr_home/controllers/kr_home_controller.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 KRSubscribeService kr_subscribeService = KRSubscribeService();
/// ///
final KRHomeController homeController = Get.find<KRHomeController>(); final KRHomeController homeController = Get.find<KRHomeController>();
///
final RxBool isDebugMode = false.obs;
///
int modeButtonClickCount = 0;
///
DateTime? lastModeButtonClickTime;
/// ///
String kr_getConnectionTypeString() { String kr_getConnectionTypeString() {
final connectionType = KRSingBoxImp.instance.kr_connectionType.value; final connectionType = KRSingBoxImp.instance.kr_connectionType.value;
@@ -30,15 +41,87 @@ class HINodeListController extends GetxController {
KRLogUtil.kr_i('连接类型已更新为: $type', tag: 'HINodeListController'); KRLogUtil.kr_i('连接类型已更新为: $type', tag: 'HINodeListController');
// //
} }
}
// ///
if (homeController.currentSelectedCountry.isNotEmpty) { void kr_handleModeButtonClick() {
KRLogUtil.kr_i('连接类型更新后,检查是否需要重选节点', tag: 'HINodeListController'); final now = DateTime.now();
//
Future.delayed(const Duration(milliseconds: 500), () { // 2
// homeController.checkCountryReselection(KRSingBoxImp.instance.kr_activeGroups); 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');
} }
} }
///
///
void kr_resetDebugMode() {
isDebugMode.value = false;
modeButtonClickCount = 0;
lastModeButtonClickTime = null;
KRLogUtil.kr_i('调试模式已重置', tag: 'HINodeListController');
}
Future<void> kr_handleRefresh() async {
await kr_subscribeService.kr_refreshAll();
try {
final savedNode =
await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG');
if (savedNode != null && savedNode.isNotEmpty) {
homeController.kr_currentNodeName.value = savedNode;
homeController.kr_cutTag.value = savedNode;
homeController.kr_cutSeletedTag.value = savedNode;
}
} catch (_) {}
if (!homeController.kr_isLatency.value) {
homeController.kr_urlTest();
}
}
@override
void onInit() {
super.onInit();
WidgetsBinding.instance.addObserver(this);
}
@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 // hi_node_list_view.dart
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
@@ -9,6 +8,7 @@ import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
import 'package:kaer_with_panels/app/widgets/kr_country_flag.dart'; import 'package:kaer_with_panels/app/widgets/kr_country_flag.dart';
import '../../../model/business/kr_outbound_item.dart'; import '../../../model/business/kr_outbound_item.dart';
import '../../../utils/kr_log_util.dart'; import '../../../utils/kr_log_util.dart';
import 'package:kaer_with_panels/app/utils/kr_secure_storage.dart';
import '../controllers/hi_node_list_controller.dart'; import '../controllers/hi_node_list_controller.dart';
import 'package:kaer_with_panels/app/modules/kr_home/models/kr_home_views_status.dart'; import 'package:kaer_with_panels/app/modules/kr_home/models/kr_home_views_status.dart';
import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart'; import 'package:kaer_with_panels/app/services/singbox_imp/kr_sing_box_imp.dart';
@@ -24,41 +24,18 @@ class HINodeListView extends GetView<HINodeListController> {
static const Color krModernGreen = Color(0xFF4CAF50); static const Color krModernGreen = Color(0xFF4CAF50);
static const Color krModernGreenLight = Color(0xFF81C784); static const Color krModernGreenLight = Color(0xFF81C784);
// UI展示
static final Map<String, int> _fakeDelays = {};
///
int _getDisplayDelay(HINodeListController controller, KROutboundItem item) {
return item.urlTestDelay.value;
// if (controller.homeController.kr_isConnected.value) {
//
// }
// if (!_fakeDelays.containsKey(item.tag)) {
// final random = Random();
// _fakeDelays[item.tag] = 30 + random.nextInt(71); // 30-100ms
// }
// return _fakeDelays[item.tag] ?? 0;
}
/// ms /// ms
/// 0 /// 0
int getFastestNodeDelay( int getFastestNodeDelay(
HINodeListController controller, HINodeListController controller,
List<KROutboundItem> outboundList, List<KROutboundItem> outboundList,
) { ) {
if (outboundList.isEmpty) return 0; 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)); outboundList
return validNodes.first.urlTestDelay.value; .sort((a, b) => a.urlTestDelay.value.compareTo(b.urlTestDelay.value));
return outboundList.first.urlTestDelay.value;
} }
/// ///
@@ -86,23 +63,20 @@ class HINodeListView extends GetView<HINodeListController> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
// 1. 使 Material InkWell // 1. 使 Material InkWell
// //
return Material( return Obx(() {
color: Colors.transparent, // Material InkWell
child: _buildSubscribeList(context) return Material(
// child: _kr_buildRegionList(context) color: Colors.transparent,
); child: controller.isDebugMode.value
? _buildSubscribeList(context)
: _kr_buildRegionList(context),
);
});
} }
/// / /// /
Widget _kr_buildRegionList(BuildContext context) { Widget _kr_buildRegionList(BuildContext context) {
return Obx(() { 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( return _kr_buildListContainer(
context, context,
child: ListView( child: ListView(
@@ -110,21 +84,23 @@ class HINodeListView extends GetView<HINodeListController> {
// 2. 使 children // 2. 使 children
children: [ children: [
if (controller.kr_subscribeService.countryOutboundList.isEmpty) if (controller.kr_subscribeService.countryOutboundList.isEmpty)
_buildEmptyListPlaceholder(context, AppTranslations.kr_home.noRegions) _buildEmptyListPlaceholder(
context, AppTranslations.kr_home.noRegions)
else ...[ else ...[
InkWell( InkWell(
// 🔧 async // 🔧 async
onTap: () async { onTap: () async {
try { try {
final success = controller.homeController
await controller.homeController.kr_performNodeSwitch('auto'); .kr_setSelectedCountryTag('auto');
final success = await controller.homeController
.kr_performNodeSwitch('auto');
if (success) { if (success) {
controller.homeController.kr_currentListStatus.value = controller.homeController.kr_currentListStatus.value =
KRHomeViewsListStatus.kr_none; KRHomeViewsListStatus.kr_none;
} }
} catch (e) { } catch (e) {
KRLogUtil.kr_e('Auto选项切换异常: $e', KRLogUtil.kr_e('Auto选项切换异常: $e', tag: 'NodeListView');
tag: 'NodeListView');
} }
}, },
child: Container( child: Container(
@@ -136,7 +112,8 @@ class HINodeListView extends GetView<HINodeListController> {
), ),
), ),
), ),
padding: EdgeInsets.symmetric(vertical: 12.w, horizontal: 16.w), padding:
EdgeInsets.symmetric(vertical: 12.w, horizontal: 16.w),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
@@ -180,143 +157,193 @@ class HINodeListView extends GetView<HINodeListController> {
], ],
), ),
), ),
Obx(() => controller.homeController.kr_coutryText.value == 'auto' Obx(() =>
? KrLocalImage( controller.homeController.kr_selectedCountryTag.value ==
imageName: 'radio-active-icon', 'auto'
imageType: ImageType.svg, ? KrLocalImage(
width: 16.w, // SVG imageName: 'radio-active-icon',
height: 16.h, imageType: ImageType.svg,
) width: 16.w, // SVG
: KrLocalImage( height: 16.h,
imageName: 'radio-icon', )
imageType: ImageType.svg, : KrLocalImage(
width: 16.w, // SVG imageName: 'radio-icon',
height: 16.h, imageType: ImageType.svg,
)), width: 16.w, // SVG
height: 16.h,
)),
], ],
), ),
), ),
), ),
...controller.kr_subscribeService.countryOutboundList.map((country) { ...controller.kr_subscribeService.countryOutboundList
.map((country) {
return InkWell( return InkWell(
onTap: () { onTap: () async {
// try {
controller.homeController.onCountrySelected(country.country); controller.homeController
.kr_setSelectedCountryTag(country.country);
final fastest = findFastestNode(country.outboundList);
final success = await controller.homeController
.kr_performNodeSwitch(fastest.tag);
if (success) {
controller.homeController.kr_currentListStatus.value =
KRHomeViewsListStatus.kr_none;
}
} catch (e) {
KRLogUtil.kr_e('国家选择切换异常: $e', tag: 'NodeListView');
}
}, },
child: _kr_buildCountryListItem(context, country: country), child: _kr_buildCountryListItem(context, country: country),
); );
}).toList(), }).toList(),
] ]
] // ] //
), ),
); );
}); });
} }
/// ///
Widget _buildSubscribeList(BuildContext context) { Widget _buildSubscribeList(BuildContext context) {
return Obx(() { 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( return _kr_buildListContainer(
context, context,
child: ListView( child: ListView(
padding: EdgeInsets.symmetric(vertical: 8.w), padding: EdgeInsets.symmetric(vertical: 8.w),
// 2. 使 children // 2. 使 children
children: [ children: [
if (controller.kr_subscribeService.allList.isEmpty) ...[
_buildEmptyListPlaceholder(context, AppTranslations.kr_home.noNodes) InkWell(
else ...[ // 🔧 async
InkWell( onTap: () async {
onTap: () { try {
controller.homeController.kr_selectNode('auto'); final success = await controller.homeController
controller.homeController.kr_currentListStatus.value = .kr_performNodeSwitch('auto');
KRHomeViewsListStatus.kr_none; if (success) {
}, controller.homeController.kr_currentListStatus.value =
child: Container( KRHomeViewsListStatus.kr_none;
decoration: BoxDecoration( }
border: Border( } catch (e) {
bottom: BorderSide( KRLogUtil.kr_e('Auto选项切换异常: $e', tag: 'NodeListView');
color: Colors.white.withOpacity(0.3), }
width: 1.0, },
), child: Container(
),
),
padding: EdgeInsets.symmetric(vertical: 12.w, horizontal: 16.w),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 32.w,
height: 22.w,
decoration: BoxDecoration( decoration: BoxDecoration(
// 2. border: Border(
color: Theme.of(context).primaryColor, bottom: BorderSide(
), color: Colors.white.withOpacity(0.3),
// 4. 使 Center width: 1.0,
child: Center( ),
child: KrLocalImage(
imageName: "hi-home-logo",
width: 14.w,
height: 14.h,
), ),
), ),
), padding:
SizedBox(width: 8.w), EdgeInsets.symmetric(vertical: 12.w, horizontal: 16.w),
Expanded( child: Row(
child: Column( crossAxisAlignment: CrossAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Container(
'自动匹配最快网络', // width: 32.w,
style: KrAppTextStyle( height: 22.w,
fontSize: 14, decoration: BoxDecoration(
color: Colors.white, // 2.
fontWeight: FontWeight.w600, // 600 color: Theme.of(context).primaryColor,
),
// 4. 使 Center
child: Center(
child: KrLocalImage(
imageName: "hi-home-logo",
width: 14.w,
height: 14.h,
),
), ),
), ),
// 2. Text: "根据网络IP自动匹配最快线路" SizedBox(width: 8.w),
Text( Expanded(
'根据网络IP自动匹配最快线路', // child: Column(
style: KrAppTextStyle( crossAxisAlignment: CrossAxisAlignment.start,
fontSize: 10, children: [
color: Colors.white, Text(
'自动匹配最快网络', //
style: KrAppTextStyle(
fontSize: 14,
color: Colors.white,
fontWeight: FontWeight.w600, // 600
),
),
// 2. Text: "根据网络IP自动匹配最快线路"
Text(
'根据网络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,
),
);
}),*/
],
), ),
), ),
Obx(() =>
controller.homeController.kr_cutTag.value == 'auto'
? KrLocalImage(
imageName: 'radio-active-icon',
imageType: ImageType.svg,
width: 16.w, // SVG
height: 16.h,
)
: KrLocalImage(
imageName: 'radio-icon',
imageType: ImageType.svg,
width: 16.w, // SVG
height: 16.h,
)),
], ],
), ),
), ),
Obx(() => controller.homeController.kr_cutTag.value == 'auto' ),
? KrLocalImage( ...controller.kr_subscribeService.allList().map((item) {
imageName: 'radio-active-icon', return InkWell(
imageType: ImageType.svg, // 🔧 async
width: 16.w, // SVG onTap: () async {
height: 16.h, try {
) KRLogUtil.kr_i('🔄 用户点击节点: ${item.tag}');
: KrLocalImage( final success = await controller.homeController
imageName: 'radio-icon', .kr_performNodeSwitch(item.tag);
imageType: ImageType.svg, if (success) {
width: 16.w, // SVG controller.homeController.kr_currentListStatus.value =
height: 16.h, KRHomeViewsListStatus.kr_none;
)), }
], } catch (e) {
), KRLogUtil.kr_e('节点切换异常: $e', tag: 'NodeListView');
}
},
child: _kr_buildNodeListItem(context, item: item),
);
}).toList(),
]
] //
), ),
),
...controller.kr_subscribeService.allList.map((item) {
return InkWell(
onTap: () => _onNodeSelected(item),
child: _kr_buildNodeListItem(context, item: item),
);
}).toList(),
]
] //
),
); );
}); });
} }
@@ -328,7 +355,8 @@ class HINodeListView extends GetView<HINodeListController> {
alignment: Alignment.center, alignment: Alignment.center,
child: Text( child: Text(
text, text,
style: KrAppTextStyle(fontSize: 14, color: Theme.of(context).textTheme.bodySmall?.color), style: KrAppTextStyle(
fontSize: 14, color: Theme.of(context).textTheme.bodySmall?.color),
), ),
); );
} }
@@ -344,17 +372,9 @@ 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 /// UI
Widget _kr_buildNodeListItem(BuildContext context, {required KROutboundItem item}) { Widget _kr_buildNodeListItem(BuildContext context,
{required KROutboundItem item}) {
return Container( return Container(
key: ValueKey(item.id), key: ValueKey(item.id),
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -369,71 +389,92 @@ class HINodeListView extends GetView<HINodeListController> {
padding: EdgeInsets.symmetric(vertical: 12.w, horizontal: 16.w), padding: EdgeInsets.symmetric(vertical: 12.w, horizontal: 16.w),
child: Row( child: Row(
children: [ children: [
KRCountryFlag(countryCode: item.country, width: 30.w, height: 20.w, isCircle: false, maintainSize: false), KRCountryFlag(
countryCode: item.country,
width: 30.w,
height: 20.w,
isCircle: false,
maintainSize: false),
SizedBox(width: 12.w), SizedBox(width: 12.w),
Expanded( Expanded(
child: Column( child: Obx(() {
crossAxisAlignment: CrossAxisAlignment.start, final isDebug = controller.isDebugMode.value;
children: [
Row( final text = isDebug
children: [ ? '${controller.homeController.kr_getCountryFullName(item.country)} - ${item.tag}'
Flexible( : '${controller.homeController.kr_getCountryFullName(item.country)}';
child: Text(
controller.homeController.kr_getCountryFullName(item.country), return Text(
style: KrAppTextStyle(fontSize: 14, color: Colors.white), text,
overflow: TextOverflow.ellipsis, style: KrAppTextStyle(
maxLines: 1, fontSize: 16,
), fontWeight: FontWeight.w500,
), color: Colors.white,
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()),
],
), ),
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(() => controller.homeController.kr_cutTag.value == item.tag Obx(() {
? KrLocalImage( // 1.
imageName: 'radio-active-icon', int displayDelay;
imageType: ImageType.svg, bool isTesting = controller.homeController.kr_isLatency.value;
width: 16.w, // SVG
height: 16.h, //
) print(
: KrLocalImage( '🔄 _kr_buildNodeListItem节点: item.tag=${item.tag}, country=${item.country}');
imageName: 'radio-icon',
imageType: ImageType.svg, //
width: 16.w, // SVG displayDelay = item.urlTestDelay.value;
height: 16.h, 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(() {
final selected =
controller.homeController.kr_cutSeletedTag.value == item.tag;
return selected
? KrLocalImage(
imageName: 'radio-active-icon',
imageType: ImageType.svg,
width: 16.w,
height: 16.h,
)
: KrLocalImage(
imageName: 'radio-icon',
imageType: ImageType.svg,
width: 16.w,
height: 16.h,
);
}),
], ],
), ),
); );
@@ -441,18 +482,6 @@ class HINodeListView extends GetView<HINodeListController> {
/// UI /// UI
Widget _kr_buildCountryListItem(BuildContext context, {required country}) { 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( return Container(
key: ValueKey(country), key: ValueKey(country),
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -467,45 +496,97 @@ class HINodeListView extends GetView<HINodeListController> {
padding: EdgeInsets.symmetric(vertical: 12.w, horizontal: 16.w), padding: EdgeInsets.symmetric(vertical: 12.w, horizontal: 16.w),
child: Row( child: Row(
children: [ children: [
KRCountryFlag(countryCode: country.country, width: 30.w, height: 20.w, isCircle: false, maintainSize: false), KRCountryFlag(
countryCode: country.country,
width: 30.w,
height: 20.w,
isCircle: false,
maintainSize: false),
SizedBox(width: 12.w), SizedBox(width: 12.w),
Expanded( Expanded(
child: Text( child: Text(
controller.homeController.kr_getCountryFullName(country.country), controller.homeController.kr_getCountryFullName(country.country),
style: KrAppTextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: Colors.white), style: KrAppTextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.white),
), ),
), ),
Obx(() { Obx(() {
final int delay = getFastestNodeDelay(controller, country.outboundList); // 1.
return Text( int displayDelay =
delay == 0 getFastestNodeDelay(controller, country.outboundList);
? '' bool isTesting = controller.homeController.kr_isLatency.value;
: delay >= 3000
? AppTranslations.kr_home.timeout // 2.
: '${delay}ms', String delayText;
style: KrAppTextStyle( Color delayColor;
fontSize: 10,
color: getLatencyColor(delay), // 3.
fontWeight: FontWeight.w500, 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,
// ),
// );
return Container(
width: 4,
height: 4,
// 使 BoxDecoration
decoration: BoxDecoration(
color: delayColor, // 使
// shape: BoxShape.circle, // 使 BoxShape.rectangle
), ),
); );
}), }),
SizedBox(width: 12.w), SizedBox(width: 12.w),
Obx(() => controller.homeController.kr_coutryText.value == country.country Obx(() {
? KrLocalImage( final selectedCountryField =
imageName: 'radio-active-icon', controller.homeController.kr_coutryText.value;
imageType: ImageType.svg, if (selectedCountryField == 'auto') {
width: 16.w, // SVG return KrLocalImage(
height: 16.h, imageName: 'radio-icon',
) imageType: ImageType.svg,
: KrLocalImage( width: 16.w,
imageName: 'radio-icon', height: 16.h,
imageType: ImageType.svg, );
width: 16.w, // SVG }
height: 16.h, final selected = selectedCountryField == country.country;
)), return selected
? KrLocalImage(
imageName: 'radio-active-icon',
imageType: ImageType.svg,
width: 16.w,
height: 16.h,
)
: KrLocalImage(
imageName: 'radio-icon',
imageType: ImageType.svg,
width: 16.w,
height: 16.h,
);
}),
], ],
), ),
); );
} }
} }
@@ -19,10 +19,47 @@ class HINodePageView extends GetView<HINodeListController> {
return HIBaseScaffold( return HIBaseScaffold(
child: Stack( child: Stack(
children: [ 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( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// //
Padding( Padding(
padding: EdgeInsets.only(left: 100.w, right: 60.w), padding: EdgeInsets.only(left: 100.w, right: 60.w),
@@ -122,8 +159,7 @@ class HINodePageView extends GetView<HINodeListController> {
padding: EdgeInsets.only(left: 60.w, right: 60.w, bottom: 90.w), // HIHelpEntrance预留空间 padding: EdgeInsets.only(left: 60.w, right: 60.w, bottom: 90.w), // HIHelpEntrance预留空间
// 2. Padding 使 EasyRefresh // 2. Padding 使 EasyRefresh
child: EasyRefresh( child: EasyRefresh(
// 3. onRefresh onRefresh: controller.kr_handleRefresh,
onRefresh: controller.kr_subscribeService.kr_refreshAll,
// 4. Header // 4. Header
header: ClassicHeader( header: ClassicHeader(
dragText: '下拉刷新', dragText: '下拉刷新',
@@ -159,7 +195,12 @@ class HINodePageView extends GetView<HINodeListController> {
}) { }) {
return Expanded( return Expanded(
child: GestureDetector( child: GestureDetector(
onTap: onTap, onTap: () {
//
controller.kr_handleModeButtonClick();
//
onTap();
},
child: AnimatedContainer( child: AnimatedContainer(
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
padding: EdgeInsets.symmetric(vertical: 4.w), padding: EdgeInsets.symmetric(vertical: 4.w),
@@ -186,4 +227,4 @@ class HINodePageView extends GetView<HINodeListController> {
), ),
); );
} }
} }
@@ -222,22 +222,22 @@ class HIUserInfoController extends GetxController {
String iconName = 'devices'; String iconName = 'devices';
if (userAgent.contains('Android') || userAgent.toLowerCase().contains('android')) { if (userAgent.contains('Android') || userAgent.toLowerCase().contains('android')) {
deviceType = AppTranslations.kr_deviceManagement.deviceTypeAndroid; deviceType = 'Android' ;// AppTranslations.kr_deviceManagement.deviceTypeAndroid;
iconName = 'phone_android'; iconName = 'phone_android';
} else if (userAgent.contains('iOS') || userAgent.contains('iPhone') || userAgent.toLowerCase().contains('ios')) { } else if (userAgent.contains('iOS') || userAgent.contains('iPhone') || userAgent.toLowerCase().contains('ios')) {
deviceType = AppTranslations.kr_deviceManagement.deviceTypeIos; deviceType = 'iPhone'; // AppTranslations.kr_deviceManagement.deviceTypeIos;
iconName = 'phone_iphone'; iconName = 'phone_iphone';
} else if (userAgent.contains('iPad')) { } else if (userAgent.contains('iPad')) {
deviceType = AppTranslations.kr_deviceManagement.deviceTypeIpad; deviceType = 'iPad';// AppTranslations.kr_deviceManagement.deviceTypeIpad;
iconName = 'tablet'; iconName = 'tablet';
} else if (userAgent.contains('macOS') || userAgent.contains('Mac') || userAgent.toLowerCase().contains('mac')) { } else if (userAgent.contains('macOS') || userAgent.contains('Mac') || userAgent.toLowerCase().contains('mac')) {
deviceType = AppTranslations.kr_deviceManagement.deviceTypeMacos; deviceType = 'Mac';// AppTranslations.kr_deviceManagement.deviceTypeMacos;
iconName = 'desktop_mac'; iconName = 'desktop_mac';
} else if (userAgent.contains('Windows') || userAgent.toLowerCase().contains('windows')) { } else if (userAgent.contains('Windows') || userAgent.toLowerCase().contains('windows')) {
deviceType = AppTranslations.kr_deviceManagement.deviceTypeWindows; deviceType = 'Windows'; //AppTranslations.kr_deviceManagement.deviceTypeWindows;
iconName = 'computer'; iconName = 'computer';
} else if (userAgent.contains('Linux') || userAgent.toLowerCase().contains('linux')) { } else if (userAgent.contains('Linux') || userAgent.toLowerCase().contains('linux')) {
deviceType = AppTranslations.kr_deviceManagement.deviceTypeLinux; deviceType = 'Linux';//AppTranslations.kr_deviceManagement.deviceTypeLinux;
iconName = 'computer'; iconName = 'computer';
} }
@@ -18,13 +18,15 @@ import 'package:kaer_with_panels/app/widgets/hi_help_entrance.dart';
import 'package:kaer_with_panels/app/common/app_run_data.dart'; import 'package:kaer_with_panels/app/common/app_run_data.dart';
import 'package:kaer_with_panels/app/modules/kr_home/controllers/kr_home_controller.dart'; import 'package:kaer_with_panels/app/modules/kr_home/controllers/kr_home_controller.dart';
import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart'; import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart';
import 'package:kaer_with_panels/app/common/app_config.dart';
import 'package:kaer_with_panels/app/widgets/kr_subscription_expiry_text.dart';
class HIUserInfoView extends GetView<HIUserInfoController> { class HIUserInfoView extends GetView<HIUserInfoController> {
const HIUserInfoView({super.key}); const HIUserInfoView({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDeviceLogin = KRAppRunData.getInstance().isDeviceLogin(); final isDeviceLogin = KRAppRunData.getInstance().isDeviceLogin();
return HIBaseScaffold( return HIBaseScaffold(
child: Stack( child: Stack(
children: [ children: [
@@ -68,75 +70,33 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Obx(() { Obx(() {
final account = KRAppRunData.getInstance().kr_account.value; final account = KRAppRunData.getInstance()
.kr_account
.value;
final isDeviceLogin = account != null &&
account.startsWith('9000');
final accountText = isDeviceLogin
? '待绑定'
: '${KRAppRunData.getInstance().kr_account.value.toString()}';
return Text( return Text(
(account != null && account.isNotEmpty) ? account : '未绑定', accountText,
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 20.sp, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
height: 0.9, //height: 0.9,
), ),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
); );
}), }),
Obx(() { KRSubscriptionExpiryText(
final userId = KRAppRunData.getInstance().kr_userId.value; expireTimeProvider: () => controller
return Text( .kr_subscribeService
'ID: ${(userId != null && userId.toString().isNotEmpty) ? userId : ''}', .kr_currentSubscribe
style: TextStyle( .value
color: Colors.white.withOpacity(0.85), ?.expireTime,
fontSize: 14.sp, ),
fontWeight: FontWeight.w500,
),
);
}),
Obx(() {
final currentSubscribe =
controller.kr_subscribeService.kr_currentSubscribe.value;
String expiryText;
if (currentSubscribe == null) {
expiryText = '尚未购买套餐';
} else {
final now = DateTime.now();
DateTime? expireDateTime;
try {
expireDateTime =
DateTime.parse(currentSubscribe.expireTime);
} catch (e) {
expireDateTime = null;
}
if (expireDateTime == null) {
expiryText = '套餐信息无效';
} else if (expireDateTime.isBefore(now)) {
final formattedExpireDate =
'${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')}';
expiryText = '已于 $formattedExpireDate 到期';
} else {
final year = expireDateTime.year;
final month = expireDateTime.month.toString().padLeft(2, '0');
final day = expireDateTime.day.toString().padLeft(2, '0');
final hour = expireDateTime.hour.toString().padLeft(2, '0');
final minute = expireDateTime.minute.toString().padLeft(2, '0');
final second = expireDateTime.second.toString().padLeft(2, '0');
// 2.
final formattedDateTime = '$year/$month/$day $hour:$minute:$second';
expiryText = '到期时间:$formattedDateTime';
}
}
return Text(
expiryText,
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
),
);
}),
], ],
), ),
), ),
@@ -146,7 +106,8 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
SizedBox(height: 12.w), SizedBox(height: 12.w),
// //
Obx(() { Obx(() {
final isDeviceLogin = KRAppRunData.getInstance().isDeviceLogin(); final isDeviceLogin =
KRAppRunData.getInstance().isDeviceLogin();
if (!isDeviceLogin) return SizedBox.shrink(); if (!isDeviceLogin) return SizedBox.shrink();
return InkWell( return InkWell(
onTap: () { onTap: () {
@@ -163,20 +124,24 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
} }
}, },
child: Container( child: Container(
margin: EdgeInsets.symmetric(horizontal: 0.w).copyWith(bottom: 10.w), // margin: EdgeInsets.symmetric(horizontal: 0)
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.w), .copyWith(bottom: 10), //
padding: EdgeInsets.symmetric(
horizontal: 16, vertical: 10),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(22.w), borderRadius: BorderRadius.circular(22),
border: Border.all(color: Colors.white, width: 2), border:
Border.all(color: Colors.white, width: 2),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
isDeviceLogin ? '绑定邮箱' : '修改密码', isDeviceLogin ? '绑定邮箱' : '修改密码',
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 16.sp, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
), ),
@@ -196,7 +161,7 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
if (controller.isLoading.value) { if (controller.isLoading.value) {
// 使 Padding // 使 Padding
return Padding( return Padding(
padding: EdgeInsets.only(top: 20.w), padding: EdgeInsets.only(top: 20),
child: const Center( child: const Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
), ),
@@ -204,18 +169,20 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
} }
return Padding( return Padding(
padding: EdgeInsets.symmetric(horizontal: 0.w), padding: EdgeInsets.symmetric(horizontal: 0),
child: GridView.builder( child: GridView.builder(
// 1. GridView SingleChildScrollView // 1. GridView SingleChildScrollView
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
// 2. GridView // 2. GridView
shrinkWrap: true, shrinkWrap: true,
// 3. // 3.
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, // crossAxisCount: 2, //
crossAxisSpacing: 10.w, // crossAxisSpacing: 10, //
mainAxisSpacing: 10.w, // mainAxisSpacing: 10, //
childAspectRatio: 1.5, // mainAxisExtent: 76,
// childAspectRatio: 1.5, //
), ),
// 4. itemCount itemBuilder // 4. itemCount itemBuilder
itemCount: controller.devices.length + 1, itemCount: controller.devices.length + 1,
@@ -229,13 +196,15 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
onDelete: (id) { onDelete: (id) {
HIDialog.show( HIDialog.show(
customMessageWidget: Padding( customMessageWidget: Padding(
padding: EdgeInsets.only(top: 16.w), padding: EdgeInsets.only(top: 16),
child: Text( child: Text(
'请确认是否移除此设备?', '请确认是否移除此设备?',
style: KrAppTextStyle( style: KrAppTextStyle(
color: color: Theme.of(context)
Theme.of(context).textTheme.bodyMedium?.color, .textTheme
fontSize: 14.sp, .bodyMedium
?.color,
fontSize: 14,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
), ),
@@ -247,8 +216,7 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
}, },
onCancel: () { onCancel: () {
controller.deleteDevice(id); controller.deleteDevice(id);
} });
);
}, },
); );
} else { } else {
@@ -268,9 +236,11 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
), ),
// 👇 3: Expanded // 👇 3: Expanded
Obx(() { Obx(() {
if((KRAppRunData.getInstance().kr_account.value != null && if ((KRAppRunData.getInstance().kr_account.value != null &&
KRAppRunData.getInstance().kr_account.value!.startsWith('9000'))) KRAppRunData.getInstance()
return SizedBox.shrink(); .kr_account
.value!
.startsWith('9000'))) return SizedBox.shrink();
return Padding( return Padding(
padding: EdgeInsets.symmetric(horizontal: 40.w), padding: EdgeInsets.symmetric(horizontal: 40.w),
child: Column( child: Column(
@@ -285,7 +255,10 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
'注销账号后,所有此账号内的剩余套餐和账户数据将被清空,无法找回。', // 1. '注销账号后,所有此账号内的剩余套餐和账户数据将被清空,无法找回。', // 1.
textAlign: TextAlign.left, // textAlign: TextAlign.left, //
style: KrAppTextStyle( style: KrAppTextStyle(
color: Theme.of(context).textTheme.bodyMedium?.color, color: Theme.of(context)
.textTheme
.bodyMedium
?.color,
fontSize: 14.sp, fontSize: 14.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
@@ -299,8 +272,7 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
Get.toNamed(Routes.KR_DELETE_ACCOUNT); Get.toNamed(Routes.KR_DELETE_ACCOUNT);
}, },
// 4. onConfirm // 4. onConfirm
onConfirm: () { onConfirm: () {},
},
); );
}, },
child: Container( child: Container(
@@ -308,7 +280,8 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
padding: EdgeInsets.symmetric(vertical: 12.w), padding: EdgeInsets.symmetric(vertical: 12.w),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.w), borderRadius: BorderRadius.circular(24.w),
border: Border.all(color: const Color(0xFFFF2ED1), width: 2), border: Border.all(
color: const Color(0xFFFF2ED1), width: 2),
), ),
alignment: Alignment.center, alignment: Alignment.center,
child: Text( child: Text(
@@ -331,7 +304,10 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
'确认要退出您的账号?', // 1. '确认要退出您的账号?', // 1.
textAlign: TextAlign.center, // textAlign: TextAlign.center, //
style: KrAppTextStyle( style: KrAppTextStyle(
color: Theme.of(context).textTheme.bodyMedium?.color, color: Theme.of(context)
.textTheme
.bodyMedium
?.color,
fontSize: 14.sp, fontSize: 14.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
@@ -340,15 +316,14 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
cancelText: '确认', cancelText: '确认',
confirmText: '返回', confirmText: '返回',
onCancel: () async { onCancel: () async {
final currentDevice = controller.devices.firstWhere( final currentDevice =
(device) => device['is_current'] == true, controller.devices.firstWhere(
(device) => device['is_current'] == true,
); );
final deviceId = currentDevice['id'] as String; final deviceId = currentDevice['id'] as String;
await controller.deleteDevice(deviceId); await controller.deleteDevice(deviceId);
}, },
onConfirm: () { onConfirm: () {},
},
); );
}, },
child: Container( child: Container(
@@ -383,6 +358,7 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
), ),
); );
} }
/// ///
Widget _buildDeviceCard({ Widget _buildDeviceCard({
required BuildContext context, required BuildContext context,
@@ -403,13 +379,14 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
final deviceType = deviceInfo['type'] as String; final deviceType = deviceInfo['type'] as String;
final iconName = deviceInfo['icon'] as String; final iconName = deviceInfo['icon'] as String;
return Stack( // 使 Stack return Stack(
// 使 Stack
children: [ children: [
// //
Container( Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 14.w), padding: EdgeInsets.symmetric(horizontal: 16, vertical: 14),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.w), // borderRadius: BorderRadius.circular(24), //
border: Border.all(color: Colors.white, width: 2), border: Border.all(color: Colors.white, width: 2),
), ),
child: Column( child: Column(
@@ -418,25 +395,25 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
children: [ children: [
Row( Row(
children: [ children: [
Icon(_getIconData(iconName), color: Colors.white, size: 18.w), Icon(_getIconData(iconName), color: Colors.white, size: 18),
SizedBox(width: 6.w), SizedBox(width: 6),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'设备:${_extractDeviceModel(userAgent)}', '设备:${deviceType}',
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 10.sp, // fontSize: 10, //
fontWeight: FontWeight.w600), fontWeight: FontWeight.w600),
), ),
SizedBox(height: 4.h), SizedBox(height: 4),
Text( 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( style: TextStyle(
color: Colors.white.withOpacity(0.9), color: Colors.white.withOpacity(0.9),
fontSize: 10.sp, fontSize: 10,
fontWeight: FontWeight.w500), fontWeight: FontWeight.w500),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@@ -490,44 +467,44 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
Widget _buildAddDeviceCard() { Widget _buildAddDeviceCard() {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
print('可用设备 2222');
print('可用设备 ${controller.kr_subscribeService.kr_currentSubscribe}');
// //
final device_limit = AppConfig.getInstance().device_limit;
HIDialog.show( HIDialog.show(
customMessageWidget: Padding( customMessageWidget: Padding(
padding: EdgeInsets.only(top: 16.w, bottom: 16.w), padding: EdgeInsets.only(top: 16.w, bottom: 16.w),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, // Column mainAxisSize: MainAxisSize.min, // Column
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// 2. Text // 2. Text
Text( Text(
'请使用邮箱登录新设备', '请使用绑定邮箱登录新设备',
style: KrAppTextStyle( style: KrAppTextStyle(
color: Colors.black, // 使 color: Colors.black, // 使
fontSize: 14.sp, fontSize: 14.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
),
), ),
SizedBox(height: 20.w), ),
Obx(() { SizedBox(height: 20.w),
final current = controller.kr_subscribeService.kr_currentSubscribe.value; Text(
"每个账号最多允许同时使用${device_limit}台设备同时在线",
// current null 0 style: KrAppTextStyle(
final deviceLimit = current?.deviceLimit ?? 0; color: Colors.black,
fontSize: 14.sp,
return Text( fontWeight: FontWeight.w600,
'每个账号最多允许同时使用$deviceLimit台设备同时在线', ),
style: KrAppTextStyle( )
color: Colors.black, ],
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
);
}),
],
),
), ),
),
confirmText: KRAppRunData.getInstance().isDeviceLogin() ? '前往' : null,
cancelText: KRAppRunData.getInstance().isDeviceLogin() ? '取消' : null,
onConfirm: () {
Get.toNamed(
Routes.MR_LOGIN,
arguments: {'entry': 'bind_email'},
);
},
); );
}, },
child: Container( child: Container(
@@ -585,7 +562,7 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
); );
} }
String _extractDeviceModel(String deviceName) { String _extractDeviceModel(String deviceName, String deviceType) {
// //
final RegExp regExp = RegExp(r'\((.*?)\)'); final RegExp regExp = RegExp(r'\((.*?)\)');
final Match? match = regExp.firstMatch(deviceName); final Match? match = regExp.firstMatch(deviceName);
@@ -627,4 +604,4 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
return Icons.devices; return Icons.devices;
} }
} }
} }
@@ -6,6 +6,7 @@ import 'package:kaer_with_panels/app/model/enum/kr_request_type.dart';
import 'package:kaer_with_panels/app/services/api_service/kr_auth_api.dart'; import 'package:kaer_with_panels/app/services/api_service/kr_auth_api.dart';
import 'package:kaer_with_panels/app/utils/kr_common_util.dart'; import 'package:kaer_with_panels/app/utils/kr_common_util.dart';
import 'package:kaer_with_panels/app/services/kr_subscribe_service.dart'; import 'package:kaer_with_panels/app/services/kr_subscribe_service.dart';
import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart';
import '../../../localization/app_translations.dart'; import '../../../localization/app_translations.dart';
@@ -65,6 +66,11 @@ class KRDeleteAccountController extends GetxController {
KRCommonUtil.kr_showToast(error.msg); KRCommonUtil.kr_showToast(error.msg);
}, },
(success) { (success) {
HIDialog.show(
title: '*重要提示',
message:
'验证邮件已发送至邮箱,如无法找到,请检查垃圾邮件箱或营销邮件箱。',
);
kr_canSendCode.value = false; kr_canSendCode.value = false;
kr_countdown.value = 60; kr_countdown.value = 60;
@@ -4,11 +4,11 @@ import 'package:get/get.dart';
import 'package:kaer_with_panels/app/common/app_run_data.dart'; import 'package:kaer_with_panels/app/common/app_run_data.dart';
import 'package:kaer_with_panels/app/widgets/hi_base_scaffold.dart'; import 'package:kaer_with_panels/app/widgets/hi_base_scaffold.dart';
import 'package:kaer_with_panels/app/widgets/kr_local_image.dart'; import 'package:kaer_with_panels/app/widgets/kr_local_image.dart';
import 'package:kaer_with_panels/app/widgets/kr_subscription_expiry_text.dart';
import '../controllers/kr_delete_account_controller.dart'; import '../controllers/kr_delete_account_controller.dart';
import 'package:kaer_with_panels/app/localization/app_translations.dart'; import 'package:kaer_with_panels/app/localization/app_translations.dart';
import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart'; import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
class KRDeleteAccountView extends GetView<KRDeleteAccountController> { class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
const KRDeleteAccountView({super.key}); const KRDeleteAccountView({super.key});
@@ -46,7 +46,8 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
padding: EdgeInsets.symmetric(vertical: 12.w), padding: EdgeInsets.symmetric(vertical: 12.w),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.w), borderRadius: BorderRadius.circular(24.w),
border: Border.all(color: const Color(0xFFFF2ED1), width: 2), border:
Border.all(color: const Color(0xFFFF2ED1), width: 2),
), ),
alignment: Alignment.center, alignment: Alignment.center,
child: Text( child: Text(
@@ -87,7 +88,6 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
); );
} }
Widget _buildUserInfoSection() { Widget _buildUserInfoSection() {
return Row( return Row(
children: [ children: [
@@ -129,12 +129,16 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
); );
}), }),
Obx(() { Obx(() {
final _ = KRAppRunData.getInstance().kr_isLogin.value; final account = KRAppRunData.getInstance()
final userId = (KRAppRunData.getInstance().kr_userId.value ?? '').toString(); .kr_account
final deviceId = KRAppRunData.getInstance().deviceId ?? ''; .value;
final isDeviceLogin = account != null &&
account.startsWith('9000');
final accountText = isDeviceLogin
? '待绑定'
: '${KRAppRunData.getInstance().kr_account.value.toString()}';
return Text( return Text(
'ID: ${userId.isNotEmpty ? userId : deviceId}', accountText,
style: TextStyle( style: TextStyle(
color: Colors.white.withOpacity(0.85), color: Colors.white.withOpacity(0.85),
fontSize: 14.sp, fontSize: 14.sp,
@@ -142,51 +146,14 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
), ),
); );
}), }),
Obx(() { KRSubscriptionExpiryText(
final currentSubscribe = expireTimeProvider: () => controller
controller.kr_subscribeService.kr_currentSubscribe.value; .kr_subscribeService.kr_currentSubscribe.value?.expireTime,
String expiryText; style: TextStyle(
color: Colors.white,
if (currentSubscribe == null) { fontSize: 12.sp,
expiryText = '尚未购买套餐'; ),
} else { ),
final now = DateTime.now();
DateTime? expireDateTime;
try {
expireDateTime =
DateTime.parse(currentSubscribe.expireTime);
} catch (e) {
expireDateTime = null;
}
if (expireDateTime == null) {
expiryText = '套餐信息无效';
} else if (expireDateTime.isBefore(now)) {
final formattedExpireDate =
'${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')}';
expiryText = '已于 $formattedExpireDate 到期';
} else {
expiryText = '到期时间:${expireDateTime}';final year = expireDateTime.year;
final month = expireDateTime.month.toString().padLeft(2, '0');
final day = expireDateTime.day.toString().padLeft(2, '0');
final hour = expireDateTime.hour.toString().padLeft(2, '0');
final minute = expireDateTime.minute.toString().padLeft(2, '0');
final second = expireDateTime.second.toString().padLeft(2, '0');
// 2.
final formattedDateTime = '$year/$month/$day $hour:$minute:$second';
expiryText = '到期时间:$formattedDateTime';
}
}
return Text(
expiryText,
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
),
);
}),
], ],
), ),
), ),
@@ -201,6 +168,7 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
child: TextField( child: TextField(
controller: controller.kr_codeController, controller: controller.kr_codeController,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
textInputAction: TextInputAction.done,
style: KrAppTextStyle( style: KrAppTextStyle(
fontSize: 16, fontSize: 16,
color: Colors.white, color: Colors.white,
@@ -212,7 +180,8 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
color: const Color(0xFFA6A6A6), color: const Color(0xFFA6A6A6),
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
contentPadding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.w), contentPadding:
EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.w),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.w), borderRadius: BorderRadius.circular(25.w),
borderSide: BorderSide(color: Colors.white, width: 2), borderSide: BorderSide(color: Colors.white, width: 2),
@@ -232,14 +201,15 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
suffixIcon: Padding( suffixIcon: Padding(
padding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 5.w), padding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 5.w),
child: Obx(() { child: Obx(() {
return GestureDetector( return GestureDetector(
onTap: controller.kr_canSendCode.value ? controller.kr_sendCode : null, onTap: controller.kr_canSendCode.value
? controller.kr_sendCode
: null,
child: Container( child: Container(
width: 100.w, width: 100.w,
alignment: Alignment.center, alignment: Alignment.center,
decoration: BoxDecoration( decoration: BoxDecoration(
color: controller.kr_canSendCode.value color: controller.kr_canSendCode.value
? Theme.of(Get.context!).primaryColor ? Theme.of(Get.context!).primaryColor
: const Color(0xFFD5D5D5), : const Color(0xFFD5D5D5),
borderRadius: BorderRadius.circular(100.r), // borderRadius: BorderRadius.circular(100.r), //
@@ -253,7 +223,7 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: controller.kr_canSendCode.value color: controller.kr_canSendCode.value
? Colors.black ? Colors.black
: const Color(0xFF464655) , : const Color(0xFF464655),
), ),
), ),
), ),
File diff suppressed because it is too large Load Diff
@@ -34,7 +34,7 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
print('🔵 Switch UI 更新: status=${status.runtimeType}, isConnected=$isConnected, isSwitching=$isSwitching'); print('🔵 Switch UI 更新: status=${status.runtimeType}, isConnected=$isConnected, isSwitching=$isSwitching');
final isShow = isConnected; // delay == -1 || isConnected; final isShow = delay == -1 || isConnected;
final Color buttonColor = Theme.of(context).primaryColor; final Color buttonColor = Theme.of(context).primaryColor;
final double screenWidth = Get.width; final double screenWidth = Get.width;
@@ -86,8 +86,17 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
print('🔵 Switch UI 正在更新,切换中点击了按钮: status=${status.runtimeType}, isConnected=$isConnected, isSwitching=$isSwitching'); print('🔵 Switch UI 正在更新,切换中点击了按钮: status=${status.runtimeType}, isConnected=$isConnected, isSwitching=$isSwitching');
return; return;
} }
final hasValidSubscription = final current = controller.kr_subscribeService.kr_currentSubscribe.value;
controller.kr_subscribeService.kr_availableSubscribes.isNotEmpty; bool hasValidSubscription = false;
if (current != null) {
DateTime? expire;
try {
expire = DateTime.parse(current.expireTime);
} catch (_) {
expire = null;
}
hasValidSubscription = expire != null && expire.isAfter(DateTime.now());
}
if (hasValidSubscription) { if (hasValidSubscription) {
controller.kr_toggleSwitch(!controller.kr_isConnected.value); controller.kr_toggleSwitch(!controller.kr_isConnected.value);
} else { } else {
@@ -291,4 +300,3 @@ class _ContinuousRippleEffectState extends State<ContinuousRippleEffect>
); );
} }
} }
@@ -137,7 +137,7 @@ class _HISubscriptionCornerButtonState extends State<HISubscriptionCornerButton>
if(currentRoute.isEmpty) return if(currentRoute.isEmpty) return
print('🔥 当前路由: $currentRoute'); print('🔥 当前路由: $currentRoute');
if (currentRoute == Routes.KR_PURCHASE_MEMBERSHIP) { if (currentRoute == Routes.KR_PURCHASE_MEMBERSHIP || currentRoute == Routes.KR_ORDER_STATUS ) {
print('🔥 已经在 KRPurchaseMembership 页面,无需跳转'); print('🔥 已经在 KRPurchaseMembership 页面,无需跳转');
return; // return; //
} }
+176 -167
View File
@@ -18,7 +18,6 @@ import 'kr_home_subscription_view.dart';
import './hi_animated_connect_button.dart'; import './hi_animated_connect_button.dart';
import 'package:kaer_with_panels/app/services/global_overlay_service.dart'; import 'package:kaer_with_panels/app/services/global_overlay_service.dart';
class KRHomeView extends StatefulWidget { class KRHomeView extends StatefulWidget {
const KRHomeView({super.key}); const KRHomeView({super.key});
@@ -35,8 +34,6 @@ class _KRHomeViewState extends State<KRHomeView> {
controller = Get.find<KRHomeController>(); controller = Get.find<KRHomeController>();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -50,174 +47,186 @@ class _KRHomeViewState extends State<KRHomeView> {
children: [ children: [
HIBaseScaffold( HIBaseScaffold(
showMenuButton: true, showMenuButton: true,
topContentAreaHeight: 80, topContentAreaHeight: 80,
child: Stack( child: Stack(
fit: StackFit.expand, fit: StackFit.expand,
children: [ children: [
Align( Align(
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
child: child: SafeArea(
SafeArea( child: Padding(
child: Padding( padding: EdgeInsets.fromLTRB(40.w, 8.w, 0, 0),
padding: EdgeInsets.fromLTRB(40.w, 8.w, 0, 0), child: Column(
child: Column( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.min, children: [
children: [ Text(
Text( 'Hi快VPN-网在我在,网快我快',
'Hi快VPN-网在我在,网快我快', style: TextStyle(
style: TextStyle( color: Colors.white,
color: Colors.white, fontSize: 14.sp,
fontSize: 14.sp, fontWeight: FontWeight.w600,
fontWeight: FontWeight.w600,
),
),
//
Obx(() {
// 1.
final currentSubscribe =
controller.kr_subscribeService.kr_currentSubscribe.value;
// 2. Widget
Widget content;
// --- ---
final textStyle = TextStyle(
color: Colors.white,
fontSize: 12.sp,
fontWeight: FontWeight.w600,
height: 1.2, //
);
// 3.
if (currentSubscribe == null) {
// --- 1: ---
content = Text(
'尚未购买套餐',
style: textStyle,
);
} else {
// --- 2: ---
final now = DateTime.now();
DateTime? expireDateTime;
try {
expireDateTime = DateTime.parse(currentSubscribe.expireTime);
} catch (e) {
//
}
if (expireDateTime != null && expireDateTime.isBefore(now)) {
// --- 2.1: ---
final formattedExpireDate =
'${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')}';
// 使 \n Text
content = Text(
'您的套餐已于 $formattedExpireDate 到期\n请前往购买新套餐',
style: textStyle,
);
} else {
// --- 2.2: ---
final difference = expireDateTime?.difference(now);
final remainingDaysText = (difference?.inDays ?? 0) > 0
? '${difference!.inDays}'
: '不足一天';
// 使 \n Text
content = Text(
'套餐剩余:$remainingDaysText\n${controller.kr_isConnected.value ? '当前线路:${controller.kr_getRealConnectedNodeCountry()}' : '未连接'}',
style: textStyle,
);
}
}
// 4. Container
return Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(top: 8.h),
child: content,
);
}),
SizedBox(height: 8.h), //
// --- Checkbox ---
Obx(() {
return Row(
mainAxisSize: MainAxisSize.min, // Row
children: [
// --- Checkbox ---
GestureDetector(
//
onTap: () => controller.toggleQuickConnect(!controller.isQuickConnectEnabled.value),
child: AbsorbPointer(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
// 1. Checkbox
Container(
width: 20.w,
height: 20.w,
decoration: BoxDecoration(
color: controller.isQuickConnectEnabled.value
? Theme.of(context).primaryColor
: Colors.transparent,
border: Border.all(
color: Theme.of(context).primaryColor,
width: 1.5,
),
borderRadius: BorderRadius.circular(4.r),
),
child: controller.isQuickConnectEnabled.value
? Icon(
Icons.check,
size: 14.w,
color: Colors.black,
)
: null,
),
SizedBox(width: 8.w),
// 2.
Text(
'闪连',
style: TextStyle(
color: Colors.white,
fontSize: 20.sp,
fontWeight: FontWeight.w600,
),
),
],
),
),
), ),
// --- --- ),
SizedBox(width: 6.w), // //
GestureDetector( Obx(() {
onTap: () { // 1.
HIDialog.show( final currentSubscribe = controller
title: '*闪连功能', .kr_subscribeService.kr_currentSubscribe.value;
message: '开启后,每次打开软件默认自动连接,无需点击连接按钮\n在后台关闭软件后,软件将自动断开',
// 2. Widget
Widget content;
// --- ---
final normalStyle = TextStyle(
color: Colors.white,
fontSize: 12.sp,
fontWeight: FontWeight.w600,
height: 1.2,
);
final highlightStyle = normalStyle.copyWith(
color: const Color(0xFFFF00B7),
);
// 3.
if (currentSubscribe == null) {
// --- 1: ---
content = Text(
'尚未购买套餐',
style: highlightStyle,
);
} else {
// --- 2: ---
final now = DateTime.now();
DateTime? expireDateTime;
try {
expireDateTime =
DateTime.parse(currentSubscribe.expireTime);
} catch (e) {
//
}
if (expireDateTime != null &&
expireDateTime.isBefore(now)) {
// --- 2.1: ---
final formattedExpireDate =
'${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')}';
// 使 \n Text
content = Text(
'您的套餐已于 $formattedExpireDate 到期\n请前往购买新套餐',
style: highlightStyle,
); );
}, } else {
child: KrLocalImage( // --- 2.2: ---
imageName: 'question-icon', // final difference =
imageType: ImageType.svg, // expireDateTime?.difference(now);
width: 24.w, final remainingDaysText =
height: 24.w, (difference?.inDays ?? 0) > 0
color: Colors.white, // ? '${difference!.inDays}'
), : '不足一天';
), // 使 \n Text
], content = Text(
); '套餐剩余:$remainingDaysText\n${controller.kr_isConnected.value ? '当前线路:${controller.kr_getRealConnectedNodeCountry()}' : '未连接'}',
}), style: normalStyle,
], );
}
}
// 4. Container
return Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(top: 8.h),
child: content,
);
}),
SizedBox(height: 8.h), //
// --- Checkbox ---
Obx(() {
return Row(
mainAxisSize: MainAxisSize.min, // Row
children: [
// --- Checkbox ---
GestureDetector(
//
onTap: () => controller.toggleQuickConnect(
!controller.isQuickConnectEnabled.value),
child: AbsorbPointer(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
// 1. Checkbox
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: controller
.isQuickConnectEnabled.value
? Theme.of(context).primaryColor
: Colors.transparent,
border: Border.all(
color:
Theme.of(context).primaryColor,
width: 1.5,
),
borderRadius:
BorderRadius.circular(4.r),
),
child: controller
.isQuickConnectEnabled.value
? Icon(
Icons.check,
size: 14,
color: Colors.black,
)
: null,
),
SizedBox(width: 8),
// 2.
Text(
'闪连',
style: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
// --- ---
SizedBox(width: 6), //
GestureDetector(
onTap: () {
HIDialog.show(
title: '*闪连功能',
message:
'开启后,每次打开软件默认自动连接,无需点击连接按钮\n在后台关闭软件后,软件将自动断开',
);
},
child: KrLocalImage(
imageName: 'question-icon', //
imageType: ImageType.svg, //
width: 24,
height: 24,
color: Colors.white, //
),
),
],
);
}),
],
),
),
), ),
), ),
), ],
), ),
], ),
), // HIAnimatedConnectButton HIBaseScaffold SafeArea
), HIAnimatedConnectButton(),
// HIAnimatedConnectButton HIBaseScaffold SafeArea ],
HIAnimatedConnectButton(), );
],
);
} }
} }
@@ -34,6 +34,7 @@ class KRInviteView extends GetView<KRInviteController> {
children: [ children: [
// 🟢 // 🟢
Container( Container(
width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.w), padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.w),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
@@ -41,7 +42,7 @@ class KRInviteView extends GetView<KRInviteController> {
), ),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min, // mainAxisSize: MainAxisSize.max,
children: [ children: [
KrLocalImage( KrLocalImage(
imageName: 'hi-home-logo', imageName: 'hi-home-logo',
@@ -204,4 +205,4 @@ class KRInviteView extends GetView<KRInviteController> {
), ),
); );
} }
} }
@@ -305,19 +305,33 @@ class KRLoginController extends GetxController
/// ///
void kr_sendCode() async { void kr_sendCode() async {
final either = await KRAuthApi().kr_sendCode( if (accountController.text.isEmpty) {
accountController.text, KRCommonUtil.kr_showToast(AppTranslations.kr_login.enterAccount);
2 return;
); // 3 }
/*
* int type;
* kr_loginStatus.value == KRLoginProgressStatus.kr_registerSendCode final check = await KRAuthApi().kr_isRegister(accountController.text);
? 2 // 2 final result = check.fold((l) {
: 3*/ 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) { either.fold((l) {
KRCommonUtil.kr_showToast(l.msg); KRCommonUtil.kr_showToast(l.msg);
}, (r) async { }, (r) async {
/// HIDialog.show(
title: '*重要提示',
message:
'验证邮件已发送至邮箱,如无法找到,请检查垃圾邮件箱或营销邮件箱。',
);
_startCountdown(); _startCountdown();
}); });
} }
@@ -380,7 +394,13 @@ class KRLoginController extends GetxController
); );
either.fold( either.fold(
(l) => KRCommonUtil.kr_showToast(l.msg), (l) {
HIDialog.show(
message: l.msg,
preventBackDismiss: true,
confirmText: '确定',
);
},
(r) async { (r) async {
_saveLoginData(r); _saveLoginData(r);
KRCommonUtil.kr_showToast('登录成功'); KRCommonUtil.kr_showToast('登录成功');
@@ -12,6 +12,7 @@ import 'package:kaer_with_panels/app/common/app_run_data.dart';
import 'package:kaer_with_panels/app/modules/kr_home/controllers/kr_home_controller.dart'; import 'package:kaer_with_panels/app/modules/kr_home/controllers/kr_home_controller.dart';
import 'package:kaer_with_panels/app/services/kr_site_config_service.dart'; import 'package:kaer_with_panels/app/services/kr_site_config_service.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:kaer_with_panels/app/widgets/kr_subscription_expiry_text.dart';
class KRLoginView extends GetView<KRLoginController> { class KRLoginView extends GetView<KRLoginController> {
const KRLoginView({super.key}); const KRLoginView({super.key});
@@ -19,7 +20,8 @@ class KRLoginView extends GetView<KRLoginController> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isKeyboardVisible = MediaQuery.of(context).viewInsets.bottom > 0; final isKeyboardVisible = MediaQuery.of(context).viewInsets.bottom > 0;
final isHideBack = (Get.arguments as Map<String, dynamic>?)?['is-back'] ?? false; final isHideBack =
(Get.arguments as Map<String, dynamic>?)?['is-back'] ?? false;
return HIBaseScaffold( return HIBaseScaffold(
showBack: !isHideBack, showBack: !isHideBack,
resizeToAvoidBottomInset: true, resizeToAvoidBottomInset: true,
@@ -27,7 +29,9 @@ class KRLoginView extends GetView<KRLoginController> {
children: [ children: [
SingleChildScrollView( SingleChildScrollView(
child: Padding( child: Padding(
padding: EdgeInsets.symmetric(horizontal: 40.w,), padding: EdgeInsets.symmetric(
horizontal: 40.w,
),
child: Column( child: Column(
children: [ children: [
// Text( // Text(
@@ -43,8 +47,7 @@ class KRLoginView extends GetView<KRLoginController> {
), ),
), ),
), ),
if (!isKeyboardVisible) if (!isKeyboardVisible) const HIHelpEntrance(),
const HIHelpEntrance(),
], ],
), ),
); );
@@ -76,27 +79,17 @@ class KRLoginView extends GetView<KRLoginController> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Obx(() { Obx(() {
final _ = KRAppRunData.getInstance().kr_isLogin.value; final account = KRAppRunData.getInstance()
final email = KRAppRunData.getInstance().kr_account.value ?? ''; .kr_account
return Text( .value;
email.isNotEmpty ? email : '', final isDeviceLogin = account != null &&
style: TextStyle( account.startsWith('9000');
color: Colors.white, final accountText = isDeviceLogin
fontSize: 20.sp, ? '待绑定'
fontWeight: FontWeight.bold, : '${KRAppRunData.getInstance().kr_account.value.toString()}';
height: 0.9,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
);
}),
Obx(() {
final _ = KRAppRunData.getInstance().kr_isLogin.value;
final userId = (KRAppRunData.getInstance().kr_userId.value ?? '').toString();
final deviceId = KRAppRunData.getInstance().deviceId ?? '';
return Text( return Text(
'ID: ${userId.isNotEmpty ? userId : deviceId}', accountText,
style: TextStyle( style: TextStyle(
color: Colors.white.withOpacity(0.85), color: Colors.white.withOpacity(0.85),
fontSize: 14.sp, fontSize: 14.sp,
@@ -104,51 +97,14 @@ class KRLoginView extends GetView<KRLoginController> {
), ),
); );
}), }),
Obx(() { KRSubscriptionExpiryText(
final currentSubscribe = expireTimeProvider: () => controller
controller.kr_subscribeService.kr_currentSubscribe.value; .kr_subscribeService.kr_currentSubscribe.value?.expireTime,
String expiryText; style: TextStyle(
color: Colors.white,
if (currentSubscribe == null) { fontSize: 12.sp,
expiryText = '尚未购买套餐'; ),
} else { ),
final now = DateTime.now();
DateTime? expireDateTime;
try {
expireDateTime =
DateTime.parse(currentSubscribe.expireTime);
} catch (e) {
expireDateTime = null;
}
if (expireDateTime == null) {
expiryText = '套餐信息无效';
} else if (expireDateTime.isBefore(now)) {
final formattedExpireDate =
'${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')}';
expiryText = '已于 $formattedExpireDate 到期';
} else {
expiryText = '到期时间:${expireDateTime}';final year = expireDateTime.year;
final month = expireDateTime.month.toString().padLeft(2, '0');
final day = expireDateTime.day.toString().padLeft(2, '0');
final hour = expireDateTime.hour.toString().padLeft(2, '0');
final minute = expireDateTime.minute.toString().padLeft(2, '0');
final second = expireDateTime.second.toString().padLeft(2, '0');
// 2.
final formattedDateTime = '$year/$month/$day $hour:$minute:$second';
expiryText = '到期时间:$formattedDateTime';
}
}
return Text(
expiryText,
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
),
);
}),
], ],
), ),
), ),
@@ -280,7 +236,8 @@ class KRLoginView extends GetView<KRLoginController> {
color: const Color(0xFFA6A6A6), color: const Color(0xFFA6A6A6),
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
contentPadding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.w), contentPadding:
EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.w),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.w), // borderRadius: BorderRadius.circular(25.w), //
borderSide: BorderSide(color: Colors.white, width: 2), borderSide: BorderSide(color: Colors.white, width: 2),
@@ -324,7 +281,8 @@ class KRLoginView extends GetView<KRLoginController> {
color: const Color(0xFFA6A6A6), color: const Color(0xFFA6A6A6),
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
contentPadding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.w), contentPadding:
EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.w),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.w), borderRadius: BorderRadius.circular(25.w),
borderSide: BorderSide(color: Colors.white, width: 2), borderSide: BorderSide(color: Colors.white, width: 2),
@@ -344,14 +302,15 @@ class KRLoginView extends GetView<KRLoginController> {
suffixIcon: Padding( suffixIcon: Padding(
padding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 5.w), padding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 5.w),
child: Obx(() { child: Obx(() {
return GestureDetector( return GestureDetector(
onTap: controller.kr_canSendCode.value ? () => controller.kr_sendCode() : null, onTap: controller.kr_canSendCode.value
? () => controller.kr_sendCode()
: null,
child: Container( child: Container(
width: 100.w, width: 100.w,
alignment: Alignment.center, alignment: Alignment.center,
decoration: BoxDecoration( decoration: BoxDecoration(
color: controller.kr_canSendCode.value color: controller.kr_canSendCode.value
? Theme.of(Get.context!).primaryColor ? Theme.of(Get.context!).primaryColor
: const Color(0xFFD5D5D5), : const Color(0xFFD5D5D5),
borderRadius: BorderRadius.circular(100.r), // borderRadius: BorderRadius.circular(100.r), //
@@ -363,7 +322,7 @@ class KRLoginView extends GetView<KRLoginController> {
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: controller.kr_canSendCode.value color: controller.kr_canSendCode.value
? Colors.black ? Colors.black
: const Color(0xFF464655) , : const Color(0xFF464655),
), ),
), ),
), ),
@@ -375,7 +334,6 @@ class KRLoginView extends GetView<KRLoginController> {
); );
} }
Widget _buildSaveButton() { Widget _buildSaveButton() {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
@@ -413,4 +371,4 @@ class KRLoginView extends GetView<KRLoginController> {
controller.kr_check(); controller.kr_check();
} }
} }
} }
@@ -6,6 +6,8 @@ import 'package:kaer_with_panels/app/localization/app_translations.dart';
import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart'; import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
import '../controllers/kr_order_status_controller.dart'; import '../controllers/kr_order_status_controller.dart';
import 'package:kaer_with_panels/app/routes/app_pages.dart'; import 'package:kaer_with_panels/app/routes/app_pages.dart';
import 'package:kaer_with_panels/app/widgets/hi_base_scaffold.dart';
import 'package:kaer_with_panels/app/widgets/hi_help_entrance.dart';
/// ///
class KROrderStatusView extends GetView<KROrderStatusController> { class KROrderStatusView extends GetView<KROrderStatusController> {
@@ -13,66 +15,39 @@ class KROrderStatusView extends GetView<KROrderStatusController> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return HIBaseScaffold(
extendBodyBehindAppBar: true, showBackgroundImage: false,
title: AppTranslations.kr_orderStatus.title,
backgroundColor: Theme.of(context).primaryColor, backgroundColor: Theme.of(context).primaryColor,
appBar: AppBar( topContentAreaHeight: 110,
backgroundColor: Colors.transparent, child: Obx(
elevation: 0, () => Stack(
leading: IconButton( children: [
icon: Icon( Center(
Icons.arrow_back_ios, child: Padding(
size: 20.r, padding: EdgeInsets.symmetric(horizontal: 24.w),
color: Theme.of(context).textTheme.bodyMedium?.color, child: Column(
), mainAxisAlignment: MainAxisAlignment.center,
onPressed: () => Get.back(), children: [
), //
centerTitle: true, // _buildStatusIcon(),
title: Text( SizedBox(height: 122.h),
AppTranslations.kr_orderStatus.title, //
style: KrAppTextStyle( _buildStatusText(),
color: Theme.of(context).textTheme.bodyMedium?.color, SizedBox(height: 16.h),
fontSize: 16, //
fontWeight: FontWeight.w500, _buildDescriptionText(),
), SizedBox(height: 32.h),
), //
), if (controller.kr_isPaymentSuccess.value)
body: Container( _buildSuccessButtons(),
decoration: BoxDecoration( SizedBox(height: 100.w),
gradient: LinearGradient( ],
begin: Alignment.topCenter, ),
end: Alignment.bottomCenter,
colors: [
Color.fromRGBO(23, 151, 255, 0.15),
Color.fromRGBO(23, 151, 255, 0.05),
],
stops: [0.0, 0.28],
),
),
child: Obx(
() => SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(height: 60.h),
//
_buildStatusIcon(),
SizedBox(height: 32.h),
//
_buildStatusText(),
SizedBox(height: 16.h),
//
_buildDescriptionText(),
SizedBox(height: 32.h),
//
if (controller.kr_isPaymentSuccess.value)
_buildSuccessButtons(),
const Spacer(),
],
), ),
), ),
), const HIHelpEntrance(isLight: false),
],
), ),
), ),
); );
@@ -134,7 +109,7 @@ class KROrderStatusView extends GetView<KROrderStatusController> {
Get.offAllNamed(Routes.KR_HOME); Get.offAllNamed(Routes.KR_HOME);
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.white, backgroundColor: Colors.white,
foregroundColor: Colors.white, foregroundColor: Colors.white,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r), borderRadius: BorderRadius.circular(12.r),
@@ -15,9 +15,6 @@ import 'package:kaer_with_panels/app/widgets/dialogs/kr_dialog.dart';
import 'package:kaer_with_panels/app/widgets/kr_local_image.dart'; import 'package:kaer_with_panels/app/widgets/kr_local_image.dart';
import 'package:kaer_with_panels/app/utils/kr_log_util.dart'; import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter_screenutil/flutter_screenutil.dart';
/// ///
class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> { class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
@@ -27,14 +24,14 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return HIBaseScaffold( return HIBaseScaffold(
showBackgroundImage: false, showBackgroundImage: false,
title: AppTranslations.kr_purchaseMembership.purchasePackage, title: '套餐选择',
subtitle: '*所有套餐均不限流量不限速度', subtitle: '*所有套餐均不限流量不限速度',
topContentAreaHeight: 110, topContentAreaHeight: 110,
child: Obx(() { child: Obx(() {
return Stack( return Stack(
children: [ children: [
Padding( Padding(
padding: EdgeInsets.only(bottom: 80.w), padding: EdgeInsets.only(bottom: 80.0),
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
@@ -67,52 +64,15 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
) )
else else
Container( Container(
margin: EdgeInsets.symmetric(horizontal: 40.r), margin: EdgeInsets.symmetric(horizontal: 40.0),
child: Column( child: Column(
children: [ children: [
// //
Container( Container(
padding: EdgeInsets.all(0.r), padding: EdgeInsets.all(0.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// if (controller.kr_plans.length > 1)
// Container(
// height: 32.h,
// child: ListView.builder(
// scrollDirection: Axis.horizontal,
// padding: EdgeInsets.zero,
// itemCount: controller.kr_plans.length,
// itemBuilder: (context, index) {
// final plan = controller.kr_plans[index];
// final isSelected = index == controller.kr_selectedPlanIndex.value;
// return GestureDetector(
// onTap: () => controller.kr_selectPlan(index),
// child: Container(
// margin: EdgeInsets.only(right: 8.w),
// padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 4.h),
// decoration: BoxDecoration(
// color: isSelected ? Colors.white : Theme.of(context).cardColor,
// borderRadius: BorderRadius.circular(16.r),
// border: Border.all(
// color: isSelected ? Colors.white : Colors.grey.withOpacity(0.3),
// width: 1,
// ),
// ),
// child: Center(
// child: Text(
// plan.kr_name,
// style: KrAppTextStyle(
// fontSize: 13,
// color: isSelected ? Colors.white : Theme.of(context).textTheme.bodyMedium?.color,
// ),
// ),
// ),
// ),
// );
// },
// ),
// ),
Column( Column(
// 使 List.generate // 使 List.generate
children: List.generate( children: List.generate(
@@ -130,12 +90,9 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
// 使 Padding mainAxisSpacing // 使 Padding mainAxisSpacing
return Padding( return Padding(
padding: padding:
EdgeInsets.only(bottom: 8.h), EdgeInsets.only(bottom: 8.0),
// 👇 SizedBox child: SizedBox(
child: ConstrainedBox( height: 140.0,
constraints: BoxConstraints(
minHeight: 100.h, // 100.h
),
child: _kr_buildPlanOptionCard( child: _kr_buildPlanOptionCard(
plan, plan,
controller controller
@@ -152,394 +109,15 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
], ],
), ),
), ),
/* SizedBox(height: 16.h),
//
Container(
padding: EdgeInsets.all(16.r),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(16.r),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppTranslations.kr_purchaseMembership.packageDescription,
style: KrAppTextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
color: Theme.of(context).textTheme.bodyMedium?.color,
),
),
SizedBox(height: 16.h),
Obx(() {
final featureLabels = controller.kr_getSelectedPlanFeatureLabels();
final features = controller.kr_getSelectedPlanFeatures();
final isExpanded = controller.kr_isDescriptionExpanded.value;
final selectedPlan = controller.kr_plans[controller.kr_selectedPlanIndex.value];
//
final trafficAndDeviceInfo = Padding(
padding: EdgeInsets.only(bottom: 8.h),
child: Container(
width: double.infinity,
padding: EdgeInsets.symmetric(
horizontal: 12.w,
vertical: 8.h,
),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(12.r),
border: Border.all(
color: Colors.grey.withOpacity(0.2),
),
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${AppTranslations.kr_purchaseMembership.trafficLimit}${controller.kr_getTrafficLimitText(selectedPlan)}',
style: KrAppTextStyle(
fontSize: 13,
color: Theme.of(context).textTheme.bodyMedium?.color,
),
),
SizedBox(height: 4.h),
Text(
'${AppTranslations.kr_purchaseMembership.deviceLimit}${controller.kr_getDeviceLimitText(selectedPlan)}',
style: KrAppTextStyle(
fontSize: 13,
color: Theme.of(context).textTheme.bodyMedium?.color,
),
),
],
),
),
],
),
),
);
// if (featureLabels.isEmpty) {
// return Column(
// children: [
// trafficAndDeviceInfo,
// Center(
// child: Padding(
// padding: EdgeInsets.symmetric(vertical: 16.h),
// child: Text(
// AppTranslations.kr_purchaseMembership.noData,
// style: KrAppTextStyle(
// fontSize: 14,
// color: Theme.of(context).textTheme.bodySmall?.color,
// ),
// ),
// ),
// ),
// ],
// );
// }
final displayCount = isExpanded ? featureLabels.length : (featureLabels.length > 3 ? 3 : featureLabels.length);
return Column(
children: [
trafficAndDeviceInfo,
...List.generate(displayCount, (index) {
final feature = features[index];
return Padding(
padding: EdgeInsets.only(bottom: 8.h),
child: GestureDetector(
onTap: () {
Get.dialog(
Dialog(
backgroundColor: Theme.of(Get.context!).cardColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.r),
),
child: Padding(
padding: EdgeInsets.all(16.r),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
feature.kr_label,
style: KrAppTextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(Get.context!).textTheme.bodyMedium?.color,
),
),
SizedBox(height: 8.h),
if (feature.kr_details.isNotEmpty)
...feature.kr_details.map((detail) => Padding(
padding: EdgeInsets.only(bottom: 8.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (detail.kr_label.isNotEmpty) ...[
Text(
detail.kr_label,
style: KrAppTextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(Get.context!).textTheme.bodyMedium?.color,
),
),
SizedBox(height: 4.h),
],
Text(
detail.kr_description,
style: KrAppTextStyle(
fontSize: 14,
color: Theme.of(Get.context!).textTheme.bodySmall?.color,
),
),
],
),
)).toList(),
SizedBox(height: 16.h),
Align(
alignment: Alignment.centerRight,
child: TextButton(
onPressed: () => Get.back(),
child: Text(
AppTranslations.kr_dialog.kr_ok,
style: KrAppTextStyle(
fontSize: 14,
color: Colors.white,
),
),
),
),
],
),
),
),
);
},
child: Container(
width: double.infinity,
padding: EdgeInsets.symmetric(
horizontal: 12.w,
vertical: 8.h,
),
decoration: BoxDecoration(
color: Theme.of(Get.context!).cardColor,
borderRadius: BorderRadius.circular(12.r),
border: Border.all(
color: Colors.grey.withOpacity(0.2),
),
),
child: Row(
children: [
Expanded(
child: Text(
featureLabels[index],
style: KrAppTextStyle(
fontSize: 13,
color: Theme.of(Get.context!).textTheme.bodyMedium?.color,
),
),
),
Container(
padding: EdgeInsets.all(4.r),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
Icons.info_outline,
size: 16.r,
color: Colors.white,
),
),
],
),
),
),
);
}),
if (featureLabels.length > 3)
GestureDetector(
onTap: () => controller.kr_toggleDescriptionExpanded(),
child: Padding(
padding: EdgeInsets.symmetric(vertical: 4.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
isExpanded
? AppTranslations.kr_purchaseMembership.collapse
: AppTranslations.kr_purchaseMembership.expand,
style: KrAppTextStyle(
fontSize: 13,
color: Colors.white,
),
),
SizedBox(width: 4.w),
Icon(
isExpanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down,
size: 16.r,
color: Colors.white,
),
],
),
),
),
],
);
}),
],
),
),
SizedBox(height: 16.h),
//
Container(
padding: EdgeInsets.all(16.r),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(16.r),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: Offset(0, 4),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppTranslations.kr_purchaseMembership.paymentMethod,
style: KrAppTextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
color: Theme.of(context).textTheme.bodyMedium?.color,
),
),
SizedBox(height: 16.h),
Obx(() => ListView.separated(
padding: EdgeInsets.zero,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: controller.kr_paymentMethods.length,
separatorBuilder: (context, index) => Divider(
height: 1.w,
indent: 44.w,
color: Theme.of(context).dividerColor.withOpacity(0.1),
),
itemBuilder: (context, index) {
final paymentMethod = controller.kr_paymentMethods[index];
return InkWell(
onTap: () => controller.kr_selectPaymentMethod(index),
child: Container(
padding: EdgeInsets.symmetric(vertical: 12.r, horizontal: 16.r),
child: Row(
children: [
Container(
width: 32.w,
height: 32.w,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Center(
child: paymentMethod.icon.isNotEmpty
? KRNetworkImage(
kr_imageUrl: paymentMethod.icon,
kr_width: 20.w,
kr_height: 20.w,
kr_placeholder: SizedBox(
width: 20.w,
height: 20.w,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
),
kr_errorWidget: Icon(
Icons.payment_rounded,
size: 20.w,
color: Colors.white,
),
)
: Icon(
Icons.payment_rounded,
size: 20.w,
color: Colors.white,
),
),
),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
controller.kr_getPaymentMethodTitle(paymentMethod),
style: KrAppTextStyle(
fontSize: 14,
color: Theme.of(context).textTheme.bodyMedium?.color,
),
),
],
),
),
Obx(() {
final isSelected = index == controller.kr_selectedPaymentMethodIndex.value;
return Container(
width: 24.w,
height: 24.w,
decoration: BoxDecoration(
color: isSelected ? Colors.white : Colors.transparent,
shape: BoxShape.circle,
border: Border.all(
color: isSelected ? Colors.white : Colors.grey.withOpacity(0.3),
width: 1.5,
),
),
child: isSelected
? Icon(
Icons.check,
color: Colors.white,
size: 16.r,
)
: null,
);
}),
],
),
),
);
},
)),
],
),
),*/
], ],
), ),
), ),
], ],
), ),
)), )),
Positioned( Positioned(
top: 160.w, // top: 160.0, //
right: 10.w, // 20.w right: 10.0, // 20.w
child: KrLocalImage( child: KrLocalImage(
imageName: 'purchase_slogan', imageName: 'purchase_slogan',
imageType: ImageType.svg, imageType: ImageType.svg,
@@ -572,12 +150,12 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
), ),
const Spacer(), const Spacer(),
Obx(() => Text( Obx(() => Text(
controller.kr_userEmail.value, controller.kr_userEmail.value,
style: KrAppTextStyle( style: KrAppTextStyle(
fontSize: 14, fontSize: 14,
color: Theme.of(context).textTheme.bodySmall?.color, color: Theme.of(context).textTheme.bodySmall?.color,
), ),
)), )),
], ],
), ),
); );
@@ -585,11 +163,12 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
// //
Widget _kr_buildPlanOptionCard( Widget _kr_buildPlanOptionCard(
KRPackageListItem plan, KRPackageListItem plan,
int planIndex, int planIndex,
int? discountIndex, int? discountIndex,
BuildContext context, BuildContext context,
bool isFirst,) { bool isFirst,
) {
// ObxisSelected // ObxisSelected
// UI // UI
// isSelected isFirst // isSelected isFirst
@@ -606,25 +185,27 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
padding: EdgeInsets.all(5), // padding: EdgeInsets.all(5), //
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.black, // color: Colors.black, //
borderRadius: BorderRadius.circular(43.r), // = + borderRadius: BorderRadius.circular(43.0), // = +
), ),
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(38.r), // borderRadius: BorderRadius.circular(38.0), //
child: Container( child: Container(
padding: EdgeInsets.fromLTRB(45.w, 10.w, 0.w, 10.w), padding: EdgeInsets.fromLTRB(45.0, 10.0, 0.0, 10.0),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected ? Colors.black : Theme.of(context).primaryColor, color: isSelected ? Colors.black : Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(38.r), borderRadius: BorderRadius.circular(38.0),
), ),
child: Stack( child: Stack(
clipBehavior: Clip.none, clipBehavior: Clip.none,
children: [ children: [
Column( mainAxisSize: MainAxisSize.min, Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Text( Text(
controller.kr_getTimeStr(plan, discountIndex: discountIndex), controller.kr_getTimeStr(plan,
discountIndex: discountIndex),
style: TextStyle( style: TextStyle(
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@@ -642,8 +223,7 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
color: isSelected color: isSelected
? Colors.white ? Colors.white
: Theme.of(context).textTheme.bodyMedium?.color, : Theme.of(context).textTheme.bodyMedium?.color,
) )),
),
Text( Text(
'约¥${controller.kr_getDayPlanPrice(plan, discountIndex: discountIndex).toStringAsFixed(2)}/天', '约¥${controller.kr_getDayPlanPrice(plan, discountIndex: discountIndex).toStringAsFixed(2)}/天',
style: TextStyle( style: TextStyle(
@@ -652,12 +232,9 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
color: isSelected color: isSelected
? Colors.white ? Colors.white
: Theme.of(context).textTheme.bodyMedium?.color, : Theme.of(context).textTheme.bodyMedium?.color,
) )),
), ]),
] if (!isFirst)
),
if(!isFirst)
Positioned( Positioned(
top: 0, top: 0,
right: 0, right: 0,
@@ -665,9 +242,9 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
angle: 0.785398, // 45 (π/4) angle: 0.785398, // 45 (π/4)
child: Container( child: Container(
// //
transform: Matrix4.translationValues(19.w, -12.w, 0), transform: Matrix4.translationValues(30, -10, 0),
width: 92.w, // width: 130, //
height: 16.w, // height: 20, //
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).primaryColor, // 使 color: Theme.of(context).primaryColor, // 使
), ),
@@ -676,7 +253,7 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
'${controller.kr_getDiscountText(plan, discountIndex!)} OFF', '${controller.kr_getDiscountText(plan, discountIndex!)} OFF',
style: TextStyle( style: TextStyle(
color: Colors.black, // color: Colors.black, //
fontSize: 14.sp, fontSize: 14,
fontWeight: FontWeight.w300, fontWeight: FontWeight.w300,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
@@ -685,47 +262,6 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
), ),
), ),
), ),
/* if (discountIndex != null && plan.kr_discount.isNotEmpty) ...[
Container(
padding: EdgeInsets.symmetric(
horizontal: 8.w,
vertical: 2.h
),
decoration: BoxDecoration(
color: plan.kr_discount[discountIndex].kr_discount < 100
? Colors.red.withOpacity(0.08)
: Colors.transparent,
borderRadius: BorderRadius.circular(4.r),
border: plan.kr_discount[discountIndex].kr_discount < 100
? Border.all(
color: Colors.red.withOpacity(0.2),
width: 1,
)
: null,
boxShadow: plan.kr_discount[discountIndex].kr_discount < 100
? [
BoxShadow(
color: Colors.red.withOpacity(0.05),
blurRadius: 4,
offset: Offset(0, 2),
spreadRadius: 0,
),
]
: null,
),
child: Text(
controller.kr_getDiscountText(plan, discountIndex),
style: KrAppTextStyle(
fontSize: 10,
fontWeight: FontWeight.w500,
color: plan.kr_discount[discountIndex].kr_discount < 100
? Colors.red.withOpacity(0.9)
: Colors.transparent,
),
),
),
],*/
], ],
), ),
), ),
@@ -733,56 +269,4 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
), ),
); );
} }
//
Widget _kr_buildBottomSection(BuildContext context) {
//
if (controller.kr_isLoading.value || controller.kr_plans.isEmpty || controller.kr_paymentMethods.isEmpty) {
return SizedBox.shrink();
}
return Container(
padding: EdgeInsets.all(16.r),
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
offset: Offset(0, -2),
blurRadius: 8,
),
],
),
child: SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
KRDialog.show(
title: AppTranslations.kr_purchaseMembership.confirmPurchase,
message: AppTranslations.kr_purchaseMembership.confirmPurchaseDesc,
cancelText: AppTranslations.kr_dialog.kr_cancel,
confirmText: AppTranslations.kr_dialog.kr_confirm,
onConfirm: () => controller.kr_startSubscription(),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 12.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.r),
),
),
child: Text(
controller.kr_getSubscribeButtonText(),
style: KrAppTextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
),
),
);
}
} }
@@ -170,13 +170,16 @@ class KRSplashController extends GetxController {
print('📞 准备调用 KRSiteConfigService().initialize()...'); print('📞 准备调用 KRSiteConfigService().initialize()...');
final success = await KRSiteConfigService().initialize(); final success = await KRSiteConfigService().initialize();
final crispId = await KRSiteConfigService().getCrispId(); final crispId = await KRSiteConfigService().getCrispId();
final device_limit = await KRSiteConfigService().getDeviceLimit();
print('📞 KRSiteConfigService().initialize() 返回: $success'); print('📞 KRSiteConfigService().initialize() 返回: $success');
if (success) { if (success) {
final config = AppConfig.getInstance(); final config = AppConfig.getInstance();
config.kr_website_id = crispId; config.kr_website_id = crispId;
config.device_limit = device_limit;
print('📞 KRSiteConfigService().initialize() 返回: $crispId'); print('📞 KRSiteConfigService().initialize() 返回: $crispId');
print('AppConfig website_id 已更新为: ${config.kr_website_id}'); print('AppConfig website_id 已更新为: ${config.kr_website_id}');
print('AppConfig device_limit 已更新为: ${config.device_limit}');
// print('[SPLASH_TIMING] ✅ 网站配置初始化成功'); // print('[SPLASH_TIMING] ✅ 网站配置初始化成功');
// await _kr_checkAndPerformDeviceLogin(); // await _kr_checkAndPerformDeviceLogin();
@@ -285,7 +288,7 @@ class KRSplashController extends GetxController {
KRLogUtil.kr_e('⏱️ 初始化超时: $e', tag: 'SplashController'); KRLogUtil.kr_e('⏱️ 初始化超时: $e', tag: 'SplashController');
print('⏱️ 初始化超时,直接跳转到主页'); print('⏱️ 初始化超时,直接跳转到主页');
// //
// Get.offAllNamed(Routes.KR_MAIN); // Get.offAllNamed(Routes.KR_HOME);
HIDialog.show( HIDialog.show(
message: '初始化超时,请检查网络或重试', message: '初始化超时,请检查网络或重试',
confirmText: '重试', confirmText: '重试',
@@ -484,7 +487,7 @@ class KRSplashController extends GetxController {
_initLog.logWarning('网络权限检查失败或超时,执行降级初始化', tag: 'Init'); _initLog.logWarning('网络权限检查失败或超时,执行降级初始化', tag: 'Init');
KRLogUtil.kr_w('⚠️ 网络权限检查失败或超时,执行降级初始化', tag: 'SplashController'); KRLogUtil.kr_w('⚠️ 网络权限检查失败或超时,执行降级初始化', tag: 'SplashController');
await _executeMinimalInitialization(); await _executeMinimalInitialization();
Get.offAllNamed(Routes.KR_MAIN); Get.offAllNamed(Routes.KR_HOME);
return; return;
} }
@@ -493,7 +496,7 @@ class KRSplashController extends GetxController {
_initLog.logError('网络权限检查异常,执行降级初始化', tag: 'Init', error: e); _initLog.logError('网络权限检查异常,执行降级初始化', tag: 'Init', error: e);
KRLogUtil.kr_w('⚠️ 网络权限检查异常: $e,执行降级初始化', tag: 'SplashController'); KRLogUtil.kr_w('⚠️ 网络权限检查异常: $e,执行降级初始化', tag: 'SplashController');
await _executeMinimalInitialization(); await _executeMinimalInitialization();
Get.offAllNamed(Routes.KR_MAIN); Get.offAllNamed(Routes.KR_HOME);
return; return;
} }
} else { } else {
@@ -582,7 +585,7 @@ class KRSplashController extends GetxController {
_initLog.logError('启动页初始化异常,执行降级策略', tag: 'Continue', error: e); _initLog.logError('启动页初始化异常,执行降级策略', tag: 'Continue', error: e);
KRLogUtil.kr_w('⚠️ 启动页初始化异常,执行降级策略: $e', tag: 'SplashController'); KRLogUtil.kr_w('⚠️ 启动页初始化异常,执行降级策略: $e', tag: 'SplashController');
await _executeMinimalInitialization(); await _executeMinimalInitialization();
Get.offAllNamed(Routes.KR_MAIN); Get.offAllNamed(Routes.KR_HOME);
} }
} }
@@ -726,7 +729,7 @@ class KRSplashController extends GetxController {
kr_isLoading.value = false; kr_isLoading.value = false;
// //
Get.offAllNamed(Routes.KR_MAIN); Get.offAllNamed(Routes.KR_HOME);
} }
/// 🔧 1.1DEBUG模式专用 /// 🔧 1.1DEBUG模式专用
+40 -14
View File
@@ -128,7 +128,8 @@ class HttpUtil {
void updateBaseUrl() { void updateBaseUrl() {
String newBaseUrl = AppConfig.getInstance().baseUrl; String newBaseUrl = AppConfig.getInstance().baseUrl;
if (_dio.options.baseUrl != newBaseUrl) { if (_dio.options.baseUrl != newBaseUrl) {
KRLogUtil.kr_i('🔄 更新baseUrl: ${_dio.options.baseUrl} -> $newBaseUrl', tag: 'HttpUtil'); KRLogUtil.kr_i('🔄 更新baseUrl: ${_dio.options.baseUrl} -> $newBaseUrl',
tag: 'HttpUtil');
_dio.options.baseUrl = newBaseUrl; _dio.options.baseUrl = newBaseUrl;
} }
} }
@@ -177,7 +178,7 @@ class HttpUtil {
try { try {
// baseUrl使 // baseUrl使
updateBaseUrl(); updateBaseUrl();
if (isShowLoading) { if (isShowLoading) {
KRCommonUtil.kr_showLoading(); KRCommonUtil.kr_showLoading();
} }
@@ -240,7 +241,9 @@ class HttpUtil {
return null; return null;
} }
} }
final userId = _kr_parseUserIdFromToken(KRAppRunData().kr_token.toString());
final userId =
_kr_parseUserIdFromToken(KRAppRunData().kr_token.toString());
// //
KRLogUtil.kr_i('🔍 请求头: $headers', tag: 'HttpUtil'); KRLogUtil.kr_i('🔍 请求头: $headers', tag: 'HttpUtil');
KRLogUtil.kr_i('🔍 请求userId: $userId', tag: 'HttpUtil'); KRLogUtil.kr_i('🔍 请求userId: $userId', tag: 'HttpUtil');
@@ -326,18 +329,22 @@ class HttpUtil {
} }
} }
} }
return BaseResponse<T>.fromJson({ if (err.type == DioExceptionType.unknown) {
'code': code, final _pathOnly = (err.requestOptions.path.isNotEmpty)
'msg': msg, ? err.requestOptions.path
'data': <String, dynamic>{} : err.requestOptions.uri.path;
}); msg = '${msg.isNotEmpty ? msg : 'unknown'} ($_pathOnly)';
KRCommonUtil.kr_showToast('请求失败($_pathOnly', timeout: 3500);
}
return BaseResponse<T>.fromJson(
{'code': code, 'msg': msg, 'data': <String, dynamic>{}});
} catch (e) { } catch (e) {
if (isShowLoading) { if (isShowLoading) {
KRCommonUtil.kr_hideLoading(); KRCommonUtil.kr_hideLoading();
} }
return BaseResponse<T>.fromJson({ return BaseResponse<T>.fromJson({
'code': -90000, 'code': -90000,
'msg': e.toString(), 'msg': '${e.toString()} (${path})',
'data': <String, dynamic>{} 'data': <String, dynamic>{}
}); });
} }
@@ -362,7 +369,8 @@ class MyInterceptor extends Interceptor {
@override @override
void onResponse(Response response, ResponseInterceptorHandler handler) { void onResponse(Response response, ResponseInterceptorHandler handler) {
if (kDebugMode) { if (kDebugMode) {
print('<<< Response │ ${response.requestOptions.method}${response.statusCode} ${response.statusMessage}${response.requestOptions.uri}'); print(
'<<< Response │ ${response.requestOptions.method}${response.statusCode} ${response.statusMessage}${response.requestOptions.uri}');
} }
if (response.data != null) { if (response.data != null) {
if (kDebugMode) { if (kDebugMode) {
@@ -375,7 +383,8 @@ class MyInterceptor extends Interceptor {
@override @override
void onError(DioException err, ErrorInterceptorHandler handler) { void onError(DioException err, ErrorInterceptorHandler handler) {
if (kDebugMode) { if (kDebugMode) {
print('<<< Error │ ${err.requestOptions.method}${err.requestOptions.uri}'); print(
'<<< Error │ ${err.requestOptions.method}${err.requestOptions.uri}');
} }
if (kDebugMode) { if (kDebugMode) {
print('Error Type: ${err.type}'); print('Error Type: ${err.type}');
@@ -396,6 +405,8 @@ class MyInterceptor extends Interceptor {
/// HTTP /// HTTP
class _KRSimpleHttpInterceptor extends Interceptor { class _KRSimpleHttpInterceptor extends Interceptor {
static String? _lastPath;
static int _lastTsMs = 0;
@override @override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) { void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
if (kDebugMode) { if (kDebugMode) {
@@ -454,7 +465,8 @@ class _KRSimpleHttpInterceptor extends Interceptor {
@override @override
void onResponse(Response response, ResponseInterceptorHandler handler) { void onResponse(Response response, ResponseInterceptorHandler handler) {
if (kDebugMode) { if (kDebugMode) {
print('<<< Response │ ${response.requestOptions.method}${response.statusCode} ${response.statusMessage}${response.requestOptions.uri}'); print(
'<<< Response │ ${response.requestOptions.method}${response.statusCode} ${response.statusMessage}${response.requestOptions.uri}');
} }
if (response.data != null) { if (response.data != null) {
if (kDebugMode) { if (kDebugMode) {
@@ -514,7 +526,8 @@ class _KRSimpleHttpInterceptor extends Interceptor {
@override @override
void onError(DioException err, ErrorInterceptorHandler handler) { void onError(DioException err, ErrorInterceptorHandler handler) {
if (kDebugMode) { if (kDebugMode) {
print('<<< Error │ ${err.requestOptions.method}${err.requestOptions.uri}'); print(
'<<< Error │ ${err.requestOptions.method}${err.requestOptions.uri}');
} }
if (kDebugMode) { if (kDebugMode) {
print('Error Type: ${err.type}'); print('Error Type: ${err.type}');
@@ -529,6 +542,19 @@ class _KRSimpleHttpInterceptor extends Interceptor {
print('Response Data: ${err.response?.data}'); print('Response Data: ${err.response?.data}');
} }
} }
if (err.type == DioExceptionType.unknown) {
final path = (err.requestOptions.path.isNotEmpty)
? err.requestOptions.path
: err.requestOptions.uri.path;
final now = DateTime.now().millisecondsSinceEpoch;
if (!(_lastPath == path && (now - _lastTsMs) < 2000)) {
_lastPath = path;
_lastTsMs = now;
KRCommonUtil.kr_showToast('请求失败($path', timeout: 3500);
}
KRLogUtil.kr_e('请求失败($path',
tag: 'HttpUtil', error: err, stackTrace: err.stackTrace);
}
handler.next(err); handler.next(err);
} }
} }
@@ -110,7 +110,7 @@ class SlideTransparentTransition extends CustomTransition {
SlideTransparentTransition({ SlideTransparentTransition({
this.direction = SlideDirection.rightToLeft, this.direction = SlideDirection.rightToLeft,
this.duration = const Duration(milliseconds: 4000), this.duration = const Duration(milliseconds: 2000),
}); });
@override @override
+1 -1
View File
@@ -1,7 +1,7 @@
/// ///
abstract class Api { 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"; static const String kr_checkSubscription = "/v1/public/user/subscribe_status";
@@ -36,7 +36,7 @@ class KRAuthApi {
BaseResponse<KRIsRegister> baseResponse = await HttpUtil.getInstance() BaseResponse<KRIsRegister> baseResponse = await HttpUtil.getInstance()
.request<KRIsRegister>(Api.kr_isRegister, data, .request<KRIsRegister>(Api.kr_isRegister, data,
method: HttpMethod.POST, isShowLoading: true); method: HttpMethod.GET, isShowLoading: true);
if (!baseResponse.isSuccess) { if (!baseResponse.isSuccess) {
return left( return left(
@@ -298,7 +298,7 @@ class KRAuthApi {
BaseResponse<dynamic> baseResponse = await HttpUtil.getInstance() BaseResponse<dynamic> baseResponse = await HttpUtil.getInstance()
.request<dynamic>(Api.kr_deleteAccount, data, .request<dynamic>(Api.kr_deleteAccount, data,
method: HttpMethod.DELETE, isShowLoading: true); method: HttpMethod.POST, isShowLoading: true);
if (!baseResponse.isSuccess) { if (!baseResponse.isSuccess) {
return left( return left(
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode)); HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
@@ -341,6 +341,11 @@ class KRSiteConfigService extends ChangeNotifier {
return _siteConfig?.site.crispId ?? '0'; return _siteConfig?.site.crispId ?? '0';
} }
///
String getDeviceLimit() {
return _siteConfig?.site.deviceLimit ?? '0';
}
/// ///
void reset() { void reset() {
_siteConfig = null; _siteConfig = null;
@@ -661,6 +661,7 @@ class KRSubscribeService {
// //
KRSingBoxImp.instance.kr_saveOutbounds(listModel.configJsonList); KRSingBoxImp.instance.kr_saveOutbounds(listModel.configJsonList);
// //
_kr_updateSubscribeStatus(); _kr_updateSubscribeStatus();
@@ -722,6 +723,7 @@ class KRSubscribeService {
// //
KRSingBoxImp.instance.kr_saveOutbounds([]); KRSingBoxImp.instance.kr_saveOutbounds([]);
} }
/// ///
File diff suppressed because it is too large Load Diff
+13 -10
View File
@@ -15,7 +15,8 @@ enum KRCountry {
ru('俄罗斯'), ru('俄罗斯'),
id('印度尼西亚'), id('印度尼西亚'),
tr('土耳其'), tr('土耳其'),
br('巴西'); br('巴西'),
other('全局代理'); //
final String kr_name; final String kr_name;
const KRCountry(this.kr_name); const KRCountry(this.kr_name);
@@ -30,7 +31,7 @@ enum KRCountry {
static KRCountry? kr_fromCode(String code) { static KRCountry? kr_fromCode(String code) {
try { try {
return KRCountry.values.firstWhere( return KRCountry.values.firstWhere(
(country) => country.kr_code == code.toLowerCase(), (country) => country.kr_code == code.toLowerCase(),
); );
} catch (e) { } catch (e) {
return null; return null;
@@ -53,7 +54,7 @@ class KRCountryUtil {
static Future<void> kr_init() async { static Future<void> kr_init() async {
try { try {
final String? kr_savedCountry = final String? kr_savedCountry =
await _kr_storage.kr_readData(key: _kr_countryKey); await _kr_storage.kr_readData(key: _kr_countryKey);
if (kr_savedCountry != null) { if (kr_savedCountry != null) {
final KRCountry? kr_country = KRCountry.kr_fromCode(kr_savedCountry); final KRCountry? kr_country = KRCountry.kr_fromCode(kr_savedCountry);
if (kr_country != null) { if (kr_country != null) {
@@ -66,7 +67,7 @@ class KRCountryUtil {
} else { } else {
kr_currentCountry.value = KRCountry.cn; kr_currentCountry.value = KRCountry.cn;
} }
} catch (err) { } catch (err) {
KRLogUtil.kr_e('初始化国家设置失败: $err', tag: 'CountryUtil'); KRLogUtil.kr_e('初始化国家设置失败: $err', tag: 'CountryUtil');
kr_currentCountry.value = KRCountry.cn; kr_currentCountry.value = KRCountry.cn;
@@ -106,9 +107,9 @@ class KRCountryUtil {
return kr_currentCountry.value.kr_code; return kr_currentCountry.value.kr_code;
} }
static String kr_getCurrentCountryName() { static String kr_getCurrentCountryName() {
return kr_getCountryName(kr_currentCountry.value); return kr_getCountryName(kr_currentCountry.value);
} }
/// ///
@@ -131,12 +132,14 @@ class KRCountryUtil {
return AppTranslations.kr_country.tr; return AppTranslations.kr_country.tr;
case KRCountry.br: case KRCountry.br:
return AppTranslations.kr_country.br; return AppTranslations.kr_country.br;
case KRCountry.other:
return '全局代理'; //
} }
} }
/// ///
static List<KRCountry> kr_getSupportedCountries() { 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.where((element) => element != KRCountry.cn).toList();
} }
return KRCountry.values; return KRCountry.values;
@@ -146,9 +149,9 @@ class KRCountryUtil {
static List<Map<String, String>> kr_getCountryInfoList() { static List<Map<String, String>> kr_getCountryInfoList() {
return KRCountry.values return KRCountry.values
.map((country) => { .map((country) => {
'code': country.kr_code, 'code': country.kr_code,
'name': country.kr_countryName, 'name': country.kr_countryName,
}) })
.toList(); .toList();
} }
} }
+110 -86
View File
@@ -1,127 +1,151 @@
import 'dart:io'; import 'dart:io';
import 'dart:async'; import 'dart:async';
import 'package:kaer_with_panels/app/utils/kr_log_util.dart'; import 'kr_log_util.dart';
/// ///
/// TCP
class KRLatencyTester { class KRLatencyTester {
/// /// TCP
/// /// 65535
/// : static Future<int> testTcpLatency({
/// - host:
/// - port:
/// - timeout:
///
/// :
/// - 65535
static Future<int> testNode({
required String host, required String host,
required int port, required int port,
int timeout = 5000, Duration timeout = const Duration(seconds: 5),
}) async { }) async {
Socket? socket;
final stopwatch = Stopwatch();
try { try {
final stopwatch = Stopwatch()..start(); KRLogUtil.kr_i('🔌 开始测试: $host:$port', tag: 'LatencyTester');
final socket = await Socket.connect( stopwatch.start();
// TCP
socket = await Socket.connect(
host, host,
port, port,
timeout: Duration(milliseconds: timeout), timeout: timeout,
).timeout(Duration(milliseconds: timeout)); );
stopwatch.stop(); stopwatch.stop();
//
await socket.close();
socket.destroy();
final latency = stopwatch.elapsedMilliseconds; final latency = stopwatch.elapsedMilliseconds;
KRLogUtil.kr_i('✅ 延迟测试成功: $host:$port = ${latency}ms', tag: 'KRLatencyTester');
KRLogUtil.kr_i('✅ 测试成功: $host:$port - ${latency}ms', tag: 'LatencyTester');
return latency; 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) { } catch (e) {
KRLogUtil.kr_w('❌ 延迟测试失败: $host:$port - $e', tag: 'KRLatencyTester'); stopwatch.stop();
return 65535; // KRLogUtil.kr_e('❌ 测试异常: $host:$port - $e', tag: 'LatencyTester');
return 65535;
} finally {
//
try {
await socket?.close();
} catch (e) {
//
}
} }
} }
/// ///
/// /// Map<tag, ms>
/// :
/// - nodes: [{"host": "example.com", "port": 443}]
/// - concurrency:
/// - timeout:
///
/// :
/// - "host:port"
static Future<Map<String, int>> testMultipleNodes({ static Future<Map<String, int>> testMultipleNodes({
required List<MapEntry<String, SocketAddress>> nodes, required List<MapEntry<String, SocketAddress>> nodes,
int concurrency = 10, int concurrency = 10, //
int timeout = 5000, Duration timeout = const Duration(seconds: 5),
}) async { }) async {
final results = <String, int>{}; final results = <String, int>{};
final semaphore = Completer<void>(); final List<List<MapEntry<String, SocketAddress>>> batches = [];
var activeCount = 0;
var completedCount = 0;
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 { KRLogUtil.kr_i('📊 开始批量测试: ${nodes.length} 个节点,分 ${batches.length} 批,每批 $concurrency', tag: 'LatencyTester');
try {
final host = node.value.address; int completedCount = 0;
final port = node.value.port;
final key = node.key; //
for (int batchIndex = 0; batchIndex < batches.length; batchIndex++) {
final latency = await testNode( final batch = batches[batchIndex];
host: host,
port: port, 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, timeout: timeout,
); );
results[key] = latency;
} catch (e) {
results[node.key] = 65535;
KRLogUtil.kr_e('❌ 节点测试异常: ${node.key} - $e', tag: 'KRLatencyTester');
} finally {
completedCount++; completedCount++;
activeCount--;
if (completedCount % 5 == 0 || completedCount == nodes.length) {
if (completedCount >= nodes.length) { KRLogUtil.kr_i('📈 测试进度: $completedCount/${nodes.length}', tag: 'LatencyTester');
semaphore.complete();
} }
}
}
// return MapEntry(tag, latency);
for (var i = 0; i < nodes.length; i += concurrency) { }).toList();
final batch = nodes.skip(i).take(concurrency);
for (final node in batch) {
activeCount++;
processNode(node);
}
// //
if (i + concurrency < nodes.length) { final batchResults = await Future.wait(futures);
await Future.delayed(Duration(milliseconds: 100));
//
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; return results;
} }
} }
/// Socket ///
///
class SocketAddress { class SocketAddress {
final String address; final String host;
final int port; final int port;
SocketAddress(this.address, this.port); SocketAddress(this.host, this.port);
@override @override
String toString() => '$address:$port'; String toString() => '$host:$port';
} }
+17 -10
View File
@@ -16,7 +16,7 @@ class KRSecureStorage {
// //
static const String _boxName = 'kaer_secure_storage'; static const String _boxName = 'kaer_secure_storage';
// //
static const String _encryptionKey = 'kaer_secure_storage_key'; static const String _encryptionKey = 'kaer_secure_storage_key';
@@ -27,19 +27,19 @@ class KRSecureStorage {
if (Platform.isMacOS || Platform.isWindows || Platform.isLinux) { if (Platform.isMacOS || Platform.isWindows || Platform.isLinux) {
final baseDir = await getApplicationSupportDirectory(); final baseDir = await getApplicationSupportDirectory();
KRLogUtil.kr_i('初始化 Hive,路径: ${baseDir.path}', tag: 'SecureStorage'); KRLogUtil.kr_i('初始化 Hive,路径: ${baseDir.path}', tag: 'SecureStorage');
// //
if (!baseDir.existsSync()) { if (!baseDir.existsSync()) {
await baseDir.create(recursive: true); await baseDir.create(recursive: true);
KRLogUtil.kr_i('已创建 Hive 目录: ${baseDir.path}', tag: 'SecureStorage'); KRLogUtil.kr_i('已创建 Hive 目录: ${baseDir.path}', tag: 'SecureStorage');
} }
await Hive.initFlutter(baseDir.path); await Hive.initFlutter(baseDir.path);
} else { } else {
// Android iOS 使 // Android iOS 使
await Hive.initFlutter(); await Hive.initFlutter();
} }
// 使 // 使
final key = HiveAesCipher(_generateKey()); final key = HiveAesCipher(_generateKey());
await Hive.openBox(_boxName, encryptionCipher: key); await Hive.openBox(_boxName, encryptionCipher: key);
@@ -48,14 +48,14 @@ class KRSecureStorage {
KRLogUtil.kr_e('初始化 Hive 失败: $e', tag: 'SecureStorage'); KRLogUtil.kr_e('初始化 Hive 失败: $e', tag: 'SecureStorage');
KRLogUtil.kr_e('错误类型: ${e.runtimeType}', tag: 'SecureStorage'); KRLogUtil.kr_e('错误类型: ${e.runtimeType}', tag: 'SecureStorage');
KRLogUtil.kr_e('错误堆栈: $stackTrace', tag: 'SecureStorage'); KRLogUtil.kr_e('错误堆栈: $stackTrace', tag: 'SecureStorage');
// Windows Linux // Windows Linux
if (Platform.isWindows || Platform.isLinux) { if (Platform.isWindows || Platform.isLinux) {
try { try {
KRLogUtil.kr_i('尝试清理并重新初始化 Hive', tag: 'SecureStorage'); KRLogUtil.kr_i('尝试清理并重新初始化 Hive', tag: 'SecureStorage');
final baseDir = await getApplicationSupportDirectory(); final baseDir = await getApplicationSupportDirectory();
final hiveDir = Directory(baseDir.path); final hiveDir = Directory(baseDir.path);
// Hive // Hive
if (hiveDir.existsSync()) { if (hiveDir.existsSync()) {
final files = hiveDir.listSync(); final files = hiveDir.listSync();
@@ -63,8 +63,8 @@ class KRSecureStorage {
if (entity is File) { if (entity is File) {
final fileName = entity.path.split(Platform.pathSeparator).last; final fileName = entity.path.split(Platform.pathSeparator).last;
// Hive .hive .lock // Hive .hive .lock
if (fileName.startsWith(_boxName) || if (fileName.startsWith(_boxName) ||
fileName.endsWith('.hive') || fileName.endsWith('.hive') ||
fileName.endsWith('.lock')) { fileName.endsWith('.lock')) {
try { try {
await entity.delete(); await entity.delete();
@@ -76,7 +76,7 @@ class KRSecureStorage {
} }
} }
} }
// //
await Hive.initFlutter(baseDir.path); await Hive.initFlutter(baseDir.path);
final key = HiveAesCipher(_generateKey()); final key = HiveAesCipher(_generateKey());
@@ -116,8 +116,15 @@ class KRSecureStorage {
try { try {
final box = await _ensureBoxOpen(); final box = await _ensureBoxOpen();
await box.put(key, value); await box.put(key, value);
// 🔧
// flush()
await box.flush();
print('💾 [SecureStorage] 数据已保存并刷新到磁盘: $key');
KRLogUtil.kr_i('✅ 数据已保存: $key', tag: 'SecureStorage'); KRLogUtil.kr_i('✅ 数据已保存: $key', tag: 'SecureStorage');
} catch (e) { } catch (e) {
print('❌ [SecureStorage] 存储数据失败: $e');
KRLogUtil.kr_e('❌ 存储数据失败: $e', tag: 'SecureStorage'); KRLogUtil.kr_e('❌ 存储数据失败: $e', tag: 'SecureStorage');
rethrow; // rethrow; //
} }
@@ -210,4 +217,4 @@ class KRSecureStorage {
return null; return null;
} }
} }
} }
+5 -5
View File
@@ -26,7 +26,7 @@ class KRWindowManager with WindowListener, TrayListener {
const WindowOptions windowOptions = WindowOptions( const WindowOptions windowOptions = WindowOptions(
size: Size(800, 668), size: Size(800, 668),
minimumSize: Size(800, 668), minimumSize: Size(400, 334),
center: true, center: true,
backgroundColor: Colors.white, backgroundColor: Colors.white,
skipTaskbar: false, skipTaskbar: false,
@@ -47,15 +47,15 @@ class KRWindowManager with WindowListener, TrayListener {
await windowManager.setTitleBarStyle(TitleBarStyle.normal); await windowManager.setTitleBarStyle(TitleBarStyle.normal);
await windowManager.setTitle('HiFastVPN'); await windowManager.setTitle('HiFastVPN');
await windowManager.setSize(const Size(800, 668)); 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.center();
await windowManager.show(); await windowManager.show();
// //
await windowManager.setPreventClose(true); await windowManager.setPreventClose(true);
} else { } else {
await windowManager.setTitle('Kaer VPN'); await windowManager.setTitle('HiFastVPN');
await windowManager.setSize(const Size(800, 668)); 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.center();
} }
@@ -94,7 +94,7 @@ class KRWindowManager with WindowListener, TrayListener {
/// ///
void _initPlatformChannel() { void _initPlatformChannel() {
if (Platform.isMacOS) { if (Platform.isMacOS) {
const platform = MethodChannel('kaer_vpn/terminate'); const platform = MethodChannel('hifast_vpn/terminate');
platform.setMethodCallHandler((call) async { platform.setMethodCallHandler((call) async {
if (call.method == 'onTerminate') { if (call.method == 'onTerminate') {
KRLogUtil.kr_i('收到应用终止通知'); KRLogUtil.kr_i('收到应用终止通知');
+439
View File
@@ -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.1sing-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');
}
}
+7 -4
View File
@@ -42,6 +42,8 @@ class HIBaseScaffold extends StatelessWidget {
/// ///
final bool showBackgroundImage; final bool showBackgroundImage;
final Color? backgroundColor;
/// resizeToAvoidBottomInset /// resizeToAvoidBottomInset
final bool? resizeToAvoidBottomInset; final bool? resizeToAvoidBottomInset;
@@ -59,6 +61,7 @@ class HIBaseScaffold extends StatelessWidget {
this.showBackgroundImage = true, this.showBackgroundImage = true,
this.topContentAreaHeight = 120.0, this.topContentAreaHeight = 120.0,
this.resizeToAvoidBottomInset = false, this.resizeToAvoidBottomInset = false,
this.backgroundColor,
}); });
@override @override
@@ -71,7 +74,7 @@ class HIBaseScaffold extends StatelessWidget {
resizeToAvoidBottomInset: resizeToAvoidBottomInset, resizeToAvoidBottomInset: resizeToAvoidBottomInset,
backgroundColor: showBackgroundImage backgroundColor: showBackgroundImage
? Colors.transparent ? Colors.transparent
: Theme.of(context).scaffoldBackgroundColor, : (backgroundColor ?? Theme.of(context).scaffoldBackgroundColor),
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
toolbarHeight: 0.0, toolbarHeight: 0.0,
@@ -152,7 +155,7 @@ class HIBaseScaffold extends StatelessWidget {
if (title != null || subtitle != null) if (title != null || subtitle != null)
Positioned( Positioned(
top: topPadding + top: topPadding +
(showBack ? 12.w : 20.w), // (showBack ? 8 : 20), //
left: 0, left: 0,
right: 0, right: 0,
child: Column( child: Column(
@@ -164,7 +167,7 @@ class HIBaseScaffold extends StatelessWidget {
title!, title!,
style: TextStyle( style: TextStyle(
color: Colors.black, // 使 color: Colors.black, // 使
fontSize: 24.sp, fontSize: 24,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
@@ -175,7 +178,7 @@ class HIBaseScaffold extends StatelessWidget {
subtitle!, subtitle!,
style: TextStyle( style: TextStyle(
color: Colors.black, // 使 color: Colors.black, // 使
fontSize: 14.sp, fontSize: 14,
fontWeight: FontWeight.w100, fontWeight: FontWeight.w100,
), ),
), ),
@@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class KRSubscriptionExpiryText extends StatelessWidget {
final String? Function() expireTimeProvider;
final TextStyle? style;
const KRSubscriptionExpiryText({
Key? key,
required this.expireTimeProvider,
this.style,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Obx(() {
final expireTimeStr = expireTimeProvider();
String expiryText;
bool highlight = false;
if (expireTimeStr == null || expireTimeStr.isEmpty) {
expiryText = '尚未购买套餐';
highlight = true;
} else {
DateTime? expireDateTime;
try {
expireDateTime = DateTime.parse(expireTimeStr);
} catch (_) {
expireDateTime = null;
}
if (expireDateTime == null) {
expiryText = '套餐信息无效';
} else if (expireDateTime.isBefore(DateTime.now())) {
final formattedExpireDate =
'${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')}';
expiryText = '已于 $formattedExpireDate 到期';
highlight = true;
} else {
final year = expireDateTime.year;
final month = expireDateTime.month.toString().padLeft(2, '0');
final day = expireDateTime.day.toString().padLeft(2, '0');
final hour = expireDateTime.hour.toString().padLeft(2, '0');
final minute = expireDateTime.minute.toString().padLeft(2, '0');
final second = expireDateTime.second.toString().padLeft(2, '0');
final formattedDateTime = '$year/$month/$day $hour:$minute:$second';
expiryText = '到期时间:$formattedDateTime';
}
}
final baseStyle = style ??
const TextStyle(
color: Colors.white,
fontSize: 12,
);
final appliedStyle = highlight
? baseStyle.copyWith(color: const Color(0xFFFF00B7))
: baseStyle;
return Text(
expiryText,
style: appliedStyle,
);
});
}
}
+8
View File
@@ -3,6 +3,7 @@ import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
// import 'package:flutter_easyloading/flutter_easyloading.dart'; // // import 'package:flutter_easyloading/flutter_easyloading.dart'; //
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter/services.dart';
// import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart'; // import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
@@ -37,6 +38,12 @@ void main() async {
// Flutter // Flutter
await Future.delayed(const Duration(milliseconds: 100)); await Future.delayed(const Duration(milliseconds: 100));
if (Platform.isAndroid || Platform.isIOS) {
await SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown],
);
}
// //
KRLogUtil.kr_init(); KRLogUtil.kr_init();
@@ -175,6 +182,7 @@ Widget _myApp(GetxTranslations translations, Locale initialLocale) {
Routes.HI_MENU, Routes.HI_MENU,
Routes.KR_HOME, Routes.KR_HOME,
Routes.HI_USER_INFO, Routes.HI_USER_INFO,
Routes.KR_ORDER_STATUS,
]; ];
print('routing.current${routing.current}'); print('routing.current${routing.current}');
GlobalOverlayService.instance.updateSubscriptionButtonColor(null); GlobalOverlayService.instance.updateSubscriptionButtonColor(null);
+9 -9
View File
@@ -571,10 +571,10 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = NJRRF427XB;
ENABLE_HARDENED_RUNTIME = NO; ENABLE_HARDENED_RUNTIME = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN; INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN;
@@ -583,7 +583,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 10.15; MACOSX_DEPLOYMENT_TARGET = 10.15;
PRODUCT_BUNDLE_IDENTIFIER = app.hi.com; PRODUCT_BUNDLE_IDENTIFIER = com.taw.hifastvpn.mac;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };
@@ -704,10 +704,10 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = NJRRF427XB;
ENABLE_HARDENED_RUNTIME = NO; ENABLE_HARDENED_RUNTIME = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN; INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN;
@@ -716,7 +716,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 10.15; MACOSX_DEPLOYMENT_TARGET = 10.15;
PRODUCT_BUNDLE_IDENTIFIER = app.hi.com; PRODUCT_BUNDLE_IDENTIFIER = com.taw.hifastvpn.mac;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@@ -731,10 +731,10 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = NJRRF427XB;
ENABLE_HARDENED_RUNTIME = NO; ENABLE_HARDENED_RUNTIME = NO;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN; INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN;
@@ -743,7 +743,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 10.15; MACOSX_DEPLOYMENT_TARGET = 10.15;
PRODUCT_BUNDLE_IDENTIFIER = app.hi.com; PRODUCT_BUNDLE_IDENTIFIER = com.taw.hifastvpn.mac;
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };
+1 -1
View File
@@ -37,7 +37,7 @@ class AppDelegate: FlutterAppDelegate {
// Flutter // Flutter
if let controller = NSApp.windows.first?.contentViewController as? FlutterViewController { if let controller = NSApp.windows.first?.contentViewController as? FlutterViewController {
let channel = FlutterMethodChannel(name: "kaer_vpn/terminate", binaryMessenger: controller.engine.binaryMessenger) let channel = FlutterMethodChannel(name: "hifast_vpn/terminate", binaryMessenger: controller.engine.binaryMessenger)
channel.invokeMethod("onTerminate", arguments: nil) channel.invokeMethod("onTerminate", arguments: nil)
} }
} }
+1 -1
View File
@@ -8,7 +8,7 @@
PRODUCT_NAME = HiFastVPN PRODUCT_NAME = HiFastVPN
// The application's bundle identifier // The application's bundle identifier
PRODUCT_BUNDLE_IDENTIFIER = app.hi.com PRODUCT_BUNDLE_IDENTIFIER = app.taw.hifastvpn.mac
// The copyright displayed in application information // The copyright displayed in application information
PRODUCT_COPYRIGHT = Copyright © 2025 HiFastVPN.com. All rights reserved. PRODUCT_COPYRIGHT = Copyright © 2025 HiFastVPN.com. All rights reserved.
+2 -2
View File
@@ -2,10 +2,10 @@
// 用于发布版本的签名设置 // 用于发布版本的签名设置
// 使用 Developer ID Application 证书进行签名(用于 App Store 外分发) // 使用 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 // 开发团队 ID
DEVELOPMENT_TEAM = 3UR892FAP3 DEVELOPMENT_TEAM = NJRRF427XB
// 启用代码签名 // 启用代码签名
CODE_SIGN_STYLE = Manual CODE_SIGN_STYLE = Manual
-16
View File
@@ -18,22 +18,6 @@
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string> <string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string></string>
<key>CFBundleURLSchemes</key>
<array>
<string>clash</string>
<string>clashmeta</string>
<string>sing-box</string>
<string>hiddify</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string> <string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
+3 -2
View File
@@ -1,4 +1,5 @@
title: Hiddify title: HiFastVPN
volume: HiFastVPN
contents: contents:
- x: 448 - x: 448
y: 344 y: 344
@@ -7,4 +8,4 @@ contents:
- x: 192 - x: 192
y: 344 y: 344
type: file type: file
path: Hiddify.app path: HiFastVPN.app
+1 -1
View File
@@ -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 # 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 # 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. # of the product and file versions while build-number is used as the build suffix.
version: 0.0.1+1 version: 0.0.4
environment: environment:
sdk: '>=3.5.0 <4.0.0' sdk: '>=3.5.0 <4.0.0'
+5
View File
@@ -126,6 +126,11 @@ install(FILES "../libcore/bin/HiFastVPNCli.exe"
COMPONENT Runtime COMPONENT Runtime
OPTIONAL) OPTIONAL)
install(FILES "runner/resources/app_icon.ico"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime
OPTIONAL)
if(PLUGIN_BUNDLED_LIBRARIES) if(PLUGIN_BUNDLED_LIBRARIES)
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
+2 -1
View File
@@ -7,6 +7,7 @@ echo 正在复制必要的 DLL 文件...
copy "%VC_REDIST_DIR%\msvcp140.dll" "%DLL_DIR%" copy "%VC_REDIST_DIR%\msvcp140.dll" "%DLL_DIR%"
copy "%VC_REDIST_DIR%\vcruntime140.dll" "%DLL_DIR%" copy "%VC_REDIST_DIR%\vcruntime140.dll" "%DLL_DIR%"
copy "%VC_REDIST_DIR%\vcruntime140_1.dll" "%DLL_DIR%" copy "%VC_REDIST_DIR%\vcruntime140_1.dll" "%DLL_DIR%"
copy "%~dp0..\libcore\libcore.dll" "%DLL_DIR%"
echo DLL 文件复制完成! echo DLL 文件复制完成!
pause pause
+5 -1
View File
@@ -14,6 +14,8 @@ Compression=lzma
SolidCompression=yes SolidCompression=yes
SetupIconFile={{SETUP_ICON_FILE}} SetupIconFile={{SETUP_ICON_FILE}}
WizardStyle=modern WizardStyle=modern
LanguageDetectionMethod=uilanguage
UsePreviousTasks=no
PrivilegesRequired={{PRIVILEGES_REQUIRED}} PrivilegesRequired={{PRIVILEGES_REQUIRED}}
ArchitecturesAllowed=x64 ArchitecturesAllowed=x64
ArchitecturesInstallIn64BitMode=x64 ArchitecturesInstallIn64BitMode=x64
@@ -26,6 +28,8 @@ CloseApplications=force
{% if locale == 'bg' %}Name: "bulgarian"; MessagesFile: "compiler:Languages\\Bulgarian.isl"{% endif %} {% if locale == 'bg' %}Name: "bulgarian"; MessagesFile: "compiler:Languages\\Bulgarian.isl"{% endif %}
{% if locale == 'ca' %}Name: "catalan"; MessagesFile: "compiler:Languages\\Catalan.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' %}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 == 'co' %}Name: "corsican"; MessagesFile: "compiler:Languages\\Corsican.isl"{% endif %}
{% if locale == 'cs' %}Name: "czech"; MessagesFile: "compiler:Languages\\Czech.isl"{% endif %} {% if locale == 'cs' %}Name: "czech"; MessagesFile: "compiler:Languages\\Czech.isl"{% endif %}
{% if locale == 'da' %}Name: "danish"; MessagesFile: "compiler:Languages\\Danish.isl"{% endif %} {% if locale == 'da' %}Name: "danish"; MessagesFile: "compiler:Languages\\Danish.isl"{% endif %}
@@ -50,7 +54,7 @@ CloseApplications=force
[Tasks] [Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkedonce 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] [Files]
Source: "{{SOURCE_DIR}}\\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "{{SOURCE_DIR}}\\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files ; NOTE: Don't use "Flags: ignoreversion" on any shared system files
+5 -1
View File
@@ -6,8 +6,11 @@ executable_name: HiFastVPN.exe
output_base_file_name: HiFastVPN.exe output_base_file_name: HiFastVPN.exe
create_desktop_icon: true create_desktop_icon: true
install_dir_name: "{autopf64}\\HiFastVPN" 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: locales:
- zh
- zh-cn
- zh_CN
- ar - ar
- en - en
- fa - fa
@@ -15,3 +18,4 @@ locales:
- pt - pt
- tr - tr
script_template: inno_setup.sas script_template: inno_setup.sas
languages_dir: compiler:Languages
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB