Compare commits

75 Commits

Author SHA1 Message Date
sepakeloudest 554d12193e feat: 全局监听iap,在home初始化。避免过早处理,升级dart和flutter,支持storekit插件,强制指定storekit2版本 2025-12-17 20:36:52 -08:00
sepakeloudest 801a77f942 feat: 全局监听iap,在home初始化。避免过早处理,升级dart和flutter,支持storekit插件,强制指定storekit2版本 2025-12-17 20:34:59 -08:00
sepakeloudest 05d2c71cd0 feat: 全局监听iap,在home初始化。避免过早处理,升级dart和flutter,支持storekit插件,强制指定storekit2版本 2025-12-17 16:47:35 -08:00
sepakeloudest 743631c4ce feat: 增加flutter_keychain来缓存订单号 2025-12-17 01:46:42 -08:00
sepakeloudest 60de644637 feat: iap支付方式 2025-12-16 01:37:59 -08:00
sepakeloudest f888b772c2 feat: 样式修改 2025-12-10 06:14:10 -08:00
sepakeloudest f70b32ec3f feat: 增加ios支付&ui修改 2025-12-09 07:23:26 -08:00
sepakeloudest ca913eb38f feat: 增加ios支付&ui修改 2025-12-09 05:51:32 -08:00
sepakeloudest dc5a5fc78a feat: 增加ios支付 2025-12-08 23:12:51 -08:00
sepakeloudest 17860e333c feat: 修复节点切换bug 2025-12-04 07:37:39 -08:00
sepakeloudest d63b3bfc3c feat: 初始化时配置项未完成配置,导致客服有bug, 2025-12-03 23:24:25 -08:00
sepakeloudest 4f6be35f61 feat: 修复ios上未检测到账号,弹窗没显示问题 2025-12-03 20:32:30 -08:00
sepakeloudest 56c7fd6921 feat: 修复ios上未检测到账号,弹窗没显示问题 2025-12-03 20:27:48 -08:00
sepakeloudest 24cf03d6ce feat: 增加连续点击样式处理 2025-12-03 19:42:46 -08:00
sepakeloudest 5bd77511cc feat: 减少日志,增强状态,减少监听 2025-12-03 18:06:47 -08:00
sepakeloudest 33b9cd34f1 feat: 增加账号未初始化提示登录重新初始化 2025-12-03 03:08:26 -08:00
sepakeloudest c87d5d4d38 feat: bugfix 2025-12-03 02:28:14 -08:00
sepakeloudest ce4abb7e01 feat: bugfix 2025-12-03 01:28:18 -08:00
sepakeloudest dbcdf8b8a4 feat: 版本号增加 2025-12-02 16:24:15 -08:00
sepakeloudest e82480b937 feat: 增加侧滑事件 2025-12-02 06:09:25 -08:00
sepakeloudest 8e27ddeded Revert "feat: 增加动画变量,处理多个变量控制动画逻辑"
This reverts commit aa0fd94cb2.
2025-12-01 22:34:48 -08:00
sepakeloudest 961d419c4d feat: 修复分享文案和null处理 2025-12-01 22:34:37 -08:00
sepakeloudest aa0fd94cb2 feat: 增加动画变量,处理多个变量控制动画逻辑 2025-12-01 19:41:38 -08:00
sepakeloudest 86687cac58 feat: bugfix 2025-12-01 06:54:18 -08:00
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
118 changed files with 6257 additions and 3500 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
+21 -2
View File
@@ -12,6 +12,10 @@ ifeq ($(OS),Windows_NT)
endif endif
endif endif
APP_NAME=HiFastVPN
VERSION_NAME=$(shell grep '^version:' pubspec.yaml | sed 's/version: //;s/+.*//;s/[^0-9.]//g')
VERSION_BUILD=$(shell grep '^version:' pubspec.yaml | sed 's/.*+//;s/[^0-9]//g')
BINDIR=libcore$(SEP)bin BINDIR=libcore$(SEP)bin
ANDROID_OUT=android$(SEP)app$(SEP)libs ANDROID_OUT=android$(SEP)app$(SEP)libs
@@ -54,7 +58,7 @@ gen:
dart run build_runner build --delete-conflicting-outputs dart run build_runner build --delete-conflicting-outputs
translate: translate:
dart run slang dart run slang --overwrite
@@ -152,6 +156,21 @@ gen_translations: #generating missing translations using google translate
android-release: android-apk-release android-release: android-apk-release
android-rename:
@FULL_VER=$(VERSION_NAME).$(VERSION_BUILD); \
OUT_DIR=build/app/outputs/flutter-apk; \
echo "Renaming APKs in $$OUT_DIR to $(APP_NAME)-$$FULL_VER-<ABI>.apk"; \
for SRC in "$$OUT_DIR"/app-*-release.apk; do \
if [ -f "$$SRC" ]; then \
FILENAME=$$(basename "$$SRC"); \
TGT="$$OUT_DIR/$(APP_NAME)-$$FULL_VER-$${FILENAME#app-}"; \
echo "Renaming $$SRC -> $$TGT"; \
mv -v "$$SRC" "$$TGT"; \
else \
echo "Skip $$SRC, file not found"; \
fi; \
done
android-apk-release: android-apk-release:
echo flutter build apk --target $(TARGET) $(BUILD_ARGS) --target-platform android-arm,android-arm64,android-x64 --split-per-abi --verbose echo flutter build apk --target $(TARGET) $(BUILD_ARGS) --target-platform android-arm,android-arm64,android-x64 --split-per-abi --verbose
flutter build apk --target $(TARGET) $(BUILD_ARGS) --target-platform android-arm,android-arm64,android-x64 --verbose flutter build apk --target $(TARGET) $(BUILD_ARGS) --target-platform android-arm,android-arm64,android-x64 --verbose
@@ -169,7 +188,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

@@ -1,21 +1,16 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off --> <style name="LaunchTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:forceDarkAllowed">false</item> <item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item> <item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item> <item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">@drawable/launch_background</item> <item name="android:windowSplashScreenBackground">@drawable/launch_background</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item> <item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
</style> </style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. --> <style name="NormalTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar"> <item name="android:windowBackground">?android:colorBackground</item>
<item name="android:windowBackground">@drawable/launch_background</item>
</style> </style>
</resources> </resources>
@@ -1,22 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on --> <style name="LaunchTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item> <item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item> <item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item> <item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item> <item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style> </style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. --> <style name="NormalTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item> <item name="android:windowBackground">?android:colorBackground</item>
</style> </style>
</resources> </resources>
@@ -1,21 +1,16 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off --> <style name="LaunchTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:forceDarkAllowed">false</item> <item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item> <item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item> <item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#ffffff</item> <item name="android:windowSplashScreenBackground">#ffffff</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item> <item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
</style> </style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. --> <style name="NormalTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item> <item name="android:windowBackground">?android:colorBackground</item>
</style> </style>
</resources> </resources>
+2 -10
View File
@@ -1,22 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off --> <style name="LaunchTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item> <item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item> <item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item> <item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item> <item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style> </style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. --> <style name="NormalTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item> <item name="android:windowBackground">?android:colorBackground</item>
</style> </style>
</resources> </resources>
+3 -3
View File
@@ -1,10 +1,10 @@
<svg width="14" height="16" viewBox="0 0 14 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="14" height="15" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_7_8207)"> <g clip-path="url(#clip0_7_8207)">
<path d="M13.0955 0.715332C13.4845 0.715332 13.7984 1.02021 13.7986 1.39795V11.4146C13.7986 11.7924 13.4846 12.0972 13.0955 12.0972H2.54565C2.02987 12.0972 1.60718 12.5075 1.60718 13.0083C1.60725 13.5091 2.02991 13.9185 2.54565 13.9185H13.0955C13.4846 13.9185 13.7986 14.2242 13.7986 14.6021C13.7984 14.9798 13.4845 15.2847 13.0955 15.2847H2.54565C1.25155 15.2847 0.201002 14.2648 0.200928 13.0083V2.9917C0.200975 1.73514 1.25153 0.715332 2.54565 0.715332H13.0955ZM2.54565 2.08154C2.0299 2.08154 1.60722 2.49092 1.60718 2.9917V10.9233C1.8932 10.8004 2.21274 10.7319 2.54565 10.7319H12.3923V2.08154H2.54565ZM6.50269 8.8833C6.76525 8.62836 7.23892 8.62839 7.49683 8.8833C7.76876 9.1519 7.7734 9.57952 7.49683 9.84814C7.22018 10.1168 6.77465 10.1122 6.50269 9.84814C6.22637 9.57955 6.22628 9.14729 6.50269 8.8833ZM5.64526 3.24268C6.12353 2.85115 6.75603 2.69168 7.37964 2.79639C8.23303 2.94208 8.93174 3.6201 9.08179 4.44873C9.23652 5.31833 8.80996 6.1884 8.02222 6.61182C7.8301 6.71198 7.70784 6.94816 7.70776 7.20752V7.31689C7.70776 7.69478 7.39381 7.9995 7.00464 7.99951C6.61545 7.99951 6.30151 7.69479 6.30151 7.31689V7.20752C6.3016 6.44273 6.70021 5.755 7.34253 5.41357C7.54404 5.30419 7.7597 5.04906 7.69409 4.68506C7.64706 4.41659 7.41202 4.18955 7.1355 4.14404C6.91992 4.10318 6.71359 4.15758 6.54956 4.28955C6.38559 4.42158 6.29663 4.61275 6.29663 4.81299C6.29661 5.19077 5.98255 5.49547 5.59351 5.49561C5.20434 5.49561 4.89041 5.19086 4.89038 4.81299C4.89038 4.2029 5.16699 3.63423 5.64526 3.24268Z" fill="#0F2C53"/> <path d="M12.8945 0C13.2836 0 13.5975 0.304873 13.5977 0.682617V10.6992C13.5977 11.0771 13.2837 11.3818 12.8945 11.3818H2.34473C1.82894 11.3818 1.40625 11.7921 1.40625 12.293C1.40632 12.7937 1.82899 13.2031 2.34473 13.2031H12.8945C13.2837 13.2031 13.5977 13.5088 13.5977 13.8867C13.5975 14.2644 13.2836 14.5693 12.8945 14.5693H2.34473C1.05062 14.5693 7.38806e-05 13.5495 0 12.293V2.27637C4.67804e-05 1.0198 1.0506 0 2.34473 0H12.8945ZM2.34473 1.36621C1.82897 1.36621 1.4063 1.77559 1.40625 2.27637V10.208C1.69228 10.0851 2.01181 10.0166 2.34473 10.0166H12.1914V1.36621H2.34473ZM6.30176 8.16797C6.56432 7.91302 7.03799 7.91306 7.2959 8.16797C7.56783 8.43657 7.57247 8.86419 7.2959 9.13281C7.01925 9.40143 6.57372 9.39688 6.30176 9.13281C6.02544 8.86422 6.02536 8.43196 6.30176 8.16797ZM5.44434 2.52734C5.9226 2.13582 6.5551 1.97635 7.17871 2.08105C8.0321 2.22675 8.73081 2.90477 8.88086 3.7334C9.03559 4.603 8.60903 5.47307 7.82129 5.89648C7.62917 5.99665 7.50691 6.23283 7.50684 6.49219V6.60156C7.50684 6.97945 7.19289 7.28417 6.80371 7.28418C6.41453 7.28418 6.10059 6.97945 6.10059 6.60156V6.49219C6.10067 5.7274 6.49928 5.03967 7.1416 4.69824C7.34312 4.58886 7.55877 4.33373 7.49316 3.96973C7.44613 3.70126 7.2111 3.47422 6.93457 3.42871C6.719 3.38785 6.51266 3.44225 6.34863 3.57422C6.18467 3.70625 6.0957 3.89742 6.0957 4.09766C6.09568 4.47544 5.78162 4.78013 5.39258 4.78027C5.00341 4.78027 4.68948 4.47553 4.68945 4.09766C4.68945 3.48757 4.96606 2.91889 5.44434 2.52734Z" fill="black"/>
</g> </g>
<defs> <defs>
<clipPath id="clip0_7_8207"> <clipPath id="clip0_7_8207">
<rect width="13.598" height="14.5693" fill="white" transform="translate(0.200928 0.715332)"/> <rect width="13.598" height="14.5693" fill="white"/>
</clipPath> </clipPath>
</defs> </defs>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

+2 -2
View File
@@ -1,3 +1,3 @@
<svg width="14" height="15" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.96265 11.3523C3.78265 11.3523 4.44975 12.0196 4.44995 12.8396V13.6902C4.44977 14.0426 4.16566 14.3267 3.81323 14.3269H0.838623C0.486039 14.3269 0.201112 14.0427 0.200928 13.6902V12.8396C0.201128 12.0196 0.868226 11.3523 1.68823 11.3523H2.96265ZM12.3113 11.3523C13.1313 11.3523 13.7984 12.0196 13.7986 12.8396V13.6902C13.7984 14.0426 13.5143 14.3267 13.1619 14.3269H10.1873C9.83467 14.3269 9.54974 14.0427 9.54956 13.6902V12.8396C9.54976 12.0196 10.2169 11.3523 11.0369 11.3523H12.3113ZM6.99976 8.37769C7.3524 8.37769 7.63737 8.66275 7.63745 9.01538V10.8767L8.72534 11.9646C8.97586 12.2152 8.97571 12.6143 8.72534 12.865C8.47463 13.1157 8.07469 13.1157 7.82397 12.865L6.99976 12.0408L6.17554 12.865C5.92482 13.1157 5.52586 13.1157 5.27515 12.865C5.02444 12.6143 5.02444 12.2153 5.27515 11.9646L6.36206 10.8767V9.01538C6.36214 8.66281 6.64719 8.37778 6.99976 8.37769ZM1.68823 12.6277C1.56938 12.6277 1.47554 12.725 1.47534 12.8396V13.0525H3.17554V12.8396C3.17534 12.725 3.0815 12.6277 2.96265 12.6277H1.68823ZM11.0369 12.6277C10.918 12.6277 10.8242 12.725 10.824 12.8396V13.0525H12.5242V12.8396C12.524 12.725 12.4301 12.6277 12.3113 12.6277H11.0369ZM2.32593 7.31616C3.26485 7.31633 4.02507 8.07644 4.02515 9.01538C4.02515 9.95439 3.2649 10.7154 2.32593 10.7156C1.38682 10.7156 0.625732 9.95449 0.625732 9.01538C0.625813 8.07634 1.38687 7.31616 2.32593 7.31616ZM11.6746 7.31616C12.6135 7.31633 13.3737 8.07644 13.3738 9.01538C13.3738 9.95439 12.6135 10.7154 11.6746 10.7156C10.7354 10.7156 9.97437 9.95449 9.97437 9.01538C9.97445 8.07634 10.7355 7.31616 11.6746 7.31616ZM2.32593 8.59058C2.09226 8.59058 1.9012 8.78173 1.90112 9.01538C1.90112 9.2491 2.09221 9.44019 2.32593 9.44019C2.5595 9.44001 2.75073 9.24899 2.75073 9.01538C2.75065 8.78184 2.55945 8.59075 2.32593 8.59058ZM11.6746 8.59058C11.4409 8.59058 11.2498 8.78173 11.2498 9.01538C11.2498 9.2491 11.4408 9.44019 11.6746 9.44019C11.9081 9.44001 12.0994 9.24899 12.0994 9.01538C12.0993 8.78184 11.9081 8.59075 11.6746 8.59058ZM8.27515 4.55347C9.09503 4.55369 9.76235 5.22087 9.76245 6.04077V7.10327C9.76245 7.45592 9.47739 7.74089 9.12476 7.74097H4.87476C4.52228 7.74071 4.23804 7.45581 4.23804 7.10327V6.04077C4.23814 5.22073 4.90528 4.55347 5.72534 4.55347H8.27515ZM5.72534 5.82886C5.60643 5.82886 5.51256 5.92612 5.51245 6.04077V6.46558H8.48706V6.04077C8.48695 5.92625 8.39387 5.82908 8.27515 5.82886H5.72534ZM6.99976 0.729248C7.93887 0.729248 8.69995 1.49033 8.69995 2.42944C8.69971 3.36835 7.93872 4.12866 6.99976 4.12866C6.06087 4.12857 5.30078 3.36829 5.30054 2.42944C5.30054 1.49039 6.06072 0.729342 6.99976 0.729248ZM6.99976 2.00366C6.76612 2.00376 6.57495 2.19579 6.57495 2.42944C6.57519 2.6629 6.76627 2.85415 6.99976 2.85425C7.23332 2.85425 7.42432 2.66296 7.42456 2.42944C7.42456 2.19573 7.23347 2.00366 6.99976 2.00366Z" fill="#0F2C53"/> <path d="M2.76172 10.623C3.58173 10.623 4.24882 11.2904 4.24902 12.1104V12.9609C4.24884 13.3134 3.96473 13.5975 3.6123 13.5977H0.637695C0.285111 13.5977 0.000183976 13.3135 0 12.9609V12.1104C0.000199964 11.2904 0.667298 10.623 1.4873 10.623H2.76172ZM12.1104 10.623C12.9304 10.623 13.5975 11.2904 13.5977 12.1104V12.9609C13.5975 13.3134 13.3134 13.5975 12.9609 13.5977H9.98633C9.63374 13.5977 9.34882 13.3135 9.34863 12.9609V12.1104C9.34883 11.2904 10.0159 10.623 10.8359 10.623H12.1104ZM6.79883 7.64844C7.15148 7.64844 7.43644 7.9335 7.43652 8.28613V10.1475L8.52441 11.2354C8.77494 11.486 8.77478 11.885 8.52441 12.1357C8.2737 12.3865 7.87376 12.3865 7.62305 12.1357L6.79883 11.3115L5.97461 12.1357C5.7239 12.3865 5.32493 12.3865 5.07422 12.1357C4.82351 11.885 4.82351 11.4861 5.07422 11.2354L6.16113 10.1475V8.28613C6.16121 7.93356 6.44626 7.64853 6.79883 7.64844ZM1.4873 11.8984C1.36845 11.8984 1.27461 11.9958 1.27441 12.1104V12.3232H2.97461V12.1104C2.97441 11.9958 2.88057 11.8984 2.76172 11.8984H1.4873ZM10.8359 11.8984C10.7171 11.8984 10.6232 11.9958 10.623 12.1104V12.3232H12.3232V12.1104C12.323 11.9958 12.2292 11.8984 12.1104 11.8984H10.8359ZM2.125 6.58691C3.06392 6.58708 3.82414 7.34719 3.82422 8.28613C3.82422 9.22514 3.06397 9.98616 2.125 9.98633C1.18589 9.98633 0.424805 9.22524 0.424805 8.28613C0.424885 7.34709 1.18594 6.58691 2.125 6.58691ZM11.4736 6.58691C12.4125 6.58709 13.1728 7.3472 13.1729 8.28613C13.1729 9.22514 12.4126 9.98616 11.4736 9.98633C10.5345 9.98633 9.77344 9.22524 9.77344 8.28613C9.77352 7.34709 10.5346 6.58691 11.4736 6.58691ZM2.125 7.86133C1.89133 7.86133 1.70028 8.05249 1.7002 8.28613C1.7002 8.51985 1.89128 8.71094 2.125 8.71094C2.35857 8.71077 2.5498 8.51974 2.5498 8.28613C2.54972 8.05259 2.35852 7.8615 2.125 7.86133ZM11.4736 7.86133C11.24 7.86133 11.0489 8.05249 11.0488 8.28613C11.0488 8.51985 11.2399 8.71094 11.4736 8.71094C11.7072 8.71076 11.8984 8.51974 11.8984 8.28613C11.8984 8.05259 11.7072 7.8615 11.4736 7.86133ZM8.07422 3.82422C8.8941 3.82444 9.56142 4.49162 9.56152 5.31152V6.37402C9.56152 6.72668 9.27646 7.01164 8.92383 7.01172H4.67383C4.32135 7.01146 4.03711 6.72656 4.03711 6.37402V5.31152C4.03722 4.49148 4.70435 3.82422 5.52441 3.82422H8.07422ZM5.52441 5.09961C5.4055 5.09961 5.31163 5.19688 5.31152 5.31152V5.73633H8.28613V5.31152C8.28603 5.19701 8.19294 5.09983 8.07422 5.09961H5.52441ZM6.79883 0C7.73794 0 8.49902 0.761083 8.49902 1.7002C8.49878 2.6391 7.73779 3.39941 6.79883 3.39941C5.85994 3.39932 5.09985 2.63904 5.09961 1.7002C5.09961 0.761141 5.8598 9.36506e-05 6.79883 0ZM6.79883 1.27441C6.56519 1.27451 6.37402 1.46654 6.37402 1.7002C6.37427 1.93365 6.56534 2.12491 6.79883 2.125C7.0324 2.125 7.22339 1.93371 7.22363 1.7002C7.22363 1.46648 7.03254 1.27441 6.79883 1.27441Z" fill="black"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

+3 -3
View File
@@ -1,10 +1,10 @@
<svg width="14" height="15" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_7_8212)"> <g clip-path="url(#clip0_7_8212)">
<path d="M13.1619 0.616089C13.5144 0.616273 13.7986 0.9012 13.7986 1.25378V9.75183C13.7986 10.1044 13.5144 10.3893 13.1619 10.3895H4.92651L1.28882 14.0272C0.889453 14.4265 0.201204 14.1462 0.200928 13.577V1.25378C0.200928 0.901086 0.485925 0.616089 0.838623 0.616089H13.1619ZM1.47534 12.038L4.21265 9.30164C4.33161 9.1828 4.49298 9.11511 4.66284 9.11511H12.5242V1.8905H1.47534V12.038ZM6.36206 5.078C6.36206 4.23663 7.63745 4.23663 7.63745 5.078V7.83972C7.63745 8.19242 7.35245 8.47742 6.99976 8.47742C6.64714 8.47732 6.36206 8.19236 6.36206 7.83972V5.078ZM6.54956 2.72839C6.78327 2.49043 7.21198 2.49045 7.44995 2.72839C7.70066 2.97911 7.69641 3.38232 7.44995 3.62878C7.20347 3.8751 6.80022 3.87945 6.54956 3.62878C6.30316 3.38237 6.299 2.97911 6.54956 2.72839Z" fill="#0F2C53"/> <path d="M12.9609 0C13.3135 0.000183671 13.5977 0.285111 13.5977 0.637695V9.13574C13.5977 9.48833 13.3135 9.77325 12.9609 9.77344H4.72559L1.08789 13.4111C0.688525 13.8105 0.000275843 13.5301 0 12.9609V0.637695C0 0.284997 0.284997 0 0.637695 0H12.9609ZM1.27441 11.4219L4.01172 8.68555C4.13068 8.56671 4.29205 8.49902 4.46191 8.49902H12.3232V1.27441H1.27441V11.4219ZM6.16113 4.46191C6.16113 3.62054 7.43652 3.62054 7.43652 4.46191V7.22363C7.43652 7.57633 7.15153 7.86133 6.79883 7.86133C6.44621 7.86124 6.16113 7.57627 6.16113 7.22363V4.46191ZM6.34863 2.1123C6.58234 1.87435 7.01105 1.87436 7.24902 2.1123C7.49974 2.36302 7.49549 2.76623 7.24902 3.0127C7.00255 3.25902 6.5993 3.26336 6.34863 3.0127C6.10224 2.76628 6.09807 2.36302 6.34863 2.1123Z" fill="black"/>
</g> </g>
<defs> <defs>
<clipPath id="clip0_7_8212"> <clipPath id="clip0_7_8212">
<rect width="13.598" height="13.598" fill="white" transform="translate(0.200928 0.616089)"/> <rect width="13.598" height="13.598" fill="white"/>
</clipPath> </clipPath>
</defs> </defs>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1012 B

+2 -2
View File
@@ -1,3 +1,3 @@
<svg width="38" height="45" viewBox="0 0 38 45" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="32" height="58" viewBox="0 0 32 58" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24.9375 26.0625H33.75V30.2812H23.75V45H14.6562V30.2812H4.71875V26.0625H13.4688L10.0625 19.25H3.40625V15H8L0.5 0.28125H11.0938L19.4062 19.75L27.3125 0.28125H37.4688L30.3438 15H34.8125V19.25H28.2812L24.9375 26.0625Z" fill="black"/> <path d="M12 29.7812C8.02083 28.6146 5.20833 26.9896 3.5625 24.9062C1.9375 22.8229 1.125 20.3125 1.125 17.375C1.125 15.4375 1.4375 13.6771 2.0625 12.0938C2.70833 10.5104 3.60417 9.14583 4.75 8C6.22917 6.52083 7.875 5.5 9.6875 4.9375C10.7917 4.58333 12.4062 4.32292 14.5312 4.15625V0H17.5V4.21875C20.8958 4.48958 23.5625 5.32292 25.5 6.71875C29.0208 8.94792 30.8438 12.5625 30.9688 17.5625H22.75C22.5833 15.7083 22.2708 14.3542 21.8125 13.5C21.0208 12.0208 19.5833 11.2083 17.5 11.0625V22.9688C22.4583 24.6771 25.7917 26.1875 27.5 27.5C30.3125 29.6875 31.7188 32.7708 31.7188 36.75C31.7188 42 29.7917 45.8125 25.9375 48.1875C23.5833 49.6458 20.7917 50.5 17.5625 50.75V57.0312H14.5312V50.8125C10.4271 50.3542 7.36458 49.4583 5.34375 48.125C1.78125 45.7292 0.03125 41.6458 0.09375 35.875H8.53125C8.82292 38.5 9.22917 40.2604 9.75 41.1562C10.5625 42.5521 12.1562 43.4583 14.5312 43.875V30.5312L12 29.7812ZM17.5 31.5625V43.7812C19.1458 43.5729 20.3438 43.1979 21.0938 42.6562C22.4062 41.6979 23.0625 40.0417 23.0625 37.6875C23.0625 35.8958 22.4479 34.4792 21.2188 33.4375C20.4896 32.8333 19.25 32.2083 17.5 31.5625ZM14.5312 22.0938V11.125C12.7188 11.1667 11.375 11.6562 10.5 12.5938C9.625 13.5104 9.1875 14.7708 9.1875 16.375C9.1875 18.125 9.84375 19.5104 11.1562 20.5312C11.8854 21.0938 13.0104 21.6146 14.5312 22.0938Z" fill="black"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 343 B

After

Width:  |  Height:  |  Size: 1.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:
+5
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:
@@ -11,3 +12,7 @@ targets:
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/>
Executable → Regular
+62
View File
@@ -26,6 +26,44 @@ PODS:
- Flutter - Flutter
- ReachabilitySwift (5.2.4) - ReachabilitySwift (5.2.4)
- SAMKeychain (1.5.3) - SAMKeychain (1.5.3)
- share_plus (0.0.1):
- Flutter
- Stripe (23.27.6):
- StripeApplePay (= 23.27.6)
- StripeCore (= 23.27.6)
- StripePayments (= 23.27.6)
- StripePaymentsUI (= 23.27.6)
- StripeUICore (= 23.27.6)
- stripe_ios (0.0.1):
- Flutter
- Stripe (~> 23.27.0)
- StripeApplePay (~> 23.27.0)
- StripeFinancialConnections (~> 23.27.0)
- StripePayments (~> 23.27.0)
- StripePaymentSheet (~> 23.27.0)
- StripePaymentsUI (~> 23.27.0)
- StripeApplePay (23.27.6):
- StripeCore (= 23.27.6)
- StripeCore (23.27.6)
- StripeFinancialConnections (23.27.6):
- StripeCore (= 23.27.6)
- StripeUICore (= 23.27.6)
- StripePayments (23.27.6):
- StripeCore (= 23.27.6)
- StripePayments/Stripe3DS2 (= 23.27.6)
- StripePayments/Stripe3DS2 (23.27.6):
- StripeCore (= 23.27.6)
- StripePaymentSheet (23.27.6):
- StripeApplePay (= 23.27.6)
- StripeCore (= 23.27.6)
- StripePayments (= 23.27.6)
- StripePaymentsUI (= 23.27.6)
- StripePaymentsUI (23.27.6):
- StripeCore (= 23.27.6)
- StripePayments (= 23.27.6)
- StripeUICore (= 23.27.6)
- StripeUICore (23.27.6):
- StripeCore (= 23.27.6)
- url_launcher_ios (0.0.1): - url_launcher_ios (0.0.1):
- Flutter - Flutter
- webview_flutter_wkwebview (0.0.1): - webview_flutter_wkwebview (0.0.1):
@@ -42,6 +80,8 @@ DEPENDENCIES:
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- stripe_ios (from `.symlinks/plugins/stripe_ios/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`) - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`)
@@ -51,6 +91,14 @@ SPEC REPOS:
- OrderedSet - OrderedSet
- ReachabilitySwift - ReachabilitySwift
- SAMKeychain - SAMKeychain
- Stripe
- StripeApplePay
- StripeCore
- StripeFinancialConnections
- StripePayments
- StripePaymentSheet
- StripePaymentsUI
- StripeUICore
EXTERNAL SOURCES: EXTERNAL SOURCES:
connectivity_plus: connectivity_plus:
@@ -69,6 +117,10 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/path_provider_foundation/darwin" :path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple: permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios" :path: ".symlinks/plugins/permission_handler_apple/ios"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
stripe_ios:
:path: ".symlinks/plugins/stripe_ios/ios"
url_launcher_ios: url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios" :path: ".symlinks/plugins/url_launcher_ios/ios"
webview_flutter_wkwebview: webview_flutter_wkwebview:
@@ -87,6 +139,16 @@ SPEC CHECKSUMS:
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
ReachabilitySwift: 32793e867593cfc1177f5d16491e3a197d2fccda ReachabilitySwift: 32793e867593cfc1177f5d16491e3a197d2fccda
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
Stripe: 9fec845645e39f371e6898926d096fd9c2feb5a5
stripe_ios: 03c617acee72e48a2d055d096a4b0ed2afebb256
StripeApplePay: 5f017e8dfe259fafbab70137776189deef754bb2
StripeCore: 01ec57f0dddfe742054dc6a322f811426c25313d
StripeFinancialConnections: 56698cb6274bf89fb8c76b934f6156f368e97765
StripePayments: 6adf11faf1b7038e77aa97019410305c6adca79d
StripePaymentSheet: 3eaf870c4388e44b0cc37e4c69d00b6957fd8bd7
StripePaymentsUI: 59ccddeacad592b09fa67e8d641340820ddb4751
StripeUICore: 879bbf5889265db13f52fac8aad7a176ba62481f
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
webview_flutter_wkwebview: a4af96a051138e28e29f60101d094683b9f82188 webview_flutter_wkwebview: a4af96a051138e28e29f60101d094683b9f82188
+35 -22
View File
@@ -24,6 +24,7 @@
0736958B2B3AC96D007249BE /* Bundle+Properties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0736958A2B3AC96D007249BE /* Bundle+Properties.swift */; }; 0736958B2B3AC96D007249BE /* Bundle+Properties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0736958A2B3AC96D007249BE /* Bundle+Properties.swift */; };
075637BA2B01583F005AFB8E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 60F1D4AAC33ACF5C8307310D /* Pods_Runner.framework */; }; 075637BA2B01583F005AFB8E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 60F1D4AAC33ACF5C8307310D /* Pods_Runner.framework */; };
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
186131822EE29AB0007DB19F /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 186131812EE29AAF007DB19F /* StoreKit.framework */; };
2A9DDE222DCF45350006D7FC /* KRActiveGroupsEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A9DDE192DCF45350006D7FC /* KRActiveGroupsEventHandler.swift */; }; 2A9DDE222DCF45350006D7FC /* KRActiveGroupsEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A9DDE192DCF45350006D7FC /* KRActiveGroupsEventHandler.swift */; };
2A9DDE232DCF45350006D7FC /* KRLogsEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A9DDE1D2DCF45350006D7FC /* KRLogsEventHandler.swift */; }; 2A9DDE232DCF45350006D7FC /* KRLogsEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A9DDE1D2DCF45350006D7FC /* KRLogsEventHandler.swift */; };
2A9DDE242DCF45350006D7FC /* KRFileMethodHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A9DDE1B2DCF45350006D7FC /* KRFileMethodHandler.swift */; }; 2A9DDE242DCF45350006D7FC /* KRFileMethodHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A9DDE1B2DCF45350006D7FC /* KRFileMethodHandler.swift */; };
@@ -126,6 +127,7 @@
07A63AA12B1E72FC00CAFA4D /* Sentry.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Sentry.xcframework; path = ../build/ios/framework/release/Sentry.xcframework; sourceTree = "<group>"; }; 07A63AA12B1E72FC00CAFA4D /* Sentry.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Sentry.xcframework; path = ../build/ios/framework/release/Sentry.xcframework; sourceTree = "<group>"; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
186131812EE29AAF007DB19F /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
2A04EFFC2E2C75F5005FA780 /* RunnerRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerRelease.entitlements; sourceTree = "<group>"; }; 2A04EFFC2E2C75F5005FA780 /* RunnerRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerRelease.entitlements; sourceTree = "<group>"; };
2A04EFFD2E2C784A005FA780 /* PacketTunnelRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PacketTunnelRelease.entitlements; sourceTree = "<group>"; }; 2A04EFFD2E2C784A005FA780 /* PacketTunnelRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PacketTunnelRelease.entitlements; sourceTree = "<group>"; };
2A9DDE192DCF45350006D7FC /* KRActiveGroupsEventHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KRActiveGroupsEventHandler.swift; sourceTree = "<group>"; }; 2A9DDE192DCF45350006D7FC /* KRActiveGroupsEventHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KRActiveGroupsEventHandler.swift; sourceTree = "<group>"; };
@@ -188,6 +190,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
186131822EE29AB0007DB19F /* StoreKit.framework in Frameworks */,
075637BA2B01583F005AFB8E /* Pods_Runner.framework in Frameworks */, 075637BA2B01583F005AFB8E /* Pods_Runner.framework in Frameworks */,
68885DD72B4EF33400D214BA /* NetworkExtension.framework in Frameworks */, 68885DD72B4EF33400D214BA /* NetworkExtension.framework in Frameworks */,
); );
@@ -367,6 +370,7 @@
B8133545EEE13EDD5549E6A3 /* Frameworks */ = { B8133545EEE13EDD5549E6A3 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
186131812EE29AAF007DB19F /* StoreKit.framework */,
413270612C752158003A1E9B /* Libcore.xcframework */, 413270612C752158003A1E9B /* Libcore.xcframework */,
0736954E2B1FEB3E007249BE /* mobile_scanner.xcframework */, 0736954E2B1FEB3E007249BE /* mobile_scanner.xcframework */,
07A63A932B1E72FB00CAFA4D /* cupertino_http.xcframework */, 07A63A932B1E72FB00CAFA4D /* cupertino_http.xcframework */,
@@ -758,11 +762,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 +792,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 +818,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 +848,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 +872,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 +902,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 +943,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";
@@ -965,7 +975,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
BUILD_LIBRARY_FOR_DISTRIBUTION = NO; BUILD_LIBRARY_FOR_DISTRIBUTION = NO;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
@@ -975,7 +984,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 +1013,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 +1033,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 +1052,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 +1069,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 +1171,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";
@@ -1195,17 +1205,16 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
BUILD_LIBRARY_FOR_DISTRIBUTION = NO; BUILD_LIBRARY_FOR_DISTRIBUTION = NO;
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 +1243,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";
@@ -1251,15 +1261,16 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
BUILD_LIBRARY_FOR_DISTRIBUTION = NO; BUILD_LIBRARY_FOR_DISTRIBUTION = NO;
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 +1299,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";
+14 -1
View File
@@ -1,7 +1,7 @@
import UIKit import UIKit
import Flutter import Flutter
import Libcore import Libcore
import PassKit
@main @main
@objc class AppDelegate: FlutterAppDelegate { @objc class AppDelegate: FlutterAppDelegate {
@@ -12,9 +12,22 @@ import Libcore
setupFileManager() setupFileManager()
registerHandlers() registerHandlers()
GeneratedPluginRegistrant.register(with: self) GeneratedPluginRegistrant.register(with: self)
// Apple Pay
debugApplePaySupport()
return super.application(application, didFinishLaunchingWithOptions: launchOptions) return super.application(application, didFinishLaunchingWithOptions: launchOptions)
} }
func debugApplePaySupport() {
// 使 Apple Pay
let canPay = PKPaymentAuthorizationController.canMakePayments()
print("💳 Can make payments: \(canPay)")
// 使
let canPayWithNetworks = PKPaymentAuthorizationController.canMakePayments(
usingNetworks: [.visa, .masterCard, .amex]
)
print("💳 Can pay with Visa/Master/Amex: \(canPayWithNetworks)")
}
func setupFileManager() { func setupFileManager() {
try? FileManager.default.createDirectory(at: FilePath.workingDirectory, withIntermediateDirectories: true) try? FileManager.default.createDirectory(at: FilePath.workingDirectory, withIntermediateDirectories: true)
FileManager.default.changeCurrentDirectoryPath(FilePath.sharedDirectory.path) FileManager.default.changeCurrentDirectoryPath(FilePath.sharedDirectory.path)
+3 -3
View File
@@ -33,7 +33,7 @@
<string>com.hi.ios</string> <string>com.hi.ios</string>
<key>CFBundleURLSchemes</key> <key>CFBundleURLSchemes</key>
<array> <array>
<string>HiFastVPN</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array> </array>
</dict> </dict>
</array> </array>
@@ -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>
+5 -1
View File
@@ -4,6 +4,10 @@
<dict> <dict>
<key>aps-environment</key> <key>aps-environment</key>
<string>development</string> <string>development</string>
<key>com.apple.developer.in-app-payments</key>
<array>
<string>merchant.com.taw.hifastvpn</string>
</array>
<key>com.apple.developer.networking.networkextension</key> <key>com.apple.developer.networking.networkextension</key>
<array> <array>
<string>packet-tunnel-provider</string> <string>packet-tunnel-provider</string>
@@ -12,7 +16,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/>
+5 -1
View File
@@ -4,6 +4,10 @@
<dict> <dict>
<key>aps-environment</key> <key>aps-environment</key>
<string>development</string> <string>development</string>
<key>com.apple.developer.in-app-payments</key>
<array>
<string>merchant.com.taw.hifastvpn</string>
</array>
<key>com.apple.developer.networking.networkextension</key> <key>com.apple.developer.networking.networkextension</key>
<array> <array>
<string>packet-tunnel-provider</string> <string>packet-tunnel-provider</string>
@@ -12,7 +16,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/>
+5 -2
View File
@@ -208,7 +208,10 @@ class VPNManager: ObservableObject {
} }
func disconnect() { func disconnect() {
guard state == .connected else { return } if state != .disconnected && state != .invalid {
manager.connection.stopVPNTunnel() manager.connection.stopVPNTunnel()
} else {
alert = VPNManagerAlert(alert: nil, message: nil)
}
} }
} }
+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);
} }
+93 -32
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;
@@ -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 = {
@@ -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}');
} }
@@ -90,7 +90,7 @@ 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) {
+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']),
@@ -8,8 +8,6 @@ class KRPurchaseOrderNo {
} }
} }
class KRPurchaseOrderUrl { class KRPurchaseOrderUrl {
final String url; final String url;
@@ -22,14 +20,16 @@ class KRPurchaseOrderUrl {
/// Checkout 响应(参考 Tauri 项目) /// Checkout 响应(参考 Tauri 项目)
class KRCheckoutResponse { class KRCheckoutResponse {
final String type; // "url" | "qr" | "stripe" final String type; // "url" | "qr" | "stripe" | "apple_iap"
final String? checkoutUrl; final String? checkoutUrl;
final KRStripePayment? stripe; final KRStripePayment? stripe;
final KRIpaPayment? ipa;
KRCheckoutResponse({ KRCheckoutResponse({
required this.type, required this.type,
this.checkoutUrl, this.checkoutUrl,
this.stripe, this.stripe,
this.ipa,
}); });
factory KRCheckoutResponse.fromJson(Map<String, dynamic> json) { factory KRCheckoutResponse.fromJson(Map<String, dynamic> json) {
@@ -39,6 +39,7 @@ class KRCheckoutResponse {
stripe: json['stripe'] != null stripe: json['stripe'] != null
? KRStripePayment.fromJson(json['stripe']) ? KRStripePayment.fromJson(json['stripe'])
: null, : null,
ipa: json['ipa'] != null ? KRIpaPayment.fromJson(json['ipa']) : null,
); );
} }
} }
@@ -63,3 +64,24 @@ class KRStripePayment {
); );
} }
} }
class KRIpaPayment {
final String productId;
final String? applicationUsername;
final String? orderNo;
KRIpaPayment({
required this.productId,
this.applicationUsername,
this.orderNo,
});
factory KRIpaPayment.fromJson(Map<String, dynamic> json) {
return KRIpaPayment(
productId: json['product_id'] ?? json['productId'] ?? '',
applicationUsername:
json['application_username'] ?? json['applicationUsername'],
orderNo: json['order_no'] ?? json['orderNo'],
);
}
}
@@ -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,
); );
} }
@@ -78,7 +78,7 @@ class KRUserAvailableSubscribeList {
}); });
factory KRUserAvailableSubscribeList.fromJson(Map<String, dynamic> json) { factory KRUserAvailableSubscribeList.fromJson(Map<String, dynamic> json) {
KRLogUtil.kr_i('订阅json列表: ${json}', tag: 'KRUserAvailableSubscribeList'); // KRLogUtil.kr_i('订阅json列表: ${json}', tag: 'KRUserAvailableSubscribeList');
final List<dynamic> listData = (json['list'] as List<dynamic>?) ?? const []; final List<dynamic> listData = (json['list'] as List<dynamic>?) ?? const [];
return KRUserAvailableSubscribeList( return KRUserAvailableSubscribeList(
list: listData list: listData
+64 -54
View File
@@ -13,76 +13,86 @@ import 'package:kaer_with_panels/app/widgets/hi_collapsible_list.dart';
import 'package:kaer_with_panels/app/widgets/hi_fixed_scrollbar.dart'; import 'package:kaer_with_panels/app/widgets/hi_fixed_scrollbar.dart';
import 'package:kaer_with_panels/app/modules/hi_menu/widgets/hi_menu_list_item.dart'; import 'package:kaer_with_panels/app/modules/hi_menu/widgets/hi_menu_list_item.dart';
import '../../../routes/app_pages.dart'; import '../../../routes/app_pages.dart';
import 'package:kaer_with_panels/app/widgets/swipe/has_swipe_config.dart';
import 'package:kaer_with_panels/app/widgets/swipe/swipe_config.dart';
class HIHelpView extends GetView<HIHelpController> { class HIHelpView extends GetView<HIHelpController> implements HasSwipeConfig {
const HIHelpView({super.key}); const HIHelpView({super.key});
@override
SwipeConfig get swipeConfig =>
SwipeConfig(enableLeft: true, onLeft: () => Get.back());
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
return HIBaseScaffold( return HIBaseScaffold(
child: Column( child: Padding(
children: [ padding: const EdgeInsets.only(left: 20),
Expanded( child: Column(
child: Obx( children: [
() => EasyRefresh( Expanded(
// controller: controller.refreshController, child: Obx(
// onRefresh: controller.kr_onRefresh, () => EasyRefresh(
// onLoad: controller.kr_onLoadMore, // controller: controller.refreshController,
// header: const DeliveryHeader( // onRefresh: controller.kr_onRefresh,
// triggerOffset: 50.0, // onLoad: controller.kr_onLoadMore,
// springRebound: true, // header: const DeliveryHeader(
// ), // triggerOffset: 50.0,
// footer: const DeliveryFooter( // springRebound: true,
// triggerOffset: 50.0, // ),
// springRebound: true, // footer: const DeliveryFooter(
// ), // triggerOffset: 50.0,
onRefresh: null, // null // springRebound: true,
onLoad: null, // null // ),
child: Padding( onRefresh: null, // null
padding: EdgeInsets.only(right: 0.w), // onLoad: null, // null
child: HiFixedScrollbar( child: Padding(
controller: scrollController, padding: EdgeInsets.only(right: 0.w), //
child: ListView.builder( child: HiFixedScrollbar(
controller: scrollController, controller: scrollController,
padding: EdgeInsets.symmetric(horizontal: 40.w), child: ListView.builder(
itemCount: controller.kr_messages.length, controller: scrollController,
itemBuilder: (context, index) { padding: EdgeInsets.only(left: 40.w - 20, right: 40.w),
final message = controller.kr_messages[index]; itemCount: controller.kr_messages.length,
final collapsibleItemData = HICollapsibleItem( itemBuilder: (context, index) {
title: message.title, final message = controller.kr_messages[index];
content: message.content, final collapsibleItemData = HICollapsibleItem(
); title: message.title,
return Padding( content: message.content,
padding: EdgeInsets.only(bottom: 10.w), );
child: HICollapsibleItemWidget(item: collapsibleItemData), return Padding(
); padding: EdgeInsets.only(bottom: 10.w),
}, child: HICollapsibleItemWidget(
item: collapsibleItemData),
);
},
),
), ),
), ),
), ),
), ),
), ),
), SizedBox(height: 60.w),
SizedBox(height: 60.w), // 线
// 线 Padding(
Padding( padding: EdgeInsets.symmetric(horizontal: 40.w),
padding: EdgeInsets.symmetric(horizontal: 40.w), child: MenuListItem(
child: MenuListItem( // 2. MenuItem
// 2. MenuItem item: MenuItem(
item: MenuItem( iconName: 'icon-5',
iconName: 'icon-5', title: '在线客服',
title: '在线客服', // 3. 使 onTap
// 3. 使 onTap onTap: () {
onTap: () { Get.toNamed(Routes.KR_CRISP);
Get.toNamed(Routes.KR_CRISP); },
}, ),
), ),
), ),
), SizedBox(height: 30.w)
SizedBox(height: 30.w) ],
], ),
), ),
); );
} }
@@ -12,6 +12,8 @@ class HIMenuController extends GetxController {
final KRSubscribeService kr_subscribeService = KRSubscribeService(); final KRSubscribeService kr_subscribeService = KRSubscribeService();
// //
final RxString kr_version = ''.obs; final RxString kr_version = ''.obs;
final RxString kr_buildNumber = ''.obs;
final RxBool kr_showBuild = false.obs;
@override @override
void onInit() { void onInit() {
@@ -23,5 +25,10 @@ class HIMenuController extends GetxController {
Future<void> _kr_getVersion() async { Future<void> _kr_getVersion() async {
final PackageInfo packageInfo = await PackageInfo.fromPlatform(); final PackageInfo packageInfo = await PackageInfo.fromPlatform();
kr_version.value = packageInfo.version; kr_version.value = packageInfo.version;
kr_buildNumber.value = packageInfo.buildNumber;
}
void toggleVersionDisplay() {
kr_showBuild.value = !kr_showBuild.value;
} }
} }
+29 -14
View File
@@ -6,6 +6,8 @@ import 'package:flutter_html/flutter_html.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:kaer_with_panels/app/widgets/hi_base_scaffold.dart'; import 'package:kaer_with_panels/app/widgets/hi_base_scaffold.dart';
import '../../../routes/app_pages.dart'; import '../../../routes/app_pages.dart';
import 'package:kaer_with_panels/app/widgets/swipe/has_swipe_config.dart';
import 'package:kaer_with_panels/app/widgets/swipe/swipe_config.dart';
import '../controllers/hi_menu_controller.dart'; import '../controllers/hi_menu_controller.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 'package:kaer_with_panels/app/localization/app_translations.dart'; import 'package:kaer_with_panels/app/localization/app_translations.dart';
@@ -16,9 +18,15 @@ import 'package:kaer_with_panels/app/common/app_run_data.dart';
import 'package:kaer_with_panels/app/modules/hi_menu/widgets/hi_menu_list_item.dart'; import 'package:kaer_with_panels/app/modules/hi_menu/widgets/hi_menu_list_item.dart';
import 'package:kaer_with_panels/app/modules/hi_menu/widgets/user_info_card.dart'; import 'package:kaer_with_panels/app/modules/hi_menu/widgets/user_info_card.dart';
class HIMenuView extends GetView<HIMenuController> { class HIMenuView extends GetView<HIMenuController> implements HasSwipeConfig {
const HIMenuView({super.key}); const HIMenuView({super.key});
@override
SwipeConfig get swipeConfig => SwipeConfig(
enableRight: true,
onRight: () => Get.offNamed(Routes.KR_HOME),
);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return HIBaseScaffold( return HIBaseScaffold(
@@ -37,10 +45,16 @@ 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 = (account == null || isDeviceLogin)
? '待绑定'
: '${KRAppRunData.getInstance().kr_account.value.toString()}';
return UserInfoCard( return UserInfoCard(
controller: controller, controller: controller,
userId: account, userId: accountText,
); );
}), }),
@@ -49,7 +63,8 @@ class HIMenuView extends GetView<HIMenuController> {
// ListView.separated Padding // ListView.separated Padding
ListView.separated( ListView.separated(
shrinkWrap: true, // ListView shrinkWrap: true, // ListView
physics: const NeverScrollableScrollPhysics(), // Stack physics:
const NeverScrollableScrollPhysics(), // Stack
itemCount: _menuItems.length, itemCount: _menuItems.length,
// //
itemBuilder: (context, index) { itemBuilder: (context, index) {
@@ -66,19 +81,20 @@ class HIMenuView extends GetView<HIMenuController> {
], ],
), ),
// //
Obx((){ Obx(() {
// 1. Obx return Widget
return Positioned( return Positioned(
bottom: 40.0, bottom: 40.0,
left: 0, left: 0,
right: 0, right: 0,
child: Text( child: GestureDetector(
'当前版本:${controller.kr_version.value}', onDoubleTap: () => controller.toggleVersionDisplay(),
textAlign: TextAlign.center, child: Text(
style: TextStyle( '当前版本:${controller.kr_showBuild.value ? '${controller.kr_version.value}.${controller.kr_buildNumber.value}' : controller.kr_version.value}',
// 2. 使 textAlign: TextAlign.center,
color: Theme.of(context).primaryColor, style: TextStyle(
fontSize: 10.w, color: Theme.of(context).primaryColor,
fontSize: 10.w,
),
), ),
), ),
); );
@@ -89,7 +105,6 @@ class HIMenuView extends GetView<HIMenuController> {
} }
} }
const List<MenuItem> _menuItems = [ const List<MenuItem> _menuItems = [
MenuItem( MenuItem(
iconName: 'icon-1', iconName: 'icon-1',
@@ -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
/// ///
@@ -30,10 +31,10 @@ class UserInfoCard extends StatelessWidget {
// //
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
child: Container( child: Container(
padding: EdgeInsets.fromLTRB(7.w, 4, 18.w, 4), padding: EdgeInsets.fromLTRB(5, 4, 18, 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.transparent, color: Colors.transparent,
borderRadius: BorderRadius.circular(35.5.r), borderRadius: BorderRadius.circular(35.5),
border: Border.all( border: Border.all(
color: Colors.white, color: Colors.white,
width: 2.0, // 👇 : 2.0 width: 2.0, // 👇 : 2.0
@@ -42,74 +43,35 @@ class UserInfoCard extends StatelessWidget {
child: Row( child: Row(
children: [ children: [
CircleAvatar( CircleAvatar(
radius: 18.r, radius: 18,
backgroundColor: Colors.white, backgroundColor: Colors.white,
child: KrLocalImage( child: KrLocalImage(
imageName: 'hi-home-logo', imageName: 'hi-home-logo',
imageType: ImageType.svg, imageType: ImageType.svg,
width: 16.w, width: 16,
height: 16.h, height: 16,
), ),
), ),
SizedBox(width: 12.w), SizedBox(width: 12),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
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,
),
);
}),
], ],
), ),
), ),
@@ -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,43 +496,87 @@ 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_selectedCountryTag.value;
imageType: ImageType.svg, final selected = selectedCountryField == country.country;
width: 16.w, // SVG return selected
height: 16.h, ? KrLocalImage(
) imageName: 'radio-active-icon',
: KrLocalImage( imageType: ImageType.svg,
imageName: 'radio-icon', width: 16.w,
imageType: ImageType.svg, height: 16.h,
width: 16.w, // SVG )
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),
@@ -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 = (account ==null || 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);
@@ -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;
@@ -1,65 +1,72 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter/services.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});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return HIBaseScaffold( return HIBaseScaffold(
child: SingleChildScrollView( child: GestureDetector(
child: Padding( behavior: HitTestBehavior.translucent,
padding: EdgeInsets.all(40.w), onTap: () {
child: Column( FocusScope.of(context).unfocus();
crossAxisAlignment: CrossAxisAlignment.center, },
children: [ child: SingleChildScrollView(
_buildUserInfoSection(), child: Padding(
SizedBox(height: 12.w), padding: EdgeInsets.all(40.w),
// child: Column(
ConstrainedBox( crossAxisAlignment: CrossAxisAlignment.center,
constraints: BoxConstraints( children: [
minHeight: 250.h, // 2. 300 (使 .h ) _buildUserInfoSection(),
), SizedBox(height: 12.w),
// 3. 使 Column MainAxisAlignment.center 使 //
child: Column( ConstrainedBox(
mainAxisAlignment: MainAxisAlignment.start, constraints: BoxConstraints(
children: [ minHeight: 250.h, // 2. 300 (使 .h )
_buildVerificationCodeField(), // 4.
],
),
),
SizedBox(height: 20.w),
GestureDetector(
onTap: () {
controller.requestDeleteAccount();
},
child: Container(
width: double.infinity,
padding: EdgeInsets.symmetric(vertical: 12.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.w),
border: Border.all(color: const Color(0xFFFF2ED1), width: 2),
), ),
alignment: Alignment.center, // 3. 使 Column MainAxisAlignment.center 使
child: Text( child: Column(
'注销账户', mainAxisAlignment: MainAxisAlignment.start,
style: TextStyle( children: [
color: const Color(0xFFFF2ED1), _buildVerificationCodeField(), // 4.
fontSize: 16.sp, ],
fontWeight: FontWeight.w700, ),
),
SizedBox(height: 20.w),
GestureDetector(
onTap: () {
controller.requestDeleteAccount();
},
child: Container(
width: double.infinity,
padding: EdgeInsets.symmetric(vertical: 12.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.w),
border:
Border.all(color: const Color(0xFFFF2ED1), width: 2),
),
alignment: Alignment.center,
child: Text(
'注销账户',
style: TextStyle(
color: const Color(0xFFFF2ED1),
fontSize: 16.sp,
fontWeight: FontWeight.w700,
),
), ),
), ),
), ),
), /*SizedBox(
/*SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
onPressed: controller.requestDeleteAccount, onPressed: controller.requestDeleteAccount,
@@ -79,15 +86,15 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
), ),
), ),
),*/ ),*/
SizedBox(height: 20.w), SizedBox(height: 20.w),
], ],
),
), ),
), ),
), ),
); );
} }
Widget _buildUserInfoSection() { Widget _buildUserInfoSection() {
return Row( return Row(
children: [ children: [
@@ -129,12 +136,14 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
); );
}), }),
Obx(() { Obx(() {
final _ = KRAppRunData.getInstance().kr_isLogin.value; final account = KRAppRunData.getInstance().kr_account.value;
final userId = (KRAppRunData.getInstance().kr_userId.value ?? '').toString(); final isDeviceLogin =
final deviceId = KRAppRunData.getInstance().deviceId ?? ''; account != null && account.startsWith('9000');
final accountText = (account ==null || 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 +151,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,
),
);
}),
], ],
), ),
), ),
@@ -197,10 +169,44 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
/// ///
Widget _buildVerificationCodeField() { Widget _buildVerificationCodeField() {
return SizedBox( return SizedBox(
height: 50.w, // height: 50.w,
child: TextField( child: TextField(
controller: controller.kr_codeController, controller: controller.kr_codeController,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
textInputAction: TextInputAction.done,
autofillHints: const [AutofillHints.oneTimeCode],
enableSuggestions: false,
autocorrect: false,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
onChanged: (value) {
var v = value.replaceAll(RegExp(r"\s+"), "");
if (v.length % 2 == 0 && v.isNotEmpty) {
final half = v.length ~/ 2;
final first = v.substring(0, half);
final second = v.substring(half);
if (first == second) {
v = first;
}
}
const maxLen = 6;
if (v.length > maxLen) {
v = v.substring(0, maxLen);
}
if (controller.kr_codeController.text != v) {
controller.kr_codeController.value =
controller.kr_codeController.value.copyWith(
text: v,
selection: TextSelection.collapsed(offset: v.length),
composing: TextRange.empty,
);
}
if (v.isNotEmpty && (v.length >= 6)) {
FocusScope.of(Get.context!).unfocus();
}
},
onSubmitted: (_) {
FocusScope.of(Get.context!).unfocus();
},
style: KrAppTextStyle( style: KrAppTextStyle(
fontSize: 16, fontSize: 16,
color: Colors.white, color: Colors.white,
@@ -212,37 +218,40 @@ 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: const BorderSide(color: Colors.white, width: 2),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.w), borderRadius: BorderRadius.circular(25.w),
borderSide: BorderSide(color: Colors.white, width: 2), borderSide: const BorderSide(color: Colors.white, width: 2),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.w), borderRadius: BorderRadius.circular(25.w),
borderSide: BorderSide(color: Colors.white, width: 2), borderSide: const BorderSide(color: Colors.white, width: 2),
), ),
isDense: true, isDense: true,
suffixIconConstraints: BoxConstraints( suffixIconConstraints: BoxConstraints(
maxHeight: 50.w, // maxHeight: 50.w,
maxWidth: 190.w,
), ),
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),
), ),
child: Text( child: Text(
controller.kr_canSendCode.value controller.kr_canSendCode.value
@@ -253,7 +262,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),
), ),
), ),
), ),
@@ -1,15 +1,21 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../controllers/kr_home_controller.dart'; import '../controllers/kr_home_controller.dart';
// IAP Service
import 'package:kaer_with_panels/app/services/iap/iap_service.dart';
class KRHomeBinding extends Bindings { class KRHomeBinding extends Bindings {
@override @override
void dependencies() { void dependencies() {
// 1. Home ()
Get.lazyPut<KRHomeController>( Get.lazyPut<KRHomeController>(
() => KRHomeController(), () => KRHomeController(),
); );
Get.lazyPut<KRHomeController>(
() => KRHomeController(), // 2. Home IAP Service
// 使 fenix: true Service
Get.lazyPut<KRIAPService>(
() => KRIAPService(),
fenix: true,
); );
} }
} }
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,8 @@
import 'dart:math'; import 'dart:math';
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.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/kr_local_image.dart'; import 'package:kaer_with_panels/app/widgets/kr_local_image.dart';
@@ -9,6 +11,10 @@ import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
import 'package:kaer_with_panels/app/services/global_overlay_service.dart'; import 'package:kaer_with_panels/app/services/global_overlay_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/singbox/model/singbox_status.dart'; import 'package:kaer_with_panels/singbox/model/singbox_status.dart';
import 'package:kaer_with_panels/app/utils/account_guard.dart';
DateTime? _hiConnectBtnNextAllowedAt;
const Duration _hiConnectBtnDebounce = Duration(milliseconds: 800);
/// ///
class HIAnimatedConnectButton extends GetView<KRHomeController> { class HIAnimatedConnectButton extends GetView<KRHomeController> {
@@ -25,16 +31,18 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
final delay = controller.kr_currentNodeLatency.value; final delay = controller.kr_currentNodeLatency.value;
// 🔧 : observable // 🔧 : observable
final _ = KRSingBoxImp.instance.kr_status.value; // final _ = KRSingBoxImp.instance.kr_status.value; //
final isConnected = controller.kr_isConnected.value; // 使 controller final isConnected = controller.kr_isConnected.value; // 使 controller
// //
final status = KRSingBoxImp.instance.kr_status.value; final status = KRSingBoxImp.instance.kr_status.value;
final isSwitching = status is SingboxStarting || status is SingboxStopping; final isSwitching =
status is SingboxStarting || status is SingboxStopping;
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;
@@ -61,35 +69,58 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
maxWidth: buttonSize * 3, maxWidth: buttonSize * 3,
maxHeight: buttonSize * 3, maxHeight: buttonSize * 3,
child: ContinuousRippleEffect( child: ContinuousRippleEffect(
color: Theme.of(context).primaryColor color: Theme.of(context).primaryColor),
),
), ),
), ),
if (!isShow) if (!isShow)
Positioned( Positioned(
top: -20, // 10.w top: -20, // 10.w
left: 115, // 10.w left: 115, // 10.w
child: KrLocalImage( child: KrLocalImage(
imageName: "hi-home-slogan", imageName: "hi-home-slogan",
imageType: ImageType.svg, imageType: ImageType.svg,
), ),
), ),
/// ///
Material( Material(
shape: const CircleBorder(), shape: const CircleBorder(),
color: isShow ? Colors.transparent : Theme.of(context).primaryColor, color: isShow
? Colors.transparent
: Theme.of(context).primaryColor,
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: InkWell( child: InkWell(
onTap: () { onTap: () async {
if(isSwitching) { HapticFeedback.lightImpact();
print('🔵 Switch UI 正在更新,切换中点击了按钮: status=${status.runtimeType}, isConnected=$isConnected, isSwitching=$isSwitching'); final ok = await ensureAccountExists();
if (!ok) return;
final _now = DateTime.now();
if (_hiConnectBtnNextAllowedAt != null &&
_now.isBefore(_hiConnectBtnNextAllowedAt!)) {
_hiConnectBtnNextAllowedAt =
_now.add(_hiConnectBtnDebounce);
return; return;
} }
final hasValidSubscription = _hiConnectBtnNextAllowedAt =
controller.kr_subscribeService.kr_availableSubscribes.isNotEmpty; _now.add(_hiConnectBtnDebounce);
final current = controller
.kr_subscribeService.kr_currentSubscribe.value;
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 {
HIDialog.show( HIDialog.show(
customMessageWidget: Padding( customMessageWidget: Padding(
@@ -98,7 +129,10 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
'尚未购买套餐,请前往购买页面下单后即可开始极速网络体验', '尚未购买套餐,请前往购买页面下单后即可开始极速网络体验',
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,
), ),
@@ -107,7 +141,8 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
cancelText: '取消', cancelText: '取消',
confirmText: '前往', confirmText: '前往',
onConfirm: () { onConfirm: () {
GlobalOverlayService.instance.triggerSubscriptionAnimation(); GlobalOverlayService.instance
.triggerSubscriptionAnimation();
}, },
); );
} }
@@ -119,7 +154,8 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
if (isShow) if (isShow)
/// isShow = true
/// isShow = true
Stack( Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
@@ -139,20 +175,21 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
controller.kr_connectText.value == '已连接' controller.kr_connectText.value == '已连接'
? '已连接\n点击断开' ? '已连接\n点击断开'
: controller.kr_connectText.value, : controller.kr_connectText.value,
key: ValueKey(controller.kr_connectText.value), key: ValueKey(
controller.kr_connectText.value),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: Colors.black, color: Colors.black,
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
height: 1 height: 1),
),
), ),
), ),
], ],
) )
else else
/// isShow = false
/// isShow = false
Column( Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -167,7 +204,8 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
controller.kr_connectText.value == '已连接' controller.kr_connectText.value == '已连接'
? '已连接\n点击断开' ? '已连接\n点击断开'
: controller.kr_connectText.value, : controller.kr_connectText.value,
key: ValueKey(controller.kr_connectText.value), key:
ValueKey(controller.kr_connectText.value),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: Colors.black, color: Colors.black,
@@ -180,11 +218,8 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
], ],
), ),
), ),
), ),
), ),
], ],
), ),
), ),
@@ -193,6 +228,7 @@ class HIAnimatedConnectButton extends GetView<KRHomeController> {
}); });
} }
} }
/// 270270~47910% /// 270270~47910%
/// 270~470 /// 270~470
class ContinuousRippleEffect extends StatefulWidget { class ContinuousRippleEffect extends StatefulWidget {
@@ -223,10 +259,11 @@ class _ContinuousRippleEffectState extends State<ContinuousRippleEffect>
// //
final duration = Duration(milliseconds: 3000 + i * 1200); final duration = Duration(milliseconds: 3000 + i * 1200);
final controller = final controller = AnimationController(vsync: this, duration: duration)
AnimationController(vsync: this, duration: duration)..repeat(reverse: true); ..repeat(reverse: true);
final curved = CurvedAnimation(parent: controller, curve: Curves.easeInOutSine); final curved =
CurvedAnimation(parent: controller, curve: Curves.easeInOutSine);
_controllers.add(controller); _controllers.add(controller);
_animations.add(curved); _animations.add(curved);
} }
@@ -291,4 +328,3 @@ class _ContinuousRippleEffectState extends State<ContinuousRippleEffect>
); );
} }
} }
@@ -8,6 +8,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../controllers/kr_home_controller.dart'; import '../controllers/kr_home_controller.dart';
import '../../../routes/app_pages.dart'; import '../../../routes/app_pages.dart';
import 'package:kaer_with_panels/app/services/global_overlay_service.dart'; import 'package:kaer_with_panels/app/services/global_overlay_service.dart';
import 'package:kaer_with_panels/app/utils/account_guard.dart';
// ======================= 1. Circular Clipper () ======================= // ======================= 1. Circular Clipper () =======================
class CircularClipper extends CustomClipper<Path> { class CircularClipper extends CustomClipper<Path> {
@@ -55,7 +56,7 @@ class CustomCircleRoute extends PageRouteBuilder {
required this.child, required this.child,
required this.startOffset, required this.startOffset,
required this.transitionColor, required this.transitionColor,
this.customDuration = const Duration(milliseconds: 1500), this.customDuration = const Duration(milliseconds: 250),
}) : super( }) : super(
opaque: false, opaque: false,
transitionDuration: customDuration, transitionDuration: customDuration,
@@ -137,7 +138,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; //
} }
@@ -162,7 +163,7 @@ class _HISubscriptionCornerButtonState extends State<HISubscriptionCornerButton>
child: KRPurchaseMembershipView(), child: KRPurchaseMembershipView(),
startOffset: startOffset, startOffset: startOffset,
transitionColor: transitionColor, transitionColor: transitionColor,
customDuration: const Duration(milliseconds: 1500), customDuration: const Duration(milliseconds: 500),
), ),
).then((_) { ).then((_) {
// //
@@ -173,7 +174,11 @@ class _HISubscriptionCornerButtonState extends State<HISubscriptionCornerButton>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
onTap: () => _startCircularTransition(context), onTap: () async {
final ok = await ensureAccountExists();
if (!ok) return;
_startCircularTransition(context);
},
child: Container( child: Container(
key: _buttonKey, // GlobalKey key: _buttonKey, // GlobalKey
width: widget.size, width: widget.size,
+198 -167
View File
@@ -18,9 +18,30 @@ 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';
import 'package:kaer_with_panels/app/widgets/swipe/has_swipe_config.dart';
import 'package:kaer_with_panels/app/widgets/swipe/swipe_config.dart';
import 'package:kaer_with_panels/app/routes/app_pages.dart';
import 'package:kaer_with_panels/app/utils/account_guard.dart';
class KRHomeView extends StatefulWidget { class KRHomeView extends StatefulWidget implements HasSwipeConfig {
const KRHomeView({super.key}); const KRHomeView({super.key});
@override
SwipeConfig get swipeConfig => SwipeConfig(
enableLeft: true,
enableRight: true,
onLeft: () {
ensureAccountExists().then((ok) {
if (!ok) return;
Get.toNamed(Routes.HI_MENU);
});
},
onRight: () {
ensureAccountExists().then((ok) {
if (!ok) return;
GlobalOverlayService.instance.triggerSubscriptionAnimation();
});
},
);
@override @override
State<KRHomeView> createState() => _KRHomeViewState(); State<KRHomeView> createState() => _KRHomeViewState();
@@ -35,8 +56,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 +69,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,
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,
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(), );
],
);
} }
} }
@@ -6,6 +6,7 @@ import 'package:kaer_with_panels/app/localization/app_translations.dart';
import 'package:kaer_with_panels/app/widgets/kr_local_image.dart'; import 'package:kaer_with_panels/app/widgets/kr_local_image.dart';
import '../controllers/kr_invite_controller.dart'; import '../controllers/kr_invite_controller.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:share_plus/share_plus.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/widgets/hi_base_scaffold.dart'; import 'package:kaer_with_panels/app/widgets/hi_base_scaffold.dart';
import 'package:kaer_with_panels/app/widgets/hi_help_entrance.dart'; import 'package:kaer_with_panels/app/widgets/hi_help_entrance.dart';
@@ -15,7 +16,6 @@ class KRInviteView extends GetView<KRInviteController> {
@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;
return HIBaseScaffold( return HIBaseScaffold(
@@ -34,14 +34,16 @@ class KRInviteView extends GetView<KRInviteController> {
children: [ children: [
// 🟢 // 🟢
Container( Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.w), width: double.infinity,
padding:
EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.w),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(25.r), borderRadius: BorderRadius.circular(25.r),
), ),
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',
@@ -67,13 +69,14 @@ class KRInviteView extends GetView<KRInviteController> {
SizedBox(height: 26.w), SizedBox(height: 26.w),
// 🟢 // 🟢
Container( Container(
padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 2.w), padding:
EdgeInsets.symmetric(horizontal: 4.w, vertical: 2.w),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all(color: Colors.white, width: 2.0), border: Border.all(color: Colors.white, width: 2.0),
borderRadius: BorderRadius.circular(1000.r), borderRadius: BorderRadius.circular(1000.r),
), ),
child: Obx( child: Obx(
() => Row( () => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Container( Container(
@@ -112,12 +115,27 @@ class KRInviteView extends GetView<KRInviteController> {
), ),
onPressed: () { onPressed: () {
if (controller.kr_referCode.value.isNotEmpty) { if (controller.kr_referCode.value.isNotEmpty) {
Clipboard.setData( final code = controller.kr_referCode.value;
ClipboardData(text: controller.kr_referCode.value), final text = '#您的好友邀请您使用Hi快网络加速器\n'
); '安装完毕后,在软件内<邀请好友>页面粘贴以下邀请码\n'
KRCommonUtil.kr_showToast( '$code\n'
AppTranslations.kr_invite.inviteCodeCopied, '您和您的好友将会分别获得3天免费时长\n\n'
); '点击此处进入下载页面\n'
'或在浏览器输入hifastvpn.com下载#';
if (GetPlatform.isIOS) {
Share.share(
text,
subject: '直接分享Hi快VPN邀请链接',
);
} else {
Clipboard.setData(
ClipboardData(
text: text),
);
KRCommonUtil.kr_showToast(
AppTranslations.kr_invite.inviteCodeCopied,
);
}
} }
}, },
), ),
@@ -142,24 +160,29 @@ class KRInviteView extends GetView<KRInviteController> {
TextField( TextField(
controller: controller.otherInviteCodeController, controller: controller.otherInviteCodeController,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold), style: const TextStyle(
color: Colors.white, fontWeight: FontWeight.bold),
decoration: InputDecoration( decoration: InputDecoration(
hintText: '填入邀请人邀请码兑换免费时长...', hintText: '填入邀请人邀请码兑换免费时长...',
hintStyle: const TextStyle(color: Color(0xFFA6A6A6)), hintStyle: const TextStyle(color: Color(0xFFA6A6A6)),
filled: true, filled: true,
fillColor: Colors.transparent, fillColor: Colors.transparent,
contentPadding: EdgeInsets.symmetric(horizontal: 22.w), contentPadding:
EdgeInsets.symmetric(horizontal: 22.w),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(1000.r), borderRadius: BorderRadius.circular(1000.r),
borderSide: const BorderSide(color: Colors.white, width: 2.0), borderSide: const BorderSide(
color: Colors.white, width: 2.0),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(1000.r), borderRadius: BorderRadius.circular(1000.r),
borderSide: const BorderSide(color: Colors.white, width: 2.0), borderSide: const BorderSide(
color: Colors.white, width: 2.0),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(1000.r), borderRadius: BorderRadius.circular(1000.r),
borderSide: const BorderSide(color: Colors.white, width: 2.0), borderSide: const BorderSide(
color: Colors.white, width: 2.0),
), ),
constraints: BoxConstraints(maxHeight: 50.h), constraints: BoxConstraints(maxHeight: 50.h),
), ),
@@ -198,8 +221,7 @@ class KRInviteView extends GetView<KRInviteController> {
), ),
// 5. HIHelpEntrance Stack // 5. HIHelpEntrance Stack
if (!isKeyboardVisible) if (!isKeyboardVisible) const HIHelpEntrance(),
const HIHelpEntrance(),
], ],
), ),
); );
@@ -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,14 @@ class KRLoginController extends GetxController
); );
either.fold( either.fold(
(l) => KRCommonUtil.kr_showToast(l.msg), (l) {
HIDialog.show(
message: l.msg,
preventBackDismiss: true,
confirmText: '确定',
);
return;
},
(r) async { (r) async {
_saveLoginData(r); _saveLoginData(r);
KRCommonUtil.kr_showToast('登录成功'); KRCommonUtil.kr_showToast('登录成功');
@@ -12,6 +12,8 @@ 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';
import 'package:flutter/services.dart';
class KRLoginView extends GetView<KRLoginController> { class KRLoginView extends GetView<KRLoginController> {
const KRLoginView({super.key}); const KRLoginView({super.key});
@@ -19,32 +21,40 @@ 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,
child: Stack( child: Stack(
children: [ children: [
SingleChildScrollView( GestureDetector(
child: Padding( behavior: HitTestBehavior.translucent,
padding: EdgeInsets.symmetric(horizontal: 40.w,), onTap: () {
child: Column( FocusScope.of(context).unfocus();
children: [ },
// Text( child: SingleChildScrollView(
// '${controller.kr_loginStatus.value}', child: Padding(
// style: TextStyle(color: Colors.white), // 使 TextStyle() padding: EdgeInsets.symmetric(
// ), horizontal: 40.w,
SizedBox(height: 20.w), ),
_buildUserInfoSection(), child: Column(
SizedBox(height: 12.w), children: [
_buildContentByEntry(), // Text(
SizedBox(height: 100.w), // // '${controller.kr_loginStatus.value}',
], // style: TextStyle(color: Colors.white), // 使 TextStyle()
// ),
SizedBox(height: 20.w),
_buildUserInfoSection(),
SizedBox(height: 12.w),
_buildBindEmailLayout(),
SizedBox(height: 100.w), //
],
),
), ),
), ),
), ),
if (!isKeyboardVisible) if (!isKeyboardVisible) const HIHelpEntrance(),
const HIHelpEntrance(),
], ],
), ),
); );
@@ -76,27 +86,15 @@ 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().kr_account.value;
final email = KRAppRunData.getInstance().kr_account.value ?? ''; final isDeviceLogin =
return Text( account != null && account.startsWith('9000');
email.isNotEmpty ? email : '', final accountText = (account ==null || isDeviceLogin)
style: TextStyle( ? '待绑定'
color: Colors.white, : '${KRAppRunData.getInstance().kr_account.value.toString()}';
fontSize: 20.sp,
fontWeight: FontWeight.bold,
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 +102,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,
),
);
}),
], ],
), ),
), ),
@@ -219,7 +180,7 @@ class KRLoginView extends GetView<KRLoginController> {
// hintText: '确认密码', // hintText: '确认密码',
// isPassword: true, // isPassword: true,
// ), // ),
SizedBox(height: 10.w), SizedBox(height: 10),
// 👇 使 // 👇 使
_buildVerificationCodeField(), _buildVerificationCodeField(),
], ],
@@ -242,7 +203,7 @@ class KRLoginView extends GetView<KRLoginController> {
controller: controller.accountController, controller: controller.accountController,
hintText: 'Email', hintText: 'Email',
), ),
SizedBox(height: 10.w), SizedBox(height: 10),
_buildStandardInputField( _buildStandardInputField(
controller: controller.psdController, controller: controller.psdController,
hintText: '密码', hintText: '密码',
@@ -265,7 +226,7 @@ class KRLoginView extends GetView<KRLoginController> {
bool isPassword = false, bool isPassword = false,
}) { }) {
return SizedBox( return SizedBox(
height: 50.w, // // height: 50, //
child: TextField( child: TextField(
controller: controller, controller: controller,
obscureText: isPassword, obscureText: isPassword,
@@ -280,7 +241,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),
@@ -310,9 +272,44 @@ class KRLoginView extends GetView<KRLoginController> {
// return SizedBox.shrink(); // return SizedBox.shrink();
// } // }
return SizedBox( return SizedBox(
height: 50.w, // height: 50, //
child: TextField( child: TextField(
controller: controller.codeController, controller: controller.codeController,
keyboardType: TextInputType.number,
textInputAction: TextInputAction.done,
autofillHints: const [AutofillHints.oneTimeCode],
enableSuggestions: false,
autocorrect: false,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
onChanged: (value) {
var v = value.replaceAll(RegExp("\\s+"), "");
if (v.length % 2 == 0 && v.isNotEmpty) {
final half = v.length ~/ 2;
final first = v.substring(0, half);
final second = v.substring(half);
if (first == second) {
v = first;
}
}
const maxLen = 6;
if (v.length > maxLen) {
v = v.substring(0, maxLen);
}
if (controller.codeController.text != v) {
controller.codeController.value =
controller.codeController.value.copyWith(
text: v,
selection: TextSelection.collapsed(offset: v.length),
composing: TextRange.empty,
);
}
if (v.isNotEmpty && (v.length >= 6)) {
FocusScope.of(Get.context!).unfocus();
}
},
onSubmitted: (_) {
FocusScope.of(Get.context!).unfocus();
},
style: KrAppTextStyle( style: KrAppTextStyle(
fontSize: 16, fontSize: 16,
color: Colors.white, color: Colors.white,
@@ -324,7 +321,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 +342,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 +362,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 +374,6 @@ class KRLoginView extends GetView<KRLoginController> {
); );
} }
Widget _buildSaveButton() { Widget _buildSaveButton() {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
@@ -383,7 +381,7 @@ class KRLoginView extends GetView<KRLoginController> {
}, },
child: Container( child: Container(
width: double.infinity, width: double.infinity,
height: 50.w, height: 50,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(Get.context!).primaryColor, color: Theme.of(Get.context!).primaryColor,
borderRadius: BorderRadius.circular(100.r), borderRadius: BorderRadius.circular(100.r),
@@ -13,77 +13,104 @@ import 'package:kaer_with_panels/app/widgets/hi_help_entrance.dart';
import 'package:kaer_with_panels/app/widgets/hi_collapsible_list.dart'; import 'package:kaer_with_panels/app/widgets/hi_collapsible_list.dart';
import 'package:kaer_with_panels/app/widgets/hi_fixed_scrollbar.dart'; import 'package:kaer_with_panels/app/widgets/hi_fixed_scrollbar.dart';
import '../../../widgets/kr_simple_loading.dart'; import '../../../widgets/kr_simple_loading.dart';
class KRMessageView extends GetView<KRMessageController> { import 'package:kaer_with_panels/app/widgets/swipe/has_swipe_config.dart';
import 'package:kaer_with_panels/app/widgets/swipe/swipe_config.dart';
class KRMessageView extends GetView<KRMessageController>
implements HasSwipeConfig {
const KRMessageView({super.key}); const KRMessageView({super.key});
@override
SwipeConfig get swipeConfig =>
SwipeConfig(enableLeft: true, onLeft: () => Get.back());
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
return HIBaseScaffold( return HIBaseScaffold(
child: Stack( child: Padding(
children: [ padding: const EdgeInsets.only(left: 20),
// child: Stack(
Obx(() { children: [
return EasyRefresh( //
controller: controller.refreshController, Obx(() {
onRefresh: controller.kr_onRefresh, return Column(
onLoad: controller.kr_onLoadMore, children: [
header: ClassicHeader( Expanded(
dragText: '下拉刷新', child: Padding(
armedText: '释放刷新', padding: EdgeInsets.only(bottom: 90.w),
readyText: '正在刷新...', child: EasyRefresh(
processingText: '正在刷新...', controller: controller.refreshController,
processedText: '刷新成功', onRefresh: controller.kr_onRefresh,
failedText: '刷新失败', onLoad: controller.kr_onLoadMore,
messageText: '最后更新于 %T', header: ClassicHeader(
textStyle: TextStyle(color: Colors.white.withOpacity(0.7)), dragText: '下拉刷新',
messageStyle: TextStyle(color: Colors.white.withOpacity(0.5), fontSize: 12.sp), armedText: '释放刷新',
iconTheme: IconThemeData(color: Colors.white.withOpacity(0.7)), readyText: '正在刷新...',
), processingText: '正在刷新...',
// 3. Footer UI提示 processedText: '刷新成功',
footer: ClassicFooter( failedText: '刷新失败',
dragText: '上拉加载', messageText: '最后更新于 %T',
armedText: '释放加载', textStyle:
readyText: '正在加载...', TextStyle(color: Colors.white.withOpacity(0.7)),
processingText: '正在加载...', messageStyle: TextStyle(
processedText: '加载成功', color: Colors.white.withOpacity(0.5),
failedText: '加载失败', fontSize: 12.sp),
noMoreText: '没有更多数据了', iconTheme: IconThemeData(
messageText: '最后更新于 %T', color: Colors.white.withOpacity(0.7)),
textStyle: TextStyle(color: Colors.white.withOpacity(0.7)), ),
messageStyle: TextStyle(color: Colors.white.withOpacity(0.5), fontSize: 12.sp), footer: ClassicFooter(
iconTheme: IconThemeData(color: Colors.white.withOpacity(0.7)), dragText: '上拉加载',
), armedText: '释放加载',
child: Padding( readyText: '正在加载...',
padding: EdgeInsets.only(right: 0.w, bottom: 90.w), // HIHelpEntrance留出空间 processingText: '正在加载...',
child: HiFixedScrollbar( processedText: '加载成功',
controller: scrollController, failedText: '加载失败',
isShowScrollbar: controller.kr_messages.length > 0, noMoreText: '没有更多数据了',
child: ListView.builder( messageText: '最后更新于 %T',
controller: scrollController, textStyle:
padding: EdgeInsets.symmetric(horizontal: 40.w), TextStyle(color: Colors.white.withOpacity(0.7)),
itemCount: controller.kr_messages.length, messageStyle: TextStyle(
itemBuilder: (context, index) { color: Colors.white.withOpacity(0.5),
final message = controller.kr_messages[index]; fontSize: 12.sp),
final collapsibleItemData = HICollapsibleItem( iconTheme: IconThemeData(
title: message.title, color: Colors.white.withOpacity(0.7)),
content: [message.content], ),
); child: Padding(
return Padding( padding: EdgeInsets.only(right: 0.w),
padding: EdgeInsets.only(bottom: 10.w), child: HiFixedScrollbar(
child: HICollapsibleItemWidget(item: collapsibleItemData), controller: scrollController,
); child: ListView.builder(
}, controller: scrollController,
padding:
EdgeInsets.only(left: 40.w - 20, right: 40.w),
itemCount: controller.kr_messages.length,
itemBuilder: (context, index) {
final message = controller.kr_messages[index];
final collapsibleItemData = HICollapsibleItem(
title: message.title,
content: [message.content],
);
return Padding(
padding: EdgeInsets.only(bottom: 10.w),
child: HICollapsibleItemWidget(
item: collapsibleItemData),
);
},
),
),
),
),
),
), ),
), ],
), );
); }),
//
},), const HIHelpEntrance(),
// ],
const HIHelpEntrance(), ),
],
), ),
); );
} }
@@ -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,36 @@ 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: 0.w),
color: Theme.of(context).textTheme.bodyMedium?.color, child: Column(
), mainAxisAlignment: MainAxisAlignment.center,
onPressed: () => Get.back(), children: [
), //
centerTitle: true, _buildStatusText(),
title: Text( SizedBox(height: 16.h),
AppTranslations.kr_orderStatus.title, //
style: KrAppTextStyle( _buildDescriptionText(),
color: Theme.of(context).textTheme.bodyMedium?.color, SizedBox(height: 32.h),
fontSize: 16, //
fontWeight: FontWeight.w500, if (controller.kr_isPaymentSuccess.value)
), _buildSuccessButtons(),
), SizedBox(height: 100.w),
), ],
body: Container( ),
decoration: BoxDecoration(
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 +106,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),
@@ -17,6 +17,13 @@ import '../../../services/api_service/kr_api.user.dart';
import '../../../utils/kr_event_bus.dart'; import '../../../utils/kr_event_bus.dart';
import '../../../network/http_util.dart'; import '../../../network/http_util.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_stripe/flutter_stripe.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'dart:async';
import 'package:kaer_with_panels/app/model/response/kr_purchase_order_no.dart';
import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart';
import 'package:kaer_with_panels/app/services/iap/iap_pending_order_service.dart';
import 'package:kaer_with_panels/app/services/iap/iap_service.dart';
/// ///
/// ///
@@ -52,7 +59,8 @@ class KRPurchaseMembershipController extends GetxController {
@override @override
void onInit() async { void onInit() async {
super.onInit(); super.onInit();
print('💳 [PurchaseMembership] ========== Controller.onInit 被调用 =========='); print(
'💳 [PurchaseMembership] ========== Controller.onInit 被调用 ==========');
print('💳 [PurchaseMembership] 当前时间: ${DateTime.now()}'); print('💳 [PurchaseMembership] 当前时间: ${DateTime.now()}');
// 🔧 Controller被初始化 // 🔧 Controller被初始化
@@ -60,11 +68,14 @@ class KRPurchaseMembershipController extends GetxController {
final dir = await getApplicationDocumentsDirectory(); final dir = await getApplicationDocumentsDirectory();
final debugFile = File('${dir.path}/PURCHASE_CONTROLLER_DEBUG.txt'); final debugFile = File('${dir.path}/PURCHASE_CONTROLLER_DEBUG.txt');
await debugFile.writeAsString( await debugFile.writeAsString(
'=' * 60 + '\n' '=' * 60 +
'💳 PurchaseMembershipController.onInit 被调用!\n' '\n'
'时间: ${DateTime.now()}\n' '💳 PurchaseMembershipController.onInit 被调用!\n'
'版本标识: Android15_Fix_v6_Final\n' '时间: ${DateTime.now()}\n'
'=' * 60 + '\n', '版本标识: Android15_Fix_v6_Final\n'
'=' *
60 +
'\n',
mode: FileMode.append, mode: FileMode.append,
); );
print('💳 [PurchaseMembership] ✅ 调试日志已写入文件: ${debugFile.path}'); print('💳 [PurchaseMembership] ✅ 调试日志已写入文件: ${debugFile.path}');
@@ -177,7 +188,8 @@ class KRPurchaseMembershipController extends GetxController {
KRLogUtil.kr_w('获取支付方式超时', tag: 'PurchaseMembership'); KRLogUtil.kr_w('获取支付方式超时', tag: 'PurchaseMembership');
}, },
); );
print('💳 [PurchaseMembership] ✓ 步骤4完成,支付方式数量: ${kr_paymentMethods.length}'); print(
'💳 [PurchaseMembership] ✓ 步骤4完成,支付方式数量: ${kr_paymentMethods.length}');
// //
kr_showPlanSelector.value = kr_plans.length > 1; kr_showPlanSelector.value = kr_plans.length > 1;
@@ -192,7 +204,8 @@ class KRPurchaseMembershipController extends GetxController {
bool domainSwitched = await KRDomain.kr_switchToNextDomain(); bool domainSwitched = await KRDomain.kr_switchToNextDomain();
if (domainSwitched) { if (domainSwitched) {
print('💳 [PurchaseMembership] ✓ 域名切换成功,当前域名: ${KRDomain.kr_currentDomain}'); print(
'💳 [PurchaseMembership] ✓ 域名切换成功,当前域名: ${KRDomain.kr_currentDomain}');
print('💳 [PurchaseMembership] 🔄 使用新域名重试...'); print('💳 [PurchaseMembership] 🔄 使用新域名重试...');
// HttpUtil baseUrl // HttpUtil baseUrl
@@ -299,7 +312,8 @@ class KRPurchaseMembershipController extends GetxController {
final either = await _kr_subscribeApi.kr_getPublicPaymentMethods(); final either = await _kr_subscribeApi.kr_getPublicPaymentMethods();
either.fold( either.fold(
(error) { (error) {
KRLogUtil.kr_e('获取公开支付方式失败: ${error.msg}', tag: 'PurchaseMembershipController'); KRLogUtil.kr_e('获取公开支付方式失败: ${error.msg}',
tag: 'PurchaseMembershipController');
}, },
(responseData) { (responseData) {
KRLogUtil.kr_i('✅ 获取公开支付方式成功', tag: 'PurchaseMembershipController'); KRLogUtil.kr_i('✅ 获取公开支付方式成功', tag: 'PurchaseMembershipController');
@@ -309,14 +323,14 @@ class KRPurchaseMembershipController extends GetxController {
final paymentMethodsData = KRPaymentMethods.fromJson(responseData); final paymentMethodsData = KRPaymentMethods.fromJson(responseData);
kr_paymentMethods.value = paymentMethodsData.list; kr_paymentMethods.value = paymentMethodsData.list;
KRLogUtil.kr_i('📊 支付方式数据已加载,共 ${kr_paymentMethods.length}', tag: 'PurchaseMembershipController'); KRLogUtil.kr_i('📊 支付方式数据已加载,共 ${kr_paymentMethods.length}',
tag: 'PurchaseMembershipController');
// //
for (var method in kr_paymentMethods) { for (var method in kr_paymentMethods) {
KRLogUtil.kr_i( KRLogUtil.kr_i(
'💳 支付方式: ${method.name} (ID: ${method.id}, Platform: ${method.platform})', '💳 支付方式: ${method.name} (ID: ${method.id}, Platform: ${method.platform})',
tag: 'PurchaseMembershipController' tag: 'PurchaseMembershipController');
);
} }
print('═══════════════════════════════════════'); print('═══════════════════════════════════════');
@@ -434,6 +448,42 @@ class KRPurchaseMembershipController extends GetxController {
print('开始订阅流程,选定支付'); print('开始订阅流程,选定支付');
kr_errorMessage.value = ''; kr_errorMessage.value = '';
// 1. iOS
if (Platform.isIOS) {
final getPendingJwsData = await IAPPendingOrderService.getPendingJwsData();
final getPendingOrderNo = await IAPPendingOrderService.getPendingOrderNo();
if (getPendingJwsData != null && getPendingJwsData.isNotEmpty!) {
print('🛑 [IAP] 拦截成功:发现未结单 $getPendingJwsData');
await HIDialog.show(
title: '检测到待激活订单',
message: '您有一笔订单已支付成功,但尚未同步会员权益。请点击“立即激活”完成同步。',
confirmText: '立即激活',
cancelText: '取消',
barrierDismissible: false,
onConfirm: () async{
// 🚀 Service
try{
await _kr_subscribeApi.kr_attachAppleIapTransaction(getPendingJwsData, getPendingOrderNo!);
Get.offNamed(
Routes.KR_ORDER_STATUS,
arguments: {
'order': getPendingOrderNo,
'payment_type': 'apple_iap',
'checkout_type': 'ipa',
},
);
}catch (e){
KRCommonUtil.kr_showToast('激活失败,请重试');
}
},
);
return; // 🛑 return
}
}
try { try {
await kr_processPurchaseAndCheckout(); await kr_processPurchaseAndCheckout();
} catch (e) { } catch (e) {
@@ -460,18 +510,54 @@ class KRPurchaseMembershipController extends GetxController {
/// ///
Future<void> kr_processPurchaseAndCheckout() async { Future<void> kr_processPurchaseAndCheckout() async {
final selectedPlan = kr_plans[kr_selectedPlanIndex.value]; final selectedPlan = kr_plans[kr_selectedPlanIndex.value];
// =========================================================================
// 🔽 iOS 使 Stripe 🔽
// =========================================================================
final isIOS = Platform.isIOS;
// 使 ID (1) 'alipay' // 1. 退
final paymentMethodId = kr_paymentMethods.isNotEmpty && kr_selectedPaymentMethodIndex.value >= 0 int finalPaymentMethodId = 1;
? kr_paymentMethods[kr_selectedPaymentMethodIndex.value].id String finalPaymentPlatform = 'alipay';
: 1;
final paymentPlatform = kr_paymentMethods.isNotEmpty && kr_selectedPaymentMethodIndex.value >= 0
? kr_paymentMethods[kr_selectedPaymentMethodIndex.value].platform
: 'alipay';
// if (kr_paymentMethods.isNotEmpty) {
//
final bool isIndexValid = kr_selectedPaymentMethodIndex.value >= 0 &&
kr_selectedPaymentMethodIndex.value < kr_paymentMethods.length;
if (isIOS) {
// 2. iOS 'stripe'
final stripeMethod = kr_paymentMethods.firstWhere(
(method) => method.platform == 'apple_iap',
);
if (stripeMethod != null) {
// Stripe使
finalPaymentMethodId = stripeMethod.id;
finalPaymentPlatform = stripeMethod.platform;
} else if (isIndexValid) {
// Stripe使
finalPaymentMethodId =
kr_paymentMethods[kr_selectedPaymentMethodIndex.value].id;
finalPaymentPlatform =
kr_paymentMethods[kr_selectedPaymentMethodIndex.value].platform;
}
} else {
// 3. iOS 使
if (isIndexValid) {
finalPaymentMethodId =
kr_paymentMethods[kr_selectedPaymentMethodIndex.value].id;
finalPaymentPlatform =
kr_paymentMethods[kr_selectedPaymentMethodIndex.value].platform;
}
}
}
final paymentMethodId = finalPaymentMethodId;
final paymentPlatform = finalPaymentPlatform;
// =========================================================================
// 🔼 🔼
// =========================================================================
final quantity = kr_getSelectedQuantity(); final quantity = kr_getSelectedQuantity();
// //
@@ -479,11 +565,12 @@ class KRPurchaseMembershipController extends GetxController {
.any((subscribe) => subscribe.subscribeId == selectedPlan.kr_id); .any((subscribe) => subscribe.subscribeId == selectedPlan.kr_id);
final subscribeId = isRenewal final subscribeId = isRenewal
? _kr_alreadySubscribe ? _kr_alreadySubscribe
.firstWhere( .firstWhere(
(subscribe) => subscribe.subscribeId == selectedPlan.kr_id, (subscribe) => subscribe.subscribeId == selectedPlan.kr_id,
orElse: () => KRAlreadySubscribe(userSubscribeId: 0, subscribeId: 0), // orElse: () =>
) KRAlreadySubscribe(userSubscribeId: 0, subscribeId: 0), //
.userSubscribeId )
.userSubscribeId
: 0; : 0;
// //
@@ -502,7 +589,7 @@ class KRPurchaseMembershipController extends GetxController {
); );
purchaseEither.fold( purchaseEither.fold(
(error) { (error) {
print('❌ 请求失败:'); print('❌ 请求失败:');
print(' 错误码: ${error.code}'); print(' 错误码: ${error.code}');
print(' 错误信息: ${error.msg}'); print(' 错误信息: ${error.msg}');
@@ -542,7 +629,8 @@ class KRPurchaseMembershipController extends GetxController {
// //
final discount = plan.kr_discount[discountIndex]; final discount = plan.kr_discount[discountIndex];
// discount 0100% // discount 0100%
final discountRate = discount.kr_discount == 0 ? 100.0 : discount.kr_discount.toDouble(); final discountRate =
discount.kr_discount == 0 ? 100.0 : discount.kr_discount.toDouble();
return (plan.kr_unitPrice / 100) * return (plan.kr_unitPrice / 100) *
discount.kr_quantity * discount.kr_quantity *
(discountRate / 100); (discountRate / 100);
@@ -557,8 +645,7 @@ class KRPurchaseMembershipController extends GetxController {
discountIndex < plan.kr_discount.length) { discountIndex < plan.kr_discount.length) {
// //
final discount = plan.kr_discount[discountIndex]; final discount = plan.kr_discount[discountIndex];
return (plan.kr_unitPrice / 100) * return (plan.kr_unitPrice / 100) * (discount.kr_discount / 100);
(discount.kr_discount / 100);
} }
return plan.kr_unitPrice / 100; return plan.kr_unitPrice / 100;
} }
@@ -621,7 +708,7 @@ class KRPurchaseMembershipController extends GetxController {
return ''; return '';
} }
// 97% -3% // 97% -3%
final discountPercent = 100 - discount.kr_discount; final discountPercent = (100 - discount.kr_discount).ceil();
return '${discountPercent}%'; return '${discountPercent}%';
} }
return ''; return '';
@@ -739,20 +826,21 @@ class KRPurchaseMembershipController extends GetxController {
final checkoutEither = await _kr_subscribeApi.kr_checkout(orderNo); final checkoutEither = await _kr_subscribeApi.kr_checkout(orderNo);
checkoutEither.fold( checkoutEither.fold(
(error) { (error) {
print('❌ Checkout 失败:'); print('❌ Checkout 失败:');
print(' 错误码: ${error.code}'); print(' 错误码: ${error.code}');
print(' 错误信息: ${error.msg}'); print(' 错误信息: ${error.msg}');
print('═══════════════════════════════════════'); print('═══════════════════════════════════════');
KRCommonUtil.kr_showToast(error.msg); KRCommonUtil.kr_showToast(error.msg);
}, },
(checkoutResponse) async { (checkoutResponse) async {
print('✅ Checkout 成功:'); print('✅ Checkout 成功:');
print(' 支付类型: ${checkoutResponse.type}'); print(' 支付类型: ${checkoutResponse.type}');
if (checkoutResponse.type == 'url') { if (checkoutResponse.type == 'url') {
// URL // URL
if (checkoutResponse.checkoutUrl != null && checkoutResponse.checkoutUrl!.isNotEmpty) { if (checkoutResponse.checkoutUrl != null &&
checkoutResponse.checkoutUrl!.isNotEmpty) {
print(' 支付链接: ${checkoutResponse.checkoutUrl}'); print(' 支付链接: ${checkoutResponse.checkoutUrl}');
print('🌐 正在用外部浏览器打开支付链接...'); print('🌐 正在用外部浏览器打开支付链接...');
print('═══════════════════════════════════════'); print('═══════════════════════════════════════');
@@ -802,10 +890,56 @@ class KRPurchaseMembershipController extends GetxController {
} else if (checkoutResponse.type == 'stripe') { } else if (checkoutResponse.type == 'stripe') {
// Stripe Stripe // Stripe Stripe
print(' Stripe Method: ${checkoutResponse.stripe?.method}'); print(' Stripe Method: ${checkoutResponse.stripe?.method}');
print(' Stripe Client Secret: ${checkoutResponse.stripe?.clientSecret}'); print(
' Stripe Client Secret: ${checkoutResponse.stripe?.clientSecret}');
print('💳 显示 Stripe 支付表单...'); print('💳 显示 Stripe 支付表单...');
print('═══════════════════════════════════════'); print('═══════════════════════════════════════');
final clientSecret = checkoutResponse.stripe?.clientSecret;
if (clientSecret == null || clientSecret.isEmpty) {
KRCommonUtil.kr_showToast('缺少支付凭证,请稍后重试');
return;
}
// iOS 使 Apple Pay
if (!Platform.isIOS) {
KRCommonUtil.kr_showToast('Apple Pay 仅在 iOS 可用');
return;
}
print(
'Platform: ${Platform.operatingSystem} ${Platform.operatingSystemVersion}');
print('Merchant identifier: ${Stripe.merchantIdentifier}');
// Platform PayApple Pay
final supported = await Stripe.instance.isPlatformPaySupported();
if (!supported) {
KRCommonUtil.kr_showToast('当前设备或 Wallet 不支持 Apple Pay,请确认已添加可用卡');
return;
}
// 2.
final params = PlatformPayConfirmParams.applePay(
applePay: ApplePayParams(
merchantCountryCode: 'US',
currencyCode: 'USD',
cartItems: [
ApplePayCartSummaryItem.immediate(
label: 'Hi快VPN 服务',
amount: kr_getPlanPrice(kr_plans[kr_selectedPlanIndex.value],
discountIndex: kr_selectedDiscountIndex.value)
.toStringAsFixed(2),
),
],
),
);
// 🚀 3. Apple Pay
await Stripe.instance.confirmPlatformPayPaymentIntent(
clientSecret: clientSecret,
confirmParams: params,
);
// C.
print('✅ Stripe 支付成功!');
Get.toNamed( Get.toNamed(
Routes.KR_ORDER_STATUS, Routes.KR_ORDER_STATUS,
arguments: { arguments: {
@@ -815,10 +949,11 @@ class KRPurchaseMembershipController extends GetxController {
'stripe': checkoutResponse.stripe, 'stripe': checkoutResponse.stripe,
}, },
); );
} else if (checkoutResponse.type == 'balance') { } else if (checkoutResponse.type == 'balance') {
// Stripe Stripe // Stripe Stripe
print(' Stripe Method: ${checkoutResponse.stripe?.method}'); print(' Stripe Method: ${checkoutResponse.stripe?.method}');
print(' Stripe Client Secret: ${checkoutResponse.stripe?.clientSecret}'); print(
' Stripe Client Secret: ${checkoutResponse.stripe?.clientSecret}');
print('💳 显示 Stripe 支付表单...'); print('💳 显示 Stripe 支付表单...');
print('═══════════════════════════════════════'); print('═══════════════════════════════════════');
@@ -831,13 +966,101 @@ class KRPurchaseMembershipController extends GetxController {
'checkout_type': 'balance', 'checkout_type': 'balance',
}, },
); );
} else if (checkoutResponse.type == 'apple_iap') {
if (!Platform.isIOS) {
KRCommonUtil.kr_showToast('仅限在 iOS 设备上购买');
return;
}
final KRIpaPayment iapParams = (checkoutResponse.ipa == null ||
(checkoutResponse.ipa?.productId.isEmpty ?? true))
? KRIpaPayment(
productId: 'com.hifastvpn.vip.day${kr_getSelectedQuantity()}')
: checkoutResponse.ipa!;
final iap = InAppPurchase.instance;
final available = await iap.isAvailable();
if (!available) {
print('IAP 不可用');
KRCommonUtil.kr_showToast('App Store 不可用');
return;
}
final productIds = {iapParams.productId};
final productDetailsResponse =
await iap.queryProductDetails(productIds);
if (productDetailsResponse.error != null) {
final e = productDetailsResponse.error!;
print('IAP 查询商品错误 code: ${e.code}');
print('IAP 查询商品错误 message: ${e.message}');
print('IAP 查询商品错误 iapParams.productId: ${iapParams.productId}');
KRCommonUtil.kr_showToast('无法获取商品信息: ${e.message}');
return;
}
if (productDetailsResponse.productDetails.isEmpty) {
print('IAP 商品列表为空 productId: ${iapParams.productId}');
KRCommonUtil.kr_showToast('未找到对应商品');
return;
}
final product = productDetailsResponse.productDetails.first;
final kr_userId = KRAppRunData.getInstance().kr_userId.value;
final purchaseParam = PurchaseParam(
productDetails: product,
applicationUserName: 'uid_${kr_userId}|orderNo_${orderNo}',
);
// ----------------------------------------------------------------------
// ProductDetails purchaseParam
// ----------------------------------------------------------------------
try {
await IAPPendingOrderService.setPendingOrderNo(orderNo);
KRCommonUtil.kr_showLoading();
final result =
await iap.buyNonConsumable(purchaseParam: purchaseParam);
if (!result) {
KRCommonUtil.kr_hideLoading();
KRCommonUtil.kr_showToast('购买请求失败,请重试');
}
} catch (e) {
KRCommonUtil.kr_hideLoading();
KRCommonUtil.kr_showToast('购买服务异常,请重试$e');
}
} else { } else {
print('⚠️ 未知的支付类型: ${checkoutResponse.type}'); print('⚠️ 未知的支付类型: ${checkoutResponse.type}');
print('═══════════════════════════════════════'); print('═══════════════════════════════════════');
// KRCommonUtil.kr_showToast('不支持的支付类型: ${checkoutResponse.type}'); // KRCommonUtil.kr_showToast('不支持的支付类型: ${checkoutResponse.type}');
} }
}, },
); );
} }
Future<void> kr_restorePurchases({bool isShowLoading = true}) async {
print('重试');
if(isShowLoading) {
KRCommonUtil.kr_showLoading();
}
final iap = InAppPurchase.instance;
await iap.restorePurchases();
if(isShowLoading) {
KRCommonUtil.kr_hideLoading();
}
}
// 1.
int _restoreClickCount = 0;
Future<void> clearPendingOrderNo() async {
_restoreClickCount++;
// 2. 3
if (_restoreClickCount > 3) {
print('恢复购买点击次数超过3次,清理待处理订单...');
await IAPPendingOrderService.clearPendingOrderNo();
_restoreClickCount = 0; //
KRCommonUtil.kr_hideLoading();
KRCommonUtil.kr_showToast('已清理缓存,请重新尝试购买');
return; //
}
}
} }
File diff suppressed because it is too large Load Diff
@@ -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模式专用
+10 -12
View File
@@ -27,13 +27,11 @@ class BaseResponse<T> {
final nonce = dataMap['time'] ?? ""; final nonce = dataMap['time'] ?? "";
// enable_security // enable_security
final shouldDecrypt = KRSiteConfigService().isDeviceSecurityEnabled(); if (cipherText.isNotEmpty && nonce.isNotEmpty) {
if (shouldDecrypt && cipherText.isNotEmpty && nonce.isNotEmpty) {
try { try {
if (kDebugMode) { if (kDebugMode) {
print('═══════════════════════════════════════'); // print('═══════════════════════════════════════');
print('🔐 检测到加密响应,开始解密...'); // print('🔐 检测到加密响应,开始解密...');
print('📥 加密数据长度: ${cipherText.length} 字符'); print('📥 加密数据长度: ${cipherText.length} 字符');
print('⏰ 时间戳: $nonce'); print('⏰ 时间戳: $nonce');
} }
@@ -42,13 +40,13 @@ class BaseResponse<T> {
body = jsonDecode(decrypted); body = jsonDecode(decrypted);
if (kDebugMode) { if (kDebugMode) {
print('✅ 解密成功'); // print('✅ 解密成功');
print(''); // print('');
print('📦 解密后的完整数据:'); // print('📦 解密后的完整数据:');
// JSON便 // // JSON便
final bodyStr = JsonEncoder.withIndent(' ').convert(body); // final bodyStr = JsonEncoder.withIndent(' ').convert(body);
print(bodyStr); // print(bodyStr);
print('═══════════════════════════════════════'); // print('═══════════════════════════════════════');
} }
KRLogUtil.kr_i('🔓 解密成功', tag: 'BaseResponse'); KRLogUtil.kr_i('🔓 解密成功', tag: 'BaseResponse');
+74 -21
View File
@@ -50,7 +50,7 @@ class HttpUtil {
void initDio() { void initDio() {
KRLogUtil.kr_i('🚀 HttpUtil.initDio() 开始初始化', tag: 'HttpUtil'); KRLogUtil.kr_i('🚀 HttpUtil.initDio() 开始初始化', tag: 'HttpUtil');
// 使 Loggy // 使 Loggy
_dio.interceptors.add(_KRSimpleHttpInterceptor()); _dio.interceptors.add(_KRSimpleHttpInterceptor(_dio));
_dio.options.baseUrl = AppConfig.getInstance().baseUrl; _dio.options.baseUrl = AppConfig.getInstance().baseUrl;
// //
@@ -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;
} }
} }
@@ -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,26 @@ 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)';
final _ua =
(err.requestOptions.extra['__unknown_attempts'] as int?) ?? 0;
if (_ua >= 2) {
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 +373,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 +387,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 +409,10 @@ class MyInterceptor extends Interceptor {
/// HTTP /// HTTP
class _KRSimpleHttpInterceptor extends Interceptor { class _KRSimpleHttpInterceptor extends Interceptor {
final Dio _dio;
_KRSimpleHttpInterceptor(this._dio);
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) {
@@ -414,9 +431,6 @@ class _KRSimpleHttpInterceptor extends Interceptor {
if (kDebugMode) { if (kDebugMode) {
print(''); print('');
} }
if (kDebugMode) {
print('🔐 检测到加密请求,正在解密...');
}
// //
final encryptedData = data['data'] as String; final encryptedData = data['data'] as String;
final nonce = data['time'] as String; final nonce = data['time'] as String;
@@ -454,7 +468,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) {
@@ -490,11 +505,11 @@ class _KRSimpleHttpInterceptor extends Interceptor {
} }
// JSON // JSON
try { try {
final jsonData = jsonDecode(decrypted); // final jsonData = jsonDecode(decrypted);
final prettyJson = JsonEncoder.withIndent(' ').convert(jsonData); // final prettyJson = JsonEncoder.withIndent(' ').convert(jsonData);
if (kDebugMode) { // if (kDebugMode) {
print(prettyJson); // print(prettyJson);
} // }
} catch (_) { } catch (_) {
if (kDebugMode) { if (kDebugMode) {
print(decrypted); print(decrypted);
@@ -514,7 +529,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 +545,43 @@ 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;
int unknownAttempts =
(err.requestOptions.extra['__unknown_attempts'] as int?) ?? 0;
if (unknownAttempts < 2) {
err.requestOptions.extra['__unknown_attempts'] = unknownAttempts + 1;
final delayMs = unknownAttempts == 0 ? 300 : 700;
Future.delayed(Duration(milliseconds: delayMs)).then((_) async {
final start = DateTime.now();
while (!KRSingBoxImp.instance.kr_isProxyReady &&
DateTime.now().difference(start) < const Duration(seconds: 1)) {
await Future.delayed(const Duration(milliseconds: 100));
}
try {
final Response<dynamic> r =
await _dio.fetch<dynamic>(err.requestOptions);
handler.resolve(r);
} catch (e) {
handler.next(e is DioException
? e
: DioException(requestOptions: err.requestOptions, error: e));
}
});
return;
} else {
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);
} }
} }
+43 -21
View File
@@ -43,6 +43,7 @@ import '../modules/kr_splash/views/kr_splash_view.dart';
import '../modules/kr_device_management/bindings/kr_device_management_binding.dart'; import '../modules/kr_device_management/bindings/kr_device_management_binding.dart';
import '../modules/kr_device_management/views/kr_device_management_view.dart'; import '../modules/kr_device_management/views/kr_device_management_view.dart';
import 'package:kaer_with_panels/app/routes/transitions/slide_transparent_transition.dart'; import 'package:kaer_with_panels/app/routes/transitions/slide_transparent_transition.dart';
import 'package:kaer_with_panels/app/widgets/swipe/swipe_wrapper.dart';
part 'app_routes.dart'; part 'app_routes.dart';
@@ -54,20 +55,23 @@ class AppPages {
static final routes = [ static final routes = [
GetPage( GetPage(
name: Routes.KR_SPLASH, name: Routes.KR_SPLASH,
page: () => const KRSplashView(), page: () => SwipeWrapper.detect(() => const KRSplashView()),
binding: KRSplashBinding(), binding: KRSplashBinding(),
popGesture: false,
transition: Transition.fade, transition: Transition.fade,
transitionDuration: const Duration(milliseconds: 500), transitionDuration: const Duration(milliseconds: 500),
), ),
GetPage( GetPage(
name: _Paths.KR_MAIN, name: _Paths.KR_MAIN,
page: () => const KRMainView(), page: () => SwipeWrapper.detect(() => const KRMainView()),
binding: KRMainBinding(), binding: KRMainBinding(),
popGesture: false,
), ),
GetPage( GetPage(
name: _Paths.KR_HOME, name: _Paths.KR_HOME,
page: () => KRHomeView(), page: () => SwipeWrapper.detect(() => KRHomeView()),
binding: KRHomeBinding(), binding: KRHomeBinding(),
popGesture: false,
arguments: {'showSubscriptionButton': true}, // arguments: {'showSubscriptionButton': true}, //
customTransition: SlideOutOnlyTransition( customTransition: SlideOutOnlyTransition(
direction: SlideDirection.leftToRight, direction: SlideDirection.leftToRight,
@@ -76,8 +80,9 @@ class AppPages {
), ),
GetPage( GetPage(
name: _Paths.HI_MENU, name: _Paths.HI_MENU,
page: () => const HIMenuView(), page: () => SwipeWrapper.detect(() => const HIMenuView()),
binding: HIMenuBinding(), binding: HIMenuBinding(),
popGesture: false,
arguments: {'showSubscriptionButton': true}, // arguments: {'showSubscriptionButton': true}, //
customTransition: ContextAwareSlideTransition( customTransition: ContextAwareSlideTransition(
targetRoute: _Paths.HI_MENU, targetRoute: _Paths.HI_MENU,
@@ -86,90 +91,107 @@ class AppPages {
), ),
GetPage( GetPage(
name: _Paths.MR_LOGIN, name: _Paths.MR_LOGIN,
page: () => const KRLoginView(), page: () => SwipeWrapper.detect(() => const KRLoginView()),
binding: MrLoginBinding(), binding: MrLoginBinding(),
popGesture: false,
arguments: {'showSubscriptionButton': true}, // arguments: {'showSubscriptionButton': true}, //
), ),
GetPage( GetPage(
name: _Paths.KR_SETTING, name: _Paths.KR_SETTING,
page: () => const KRSettingView(), page: () => SwipeWrapper.detect(() => const KRSettingView()),
binding: KRSettingBinding(), binding: KRSettingBinding(),
popGesture: false,
), ),
GetPage( GetPage(
name: _Paths.KR_USER_INFO, name: _Paths.KR_USER_INFO,
page: () => const KRUserInfoView(), page: () => SwipeWrapper.detect(() => const KRUserInfoView()),
binding: KRUserInfoBinding(), binding: KRUserInfoBinding(),
popGesture: false,
), ),
GetPage( GetPage(
name: _Paths.KR_INVITE, name: _Paths.KR_INVITE,
page: () => const KRInviteView(), page: () => SwipeWrapper.detect(() => const KRInviteView()),
binding: KRInviteBinding(), binding: KRInviteBinding(),
popGesture: false,
), ),
GetPage( GetPage(
name: _Paths.KR_STATISTICS, name: _Paths.KR_STATISTICS,
page: () => const KRStatisticsView(), page: () => SwipeWrapper.detect(() => const KRStatisticsView()),
binding: KRStatisticsBinding(), binding: KRStatisticsBinding(),
popGesture: false,
), ),
GetPage( GetPage(
name: _Paths.KR_LANGUAGE_SELECTOR, name: _Paths.KR_LANGUAGE_SELECTOR,
page: () => const KRLanguageSelectorView(), page: () => SwipeWrapper.detect(() => const KRLanguageSelectorView()),
binding: KRLanguageSelectorBinding(), binding: KRLanguageSelectorBinding(),
popGesture: false,
), ),
GetPage( GetPage(
name: _Paths.KR_COUNTRY_SELECTOR, name: _Paths.KR_COUNTRY_SELECTOR,
page: () => const KRCountrySelectorView(), page: () => SwipeWrapper.detect(() => const KRCountrySelectorView()),
binding: KRCountrySelectorBinding(), binding: KRCountrySelectorBinding(),
popGesture: false,
), ),
GetPage( GetPage(
name: _Paths.KR_PURCHASE_MEMBERSHIP, name: _Paths.KR_PURCHASE_MEMBERSHIP,
page: () => const KRPurchaseMembershipView(), page: () => SwipeWrapper.detect(() => const KRPurchaseMembershipView()),
binding: KRPurchaseMembershipBinding(), binding: KRPurchaseMembershipBinding(),
popGesture: false,
arguments: {'showSubscriptionButton': true}, // arguments: {'showSubscriptionButton': true}, //
), ),
GetPage( GetPage(
name: _Paths.KR_MESSAGE, name: _Paths.KR_MESSAGE,
page: () => const KRMessageView(), page: () => SwipeWrapper.detect(() => const KRMessageView()),
binding: KrMessageBinding(), binding: KrMessageBinding(),
popGesture: false,
), ),
GetPage( GetPage(
name: _Paths.KR_DELETE_ACCOUNT, name: _Paths.KR_DELETE_ACCOUNT,
page: () => const KRDeleteAccountView(), page: () => SwipeWrapper.detect(() => const KRDeleteAccountView()),
binding: KrDeleteAccountBinding(), binding: KrDeleteAccountBinding(),
popGesture: false,
), ),
GetPage( GetPage(
name: Routes.KR_WEBVIEW, name: Routes.KR_WEBVIEW,
page: () => const KRWebView(), page: () => SwipeWrapper.detect(() => const KRWebView()),
binding: KRWebViewBinding(), binding: KRWebViewBinding(),
popGesture: false,
), ),
GetPage( GetPage(
name: Routes.KR_ORDER_STATUS, name: Routes.KR_ORDER_STATUS,
page: () => const KROrderStatusView(), page: () => SwipeWrapper.detect(() => const KROrderStatusView()),
binding: KROrderStatusBinding(), binding: KROrderStatusBinding(),
popGesture: false,
), ),
GetPage( GetPage(
name: _Paths.KR_CRISP, name: _Paths.KR_CRISP,
page: () => const KRCrispView(), page: () => SwipeWrapper.detect(() => const KRCrispView()),
binding: KRCrispBinding(), binding: KRCrispBinding(),
popGesture: false,
), ),
GetPage( GetPage(
name: _Paths.KR_DEVICE_MANAGEMENT, name: _Paths.KR_DEVICE_MANAGEMENT,
page: () => const KRDeviceManagementView(), page: () => SwipeWrapper.detect(() => const KRDeviceManagementView()),
binding: KRDeviceManagementBinding(), binding: KRDeviceManagementBinding(),
popGesture: false,
), ),
GetPage( GetPage(
name: _Paths.HI_NODE_LIST, name: _Paths.HI_NODE_LIST,
page: () => const HINodePageView(), page: () => SwipeWrapper.detect(() => const HINodePageView()),
binding: HiNodeListBinding(), binding: HiNodeListBinding(),
popGesture: false,
), ),
GetPage( GetPage(
name: _Paths.HI_HELP, name: _Paths.HI_HELP,
page: () => const HIHelpView(), page: () => SwipeWrapper.detect(() => const HIHelpView()),
binding: HIHelpBinding(), binding: HIHelpBinding(),
popGesture: false,
), ),
GetPage( GetPage(
name: _Paths.HI_USER_INFO, name: _Paths.HI_USER_INFO,
page: () => const HIUserInfoView(), page: () => SwipeWrapper.detect(() => const HIUserInfoView()),
binding: HIUserInfoBinding(), binding: HIUserInfoBinding(),
popGesture: false,
arguments: {'showSubscriptionButton': true}, // arguments: {'showSubscriptionButton': true}, //
), ),
]; ];
@@ -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: 1000),
}); });
@override @override
@@ -4,7 +4,7 @@ import 'slide_transparent_transition.dart';
/// ///
class TransitionConfig { class TransitionConfig {
static Duration _defaultDuration = const Duration(milliseconds: 350); static Duration _defaultDuration = const Duration(milliseconds: 150);
static const SlideDirection _defaultDirection = SlideDirection.rightToLeft; static const SlideDirection _defaultDirection = SlideDirection.rightToLeft;
static const Curve _defaultCurve = Curves.easeOutCubic; static const Curve _defaultCurve = Curves.easeOutCubic;
+10 -7
View File
@@ -1,13 +1,15 @@
/// ///
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";
/// ///
// static const String kr_register = "/v1/auth/register"; // static const String kr_register = "/v1/auth/register";
static const String kr_register = "/v1/public/user/bind_email_with_verification"; static const String kr_register =
"/v1/public/user/bind_email_with_verification";
/// ///
static const String kr_checkVerificationCode = "/v1/auth/check-code"; static const String kr_checkVerificationCode = "/v1/auth/check-code";
@@ -56,12 +58,10 @@ abstract class Api {
static const String kr_getPackageList = "/v1/public/subscribe/list"; static const String kr_getPackageList = "/v1/public/subscribe/list";
/// ///
static const String kr_getAlreadySubscribe = static const String kr_getAlreadySubscribe = "/v1/public/user/subscribe";
"/v1/public/user/subscribe";
/// OmnOem /// OmnOem
static const String kr_userAvailableSubscribe = static const String kr_userAvailableSubscribe = "/v1/public/user/subscribe";
"/v1/public/user/subscribe";
/// ///
static const String kr_renewal = "/v1/public/order/renewal"; static const String kr_renewal = "/v1/public/order/renewal";
@@ -111,10 +111,13 @@ abstract class Api {
/// ///
static const String kr_getPublicPaymentMethods = "/v1/public/payment/methods"; static const String kr_getPublicPaymentMethods = "/v1/public/payment/methods";
static const String kr_attachAppleIapTransaction =
"/v1/public/iap/apple/transactions/attach";
static const String kr_restoreAppleIap = "/v1/public/iap/apple/restore";
/// ///
static const String kr_getUserInfo = "/v1/public/user/info"; static const String kr_getUserInfo = "/v1/public/user/info";
/// ///
static const String hi_invite_code = "/v1/public/user/bind_invite_code"; static const String hi_invite_code = "/v1/public/user/bind_invite_code";
} }
@@ -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));
@@ -145,7 +145,7 @@ class KRSubscribeApi {
data['order_no'] = orderNo; data['order_no'] = orderNo;
BaseResponse<KROrderStatus> baseResponse = BaseResponse<KROrderStatus> baseResponse =
await HttpUtil.getInstance().request<KROrderStatus>( await HttpUtil.getInstance().request<KROrderStatus>(
Api.kr_queryOrderStatus, Api.kr_queryOrderStatus,
data, data,
method: HttpMethod.GET, method: HttpMethod.GET,
@@ -271,7 +271,8 @@ class KRSubscribeApi {
} }
/// , /// ,
Future<Either<HttpError, KRCheckoutResponse>> kr_checkout(String orderId) async { Future<Either<HttpError, KRCheckoutResponse>> kr_checkout(
String orderId) async {
final Map<String, dynamic> data = <String, dynamic>{}; final Map<String, dynamic> data = <String, dynamic>{};
data['orderNo'] = orderId; data['orderNo'] = orderId;
data['returnUrl'] = AppConfig.getInstance().baseUrl; data['returnUrl'] = AppConfig.getInstance().baseUrl;
@@ -329,4 +330,46 @@ class KRSubscribeApi {
return right(baseResponse.model); return right(baseResponse.model);
} }
Future<Either<HttpError, bool>> kr_attachAppleIapTransaction(
String jwsData, String orderNo) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['signed_transaction_jws'] = jwsData;
data['order_no'] = orderNo;
BaseResponse<KRStatus> baseResponse =
await HttpUtil.getInstance().request<KRStatus>(
Api.kr_attachAppleIapTransaction,
data,
method: HttpMethod.POST,
isShowLoading: false,
);
if (!baseResponse.isSuccess) {
return left(
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
}
return right(baseResponse.model.kr_bl);
}
Future<Either<HttpError, bool>> kr_restoreAppleIapTransaction(
String transactionId, String orderNo) async {
final Map<String, dynamic> data = <String, dynamic>{};
data['transaction_id'] = [transactionId];
data['order_no'] = orderNo;
BaseResponse<KRStatus> baseResponse =
await HttpUtil.getInstance().request<KRStatus>(
Api.kr_restoreAppleIap,
data,
method: HttpMethod.POST,
isShowLoading: false,
);
if (!baseResponse.isSuccess) {
return left(
HttpError(msg: baseResponse.retMsg, code: baseResponse.retCode));
}
return right(baseResponse.model.kr_bl);
}
} }
+4 -10
View File
@@ -78,7 +78,7 @@ class GlobalOverlayService extends GetxService {
// 2 money-icon // 2 money-icon
Positioned( Positioned(
top: MediaQuery.of(context).padding.top + 8, top: MediaQuery.of(context).padding.top + 8,
right: (radius - (statusBarHeight / 2)) / 2, right: ((radius - (statusBarHeight / 2)) / 2) + 3,
child: GestureDetector( child: GestureDetector(
behavior: HitTestBehavior.translucent, // behavior: HitTestBehavior.translucent, //
onTap: () { onTap: () {
@@ -88,15 +88,9 @@ class GlobalOverlayService extends GetxService {
}, },
child: IgnorePointer( child: IgnorePointer(
ignoring: false, // ignoring: false, //
child: SizedBox( child: KrLocalImage(
width: 44, imageName: 'money-icon',
height: 44, imageType: ImageType.svg,
child: KrLocalImage(
imageName: 'money-icon',
width: 44,
height: 44,
imageType: ImageType.svg,
),
), ),
), ),
), ),
@@ -0,0 +1,29 @@
import 'package:flutter_keychain/flutter_keychain.dart';
class IAPPendingOrderService {
static const _keyOrderNo = 'hi_iap_pending_order_no';
static const _keyJwsData = 'hi_iap_pending_jws_data'; // Key
static Future<void> setPendingOrderNo(String orderNo) async {
await FlutterKeychain.put(key: _keyOrderNo, value: orderNo);
}
static Future<String?> getPendingOrderNo() async {
return await FlutterKeychain.get(key: _keyOrderNo);
}
/// JWS
static Future<void> setPendingJwsData(String jwsData) async {
await FlutterKeychain.put(key: _keyJwsData, value: jwsData);
}
/// JWS
static Future<String?> getPendingJwsData() async {
return await FlutterKeychain.get(key: _keyJwsData);
}
static Future<void> clearPendingOrderNo() async {
await FlutterKeychain.remove(key: _keyOrderNo);
await FlutterKeychain.remove(key: _keyJwsData);
}
}
+212
View File
@@ -0,0 +1,212 @@
import 'dart:async';
import 'dart:io';
import 'package:get/get.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:kaer_with_panels/app/services/iap/iap_pending_order_service.dart';
import 'package:kaer_with_panels/app/utils/kr_common_util.dart';
import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart';
import 'package:kaer_with_panels/app/services/api_service/kr_subscribe_api.dart';
import 'package:kaer_with_panels/app/routes/app_pages.dart';
class KRIAPService extends GetxService {
static KRIAPService get instance => Get.find<KRIAPService>();
final KRSubscribeApi _kr_subscribeApi = KRSubscribeApi();
final InAppPurchase _iap = InAppPurchase.instance;
StreamSubscription<List<PurchaseDetails>>? _subscription;
//
bool _hasStarted = false;
@override
void onInit() {
super.onInit();
// Home
}
/// Home
void setup() {
if (!Platform.isIOS) return;
if (_hasStarted) return;
_hasStarted = true;
_startGlobalListening();
}
void _startGlobalListening() {
print('💳 [IAPService] ========== 🚀 全局监听流已就绪 ==========');
//
_subscription?.cancel();
_subscription = _iap.purchaseStream.listen(
_handleIapUpdates,
onDone: () => _subscription?.cancel(),
onError: (error) {
print('💳 [IAPService] ❌ Stream Error: $error');
//
},
);
}
// 1.
void _handleIapUpdates(List<PurchaseDetails> purchases) async {
final pendingOrderNo = await IAPPendingOrderService.getPendingOrderNo();
print('🔄 [IAPService] 处理状态222: ${pendingOrderNo}');
// App
if (pendingOrderNo == null || pendingOrderNo.isEmpty) return;
for (final p in purchases) {
print('🔄 [IAPService] 处理状态: ${p.status} | ID: ${p.purchaseID}');
if (p.status == PurchaseStatus.pending) continue;
if (p.status == PurchaseStatus.error) {
if (p.pendingCompletePurchase) await _iap.completePurchase(p);
KRCommonUtil.kr_showToast('购买失败');
KRCommonUtil.kr_hideLoading();
continue;
}
if (p.status == PurchaseStatus.purchased || p.status == PurchaseStatus.restored) {
//
_verifyWithServer(p, pendingOrderNo);
}
}
}
// 2. UI
Future<void> _verifyWithServer(PurchaseDetails p, String orderNo) async {
final String jwsData = p.verificationData.serverVerificationData;
if (jwsData.isEmpty) {
print('❌ [IAPService] JWS 数据为空,跳过验证');
if (p.pendingCompletePurchase) {
await InAppPurchase.instance.completePurchase(p);
}
KRCommonUtil.kr_hideLoading();
return;
}
KRCommonUtil.kr_showLoading(message: '正在激活会员权益...');
bool ok = false;
try {
//
if (p.status == PurchaseStatus.purchased || p.status == PurchaseStatus.restored) {
await IAPPendingOrderService.setPendingJwsData(jwsData);
final either = await _kr_subscribeApi.kr_attachAppleIapTransaction(jwsData, orderNo);
either.fold((e) => print('❌ [IAPService] 支付激活失败: ${e.msg}'), (v) => ok = true);
}
// else {
// // restore JWS
// // final either = await _kr_subscribeApi.kr_restoreAppleIapTransaction(jwsData, orderNo);
// // either.fold((e) => print('❌ [IAPService] 恢复激活失败: ${e.msg}'), (v) => ok = true);
// }
} catch (e) {
print('🌐 [IAPService] 网络请求异常');
}
if (ok) {
await _finalizeTransaction(p, orderNo);
} else {
_handleVerificationFailure(p, orderNo);
}
}
String? findCurrentRoute() {
String? routeName;
try {
// Navigator context
Get.key.currentState?.popUntil((route) {
routeName = route.settings.name;
//
print('层级检查: $routeName');
// null
if (routeName != null && routeName != '/') return true;
return false;
});
} catch (e) {
print('路由查找异常: $e');
}
return routeName;
}
// 3.
Future<void> _finalizeTransaction(PurchaseDetails p, String orderNo) async {
print('✅ [IAPService] 验证成功,清理流程${p.pendingCompletePurchase}');
// I.
if (p.pendingCompletePurchase) {
await _iap.completePurchase(p);
}
await _iap.completePurchase(p);
// II.
await IAPPendingOrderService.clearPendingOrderNo();
KRCommonUtil.kr_hideLoading();
String? realRoute = findCurrentRoute();
print('🔍 [IAP Fix] Final Found Route: "$realRoute"');
// if (Get.currentRoute.contains(Routes.KR_ORDER_STATUS)) return;
//
// // 2.
// //
// if (Get.currentRoute.contains(Routes.KR_PURCHASE_MEMBERSHIP)) {
// _navigateToStatusPage(orderNo);
// } else {
// // 3. Home
// //
// HIDialog.show(
// title: '订单处理成功',
// message: '检测到您有一笔未完成的订单已激活成功,会员权益已到账。',
// confirmText: '查看订单',
// cancelText: '我知道了',
// onConfirm: () => _navigateToStatusPage(orderNo),
// );
// }
//
_navigateToStatusPage(orderNo);
}
void _navigateToStatusPage(String orderNo) {
Get.offNamed(
Routes.KR_ORDER_STATUS,
arguments: {
'order': orderNo,
'payment_type': 'apple_iap',
'checkout_type': 'ipa',
},
);
}
// 4.
void _handleVerificationFailure(PurchaseDetails p, String orderNo) {
KRCommonUtil.kr_hideLoading();
HIDialog.show(
title: '激活失败',
message: '您的支付已成功,但由于网络原因未能开启权益。',
confirmText: '重试激活',
cancelText: '关闭',
barrierDismissible: false,
preventBackDismiss: true,
onConfirm: () {
// 💡
_verifyWithServer(p, orderNo);
},
);
}
// 5. UI
Future<void> retryUnfinishedTransactions() async {
try {
await _iap.restorePurchases();
} catch (e) {
print('e');
}
}
@override
void onClose() {
_subscription?.cancel();
super.onClose();
}
}
@@ -97,6 +97,9 @@ class KRSiteConfigService extends ChangeNotifier {
_siteConfig = KRSiteConfig.fromJson(responseData['data']); _siteConfig = KRSiteConfig.fromJson(responseData['data']);
_isInitialized = true; _isInitialized = true;
final config = AppConfig.getInstance();
config.kr_website_id = getCrispId();
config.device_limit = getDeviceLimit();
// //
_printConfigInfo(); _printConfigInfo();
@@ -341,6 +344,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;
+5 -3
View File
@@ -250,15 +250,15 @@ class KRSubscribeService {
} }
kr_currentSubscribe.value = updatedSubscribe; kr_currentSubscribe.value = updatedSubscribe;
KRLogUtil.kr_i('更新当前订阅信息', tag: 'SubscribeService'); // KRLogUtil.kr_i('更新当前订阅信息', tag: 'SubscribeService');
// //
kr_availableSubscribes.assignAll(subscribes); kr_availableSubscribes.assignAll(subscribes);
} }
} }
KRLogUtil.kr_i('获取可用订阅列表成功: ${subscribes.length} 个订阅', // KRLogUtil.kr_i('获取可用订阅列表成功: ${subscribes.length} 个订阅',
tag: 'SubscribeService'); // tag: 'SubscribeService');
KRLogUtil.kr_i( KRLogUtil.kr_i(
'订阅列表: ${subscribes.map((s) => '${s.name}(${s.id})').join(', ')}', '订阅列表: ${subscribes.map((s) => '${s.name}(${s.id})').join(', ')}',
tag: 'SubscribeService'); tag: 'SubscribeService');
@@ -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
+37
View File
@@ -0,0 +1,37 @@
import 'package:get/get.dart';
import 'package:kaer_with_panels/app/common/app_run_data.dart';
import 'package:kaer_with_panels/app/common/app_config.dart';
import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart';
import 'package:kaer_with_panels/app/routes/app_pages.dart';
import 'package:kaer_with_panels/app/utils/kr_common_util.dart';
import 'package:kaer_with_panels/app/services/kr_site_config_service.dart';
Future<bool> ensureAccountExists() async {
final app = KRAppRunData.getInstance();
final account = app.kr_account.value;
final isLoggedIn = app.kr_isLogin.value;
final config = AppConfig.getInstance();
if (account == null ||
account.isEmpty ||
!isLoggedIn
) {
await HIDialog.show(
message: '未检测到账号信息,请重试初始化',
confirmText: '重试',
preventBackDismiss: true,
onConfirm: () {
Get.offAllNamed(Routes.KR_SPLASH);
},
);
return false;
}
var crispId = config.kr_website_id;
var deviceLimitText = config.device_limit;
var deviceLimit = int.tryParse(deviceLimitText) ?? 0;
if ((crispId.isEmpty || crispId == '0') || deviceLimit == 0) {
KRSiteConfigService().initialize();
}
return true;
}
+1 -1
View File
@@ -5,7 +5,7 @@ class KRCommonUtil {
/// meesage: , toastPosition: , timeout: /// meesage: , toastPosition: , timeout:
static kr_showToast(String message, static kr_showToast(String message,
{KRToastPosition toastPosition = KRToastPosition.center, {KRToastPosition toastPosition = KRToastPosition.center,
int timeout = 1500}) { int timeout = 2500}) {
KRToast.kr_showToast( KRToast.kr_showToast(
message, message,
position: toastPosition, position: toastPosition,
+11 -8
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) {
@@ -106,7 +107,7 @@ 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();
} }
} }
+101 -77
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 {
try { Socket? socket;
final stopwatch = Stopwatch()..start(); final stopwatch = Stopwatch();
final socket = await Socket.connect( try {
KRLogUtil.kr_i('🔌 开始测试: $host:$port', tag: 'LatencyTester');
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;
final port = node.value.port;
final key = node.key;
final latency = await testNode( int completedCount = 0;
host: host,
port: port, //
for (int batchIndex = 0; batchIndex < batches.length; batchIndex++) {
final batch = batches[batchIndex];
KRLogUtil.kr_i('📦 测试第 ${batchIndex + 1}/${batches.length} 批(${batch.length} 个节点)', tag: 'LatencyTester');
//
final futures = batch.map((node) async {
final tag = node.key;
final address = node.value;
final latency = await testTcpLatency(
host: address.host,
port: address.port,
timeout: timeout, 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 >= nodes.length) { if (completedCount % 5 == 0 || completedCount == nodes.length) {
semaphore.complete(); KRLogUtil.kr_i('📈 测试进度: $completedCount/${nodes.length}', tag: 'LatencyTester');
} }
}
}
// 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;
} }
} }
// //
await semaphore.future; 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');
}
}
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';
} }
+7
View File
@@ -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; //
} }
+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');
}
}
+11 -5
View File
@@ -4,6 +4,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.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/routes/app_pages.dart'; import 'package:kaer_with_panels/app/routes/app_pages.dart';
import 'package:kaer_with_panels/app/utils/account_guard.dart';
/// 🔹 HIBaseScaffold /// 🔹 HIBaseScaffold
/// ///
@@ -42,6 +43,8 @@ class HIBaseScaffold extends StatelessWidget {
/// ///
final bool showBackgroundImage; final bool showBackgroundImage;
final Color? backgroundColor;
/// resizeToAvoidBottomInset /// resizeToAvoidBottomInset
final bool? resizeToAvoidBottomInset; final bool? resizeToAvoidBottomInset;
@@ -59,6 +62,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 +75,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,
@@ -109,7 +113,9 @@ class HIBaseScaffold extends StatelessWidget {
// //
if (showMenuButton) { if (showMenuButton) {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () async {
final ok = await ensureAccountExists();
if (!ok) return;
Get.toNamed(Routes.HI_MENU); Get.toNamed(Routes.HI_MENU);
}, },
child: Container( child: Container(
@@ -152,7 +158,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 +170,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 +181,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,
), ),
), ),
+6 -6
View File
@@ -86,7 +86,7 @@ class _HICollapsibleItemWidgetState extends State<HICollapsibleItemWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final BorderRadius borderRadius = BorderRadius.circular(40.w); final BorderRadius borderRadius = BorderRadius.circular(25);
return AnimatedContainer( return AnimatedContainer(
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
@@ -109,7 +109,7 @@ class _HICollapsibleItemWidgetState extends State<HICollapsibleItemWidget> {
}, },
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
child: Padding( child: Padding(
padding: EdgeInsets.fromLTRB(24.w, 16.w, 24.w, 16.w), padding: EdgeInsets.fromLTRB(24, 16, 24, 16),
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
@@ -117,7 +117,7 @@ class _HICollapsibleItemWidgetState extends State<HICollapsibleItemWidget> {
widget.item.title, widget.item.title,
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 12.sp, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
maxLines: 1, maxLines: 1,
@@ -144,7 +144,7 @@ class _HICollapsibleItemWidgetState extends State<HICollapsibleItemWidget> {
curve: Curves.easeInOut, curve: Curves.easeInOut,
child: _isExpanded child: _isExpanded
? Container( ? Container(
padding: EdgeInsets.fromLTRB(24.w, 0, 24.w, 16.w), padding: EdgeInsets.fromLTRB(24, 0, 24, 16),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: widget.item.content.map((itemText) { children: widget.item.content.map((itemText) {
@@ -154,7 +154,7 @@ class _HICollapsibleItemWidgetState extends State<HICollapsibleItemWidget> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Padding( Padding(
padding: EdgeInsets.only(top: 11.w, right: 8.w), padding: EdgeInsets.only(top: 11, right: 8),
child: Container( child: Container(
width: 5.w, width: 5.w,
height: 5.w, height: 5.w,
@@ -169,7 +169,7 @@ class _HICollapsibleItemWidgetState extends State<HICollapsibleItemWidget> {
TextSpan( TextSpan(
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 13.sp, fontSize: 13,
height: 1.8, height: 1.8,
fontWeight: FontWeight.w300, fontWeight: FontWeight.w300,
), ),
+155
View File
@@ -0,0 +1,155 @@
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'swipe/swipe_controller.dart';
import 'swipe/swipe_config.dart';
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
class HIEdgeSwipeDetector extends StatefulWidget {
final Widget child;
final double reservedEdgeWidth;
final double detectionEdgeWidth;
final double minSwipeDistance;
final double minSwipeVelocity;
final SwipeConfig? config;
const HIEdgeSwipeDetector(
{super.key,
required this.child,
this.reservedEdgeWidth = 0,
this.detectionEdgeWidth = 20,
this.minSwipeDistance = 20,
this.minSwipeVelocity = 300,
this.config});
@override
State<HIEdgeSwipeDetector> createState() => _HIEdgeSwipeDetectorState();
}
class _HIEdgeSwipeDetectorState extends State<HIEdgeSwipeDetector> {
Offset? _start;
Offset? _last;
DateTime? _startTime;
final SwipeController _controller =
Get.put(SwipeController(), permanent: true);
bool _fromLeft = false;
bool _fromRight = false;
@override
Widget build(BuildContext context) {
return Stack(
fit: StackFit.expand,
children: [
widget.child,
Positioned(
left: 0,
top: 0,
bottom: 0,
width: widget.detectionEdgeWidth,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onHorizontalDragStart: (details) {
_fromLeft = true;
_fromRight = false;
_start = details.globalPosition;
_startTime = DateTime.now();
KRLogUtil.kr_d('dragStart LEFT x=${details.globalPosition.dx}',
tag: 'HIEdgeSwipeDetector');
},
onHorizontalDragUpdate: (details) {
_last = details.globalPosition;
KRLogUtil.kr_d('dragUpdate LEFT dx=${details.delta.dx}',
tag: 'HIEdgeSwipeDetector');
},
onHorizontalDragEnd: (details) {
if (_start == null || _startTime == null) return;
final durationMs =
DateTime.now().difference(_startTime!).inMilliseconds;
final velocity = details.velocity.pixelsPerSecond.dx.abs();
final distance = _last != null ? (_last!.dx - _start!.dx) : 0.0;
final okByVelocity = velocity >= widget.minSwipeVelocity;
final okByDistance = distance.abs() >= widget.minSwipeDistance;
KRLogUtil.kr_d(
'dragEnd LEFT vel=${details.velocity.pixelsPerSecond.dx} dur=$durationMs dist=$distance okV=$okByVelocity okD=$okByDistance',
tag: 'HIEdgeSwipeDetector');
if (!okByVelocity && !okByDistance) {
_reset();
return;
}
if (distance > 0) {
final cfg = widget.config;
if (cfg == null || !cfg.enableLeft) {
_reset();
return;
}
if (cfg.onLeft != null) {
cfg.onLeft!();
} else {
Get.back();
}
}
_reset();
},
),
),
Positioned(
right: 0,
top: 0,
bottom: 0,
width: widget.detectionEdgeWidth,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onHorizontalDragStart: (details) {
_fromLeft = false;
_fromRight = true;
_start = details.globalPosition;
_startTime = DateTime.now();
KRLogUtil.kr_d('dragStart RIGHT x=${details.globalPosition.dx}',
tag: 'HIEdgeSwipeDetector');
},
onHorizontalDragUpdate: (details) {
_last = details.globalPosition;
KRLogUtil.kr_d('dragUpdate RIGHT dx=${details.delta.dx}',
tag: 'HIEdgeSwipeDetector');
},
onHorizontalDragEnd: (details) {
if (_start == null || _startTime == null) return;
final durationMs =
DateTime.now().difference(_startTime!).inMilliseconds;
final velocity = details.velocity.pixelsPerSecond.dx.abs();
final distance = _last != null ? (_last!.dx - _start!.dx) : 0.0;
final okByVelocity = velocity >= widget.minSwipeVelocity;
final okByDistance = distance.abs() >= widget.minSwipeDistance;
KRLogUtil.kr_d(
'dragEnd RIGHT vel=${details.velocity.pixelsPerSecond.dx} dur=$durationMs dist=$distance okV=$okByVelocity okD=$okByDistance',
tag: 'HIEdgeSwipeDetector');
if (!okByVelocity && !okByDistance) {
_reset();
return;
}
if (distance < 0) {
final cfg = widget.config;
if (cfg == null || !cfg.enableRight) {
_reset();
return;
}
if (cfg.onRight != null) {
cfg.onRight!();
} else {
_controller.openRightMenu();
}
}
_reset();
},
),
),
],
);
}
void _reset() {
_start = null;
_last = null;
_startTime = null;
_fromLeft = false;
_fromRight = false;
}
}
+148 -45
View File
@@ -1,4 +1,7 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
class HiFixedScrollbar extends StatefulWidget { class HiFixedScrollbar extends StatefulWidget {
@@ -6,17 +9,13 @@ class HiFixedScrollbar extends StatefulWidget {
final ScrollController controller; final ScrollController controller;
final bool isShowScrollbar; final bool isShowScrollbar;
/// 18
final double right; final double right;
///
final double thickness; final double thickness;
/// 30%
final Color thumbColor; final Color thumbColor;
/// 15%
final Color trackColor; final Color trackColor;
/// 50
final double thumbHeight; final double thumbHeight;
final Duration fadeDuration;
final Duration fadeDelay;
const HiFixedScrollbar({ const HiFixedScrollbar({
super.key, super.key,
@@ -28,23 +27,81 @@ class HiFixedScrollbar extends StatefulWidget {
this.thumbHeight = 50, this.thumbHeight = 50,
this.thumbColor = const Color.fromRGBO(255, 255, 255, 0.3), this.thumbColor = const Color.fromRGBO(255, 255, 255, 0.3),
this.trackColor = const Color.fromRGBO(255, 255, 255, 0.15), this.trackColor = const Color.fromRGBO(255, 255, 255, 0.15),
this.fadeDuration = const Duration(milliseconds: 300),
this.fadeDelay = const Duration(milliseconds: 500),
}); });
@override @override
State<HiFixedScrollbar> createState() => _HiFixedScrollbarState(); State<HiFixedScrollbar> createState() => _HiFixedScrollbarState();
} }
class _HiFixedScrollbarState extends State<HiFixedScrollbar> { class _HiFixedScrollbarState extends State<HiFixedScrollbar>
with SingleTickerProviderStateMixin {
double _thumbOffset = 0.0; double _thumbOffset = 0.0;
late AnimationController _fadeController;
Timer? _fadeTimer;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_fadeController = AnimationController(
vsync: this,
duration: widget.fadeDuration,
);
// 1. ( _thumbOffset)
widget.controller.addListener(_updateThumbPosition); widget.controller.addListener(_updateThumbPosition);
SchedulerBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
if (widget.controller.hasClients) {
_updateThumbPosition();
}
});
}
@override
void didUpdateWidget(covariant HiFixedScrollbar oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.controller != oldWidget.controller) {
//
oldWidget.controller.removeListener(_updateThumbPosition);
//
widget.controller.addListener(_updateThumbPosition);
_fadeTimer?.cancel();
SchedulerBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
if (widget.controller.hasClients) {
_updateThumbPosition();
}
});
}
}
bool _onScrollNotification(ScrollNotification notification) {
if (!mounted) return false;
if (_fadeTimer != null && _fadeTimer!.isActive) {
_fadeTimer?.cancel();
}
if (notification is ScrollStartNotification ||
notification is ScrollUpdateNotification) {
_fadeController.forward();
_updateThumbPosition();
} else if (notification is ScrollEndNotification ||
(notification is UserScrollNotification &&
notification.direction == ScrollDirection.idle)) {
_fadeTimer = Timer(widget.fadeDelay, () {
if (mounted) {
_fadeController.reverse();
} else {
_fadeTimer?.cancel();
}
});
}
return false;
} }
void _updateThumbPosition() { void _updateThumbPosition() {
if (!mounted) return; if (!mounted || !widget.controller.hasClients) return;
final position = widget.controller.position; final position = widget.controller.position;
if (!position.hasPixels || !position.hasContentDimensions) return; if (!position.hasPixels || !position.hasContentDimensions) return;
@@ -52,18 +109,43 @@ class _HiFixedScrollbarState extends State<HiFixedScrollbar> {
final offset = position.pixels; final offset = position.pixels;
final viewport = position.viewportDimension; final viewport = position.viewportDimension;
// if (maxScrollExtent <= 0) {
final trackHeight = viewport - widget.thumbHeight; if (_thumbOffset != 0.0) {
final scrollRatio = maxScrollExtent == 0 ? 0 : offset / maxScrollExtent; setState(() => _thumbOffset = 0.0);
}
if (_fadeController.status != AnimationStatus.dismissed) {
_fadeController.reverse();
}
return;
}
setState(() { final trackHeight = viewport - widget.thumbHeight.h;
_thumbOffset = (trackHeight * scrollRatio).clamp(0, trackHeight); final scrollRatio = offset / maxScrollExtent;
}); final newOffset = (trackHeight * scrollRatio).clamp(0.0, trackHeight);
if (_thumbOffset != newOffset) {
setState(() {
_thumbOffset = newOffset;
});
if (_fadeTimer != null && _fadeTimer!.isActive) {
_fadeTimer?.cancel();
}
_fadeController.forward();
_fadeTimer = Timer(widget.fadeDelay, () {
if (mounted) {
_fadeController.reverse();
} else {
_fadeTimer?.cancel();
}
});
}
} }
@override @override
void dispose() { void dispose() {
widget.controller.removeListener(_updateThumbPosition); widget.controller.removeListener(_updateThumbPosition);
_fadeController.dispose();
_fadeTimer?.cancel();
super.dispose(); super.dispose();
} }
@@ -71,41 +153,62 @@ class _HiFixedScrollbarState extends State<HiFixedScrollbar> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return LayoutBuilder( return LayoutBuilder(
builder: (_, constraints) { builder: (_, constraints) {
final position =
widget.controller.hasClients ? widget.controller.position : null;
if (position == null || !position.hasContentDimensions) {
return widget.child;
}
final maxScrollExtent = position.maxScrollExtent;
final canShowScrollbar = widget.isShowScrollbar && maxScrollExtent > 0;
return Stack( return Stack(
children: [ children: [
// NotificationListener<ScrollNotification>(
widget.child, onNotification: _onScrollNotification,
child: widget.child,
),
if (canShowScrollbar)
AnimatedBuilder(
animation: _fadeController,
builder: (context, child) {
return Opacity(
opacity: _fadeController.value,
child: Stack(
children: [
// (Track)
Positioned(
right: widget.right.w.toDouble(),
top: 0,
bottom: 0,
child: Container(
width: widget.thickness.w.toDouble(),
decoration: BoxDecoration(
color: widget.trackColor,
borderRadius: BorderRadius.circular(4),
),
),
),
if(widget.isShowScrollbar) // (Thumb)
...[ Positioned(
// right: widget.right.w.toDouble(),
Positioned( top: _thumbOffset,
right: widget.right.w, child: Container(
top: 0, width: widget.thickness.w.toDouble(),
bottom: 0, height: widget.thumbHeight.h.toDouble(),
child: Container( decoration: BoxDecoration(
width: widget.thickness.w, color: widget.thumbColor,
decoration: BoxDecoration( borderRadius: BorderRadius.circular(4),
color: widget.trackColor, ),
borderRadius: BorderRadius.circular(4), ),
),
],
), ),
), );
), },
),
//
Positioned(
right: widget.right.w,
top: _thumbOffset,
child: Container(
width: widget.thickness.w,
height: widget.thumbHeight,
decoration: BoxDecoration(
color: widget.thumbColor,
borderRadius: BorderRadius.circular(4),
),
),
),
]
], ],
); );
}, },
+15 -15
View File
@@ -39,23 +39,23 @@ class KRCountryFlag extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// 🔍 // 🔍
if (kDebugMode) { // if (kDebugMode) {
print('🏳️ KRCountryFlag.build 被调用'); // print('🏳️ KRCountryFlag.build 被调用');
} // }
if (kDebugMode) { // if (kDebugMode) {
print(' - 原始 countryCode: "$countryCode"'); // print(' - 原始 countryCode: "$countryCode"');
} // }
if (kDebugMode) { // if (kDebugMode) {
print(' - countryCode.isEmpty: ${countryCode.isEmpty}'); // print(' - countryCode.isEmpty: ${countryCode.isEmpty}');
} // }
if (kDebugMode) { // if (kDebugMode) {
print(' - countryCode.length: ${countryCode.length}'); // print(' - countryCode.length: ${countryCode.length}');
} // }
final processedCode = _getCountryCode(countryCode); final processedCode = _getCountryCode(countryCode);
if (kDebugMode) { // if (kDebugMode) {
print(' - 处理后 code: "$processedCode"'); // print(' - 处理后 code: "$processedCode"');
} // }
// //
if (countryCode.isEmpty) { if (countryCode.isEmpty) {
@@ -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,
);
});
}
}
@@ -0,0 +1,6 @@
import 'swipe_config.dart';
abstract class HasSwipeConfig {
SwipeConfig get swipeConfig;
}
+10
View File
@@ -0,0 +1,10 @@
import 'package:flutter/widgets.dart';
class SwipeConfig {
final bool enableLeft;
final bool enableRight;
final VoidCallback? onLeft;
final VoidCallback? onRight;
const SwipeConfig({this.enableLeft = false, this.enableRight = false, this.onLeft, this.onRight});
}
@@ -0,0 +1,61 @@
import 'package:get/get.dart';
import 'package:flutter/foundation.dart';
import 'package:get/get_navigation/src/extension_navigation.dart';
import 'package:kaer_with_panels/app/routes/app_pages.dart';
import 'swipe_config.dart';
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
class SwipeController extends GetxController {
final Rxn<SwipeConfig> currentConfig = Rxn<SwipeConfig>();
final RxInt currentPageIndex = 0.obs;
final RxBool leftMenuOpen = false.obs;
final RxBool rightMenuOpen = false.obs;
void setConfig(SwipeConfig? config) {
currentConfig.value = config;
}
void setPageIndex(int index) {
currentPageIndex.value = index;
}
void closeMenus() {
leftMenuOpen.value = false;
rightMenuOpen.value = false;
}
void openLeftMenu() {
KRLogUtil.kr_d('openLeftMenu', tag: 'SwipeController');
leftMenuOpen.value = true;
Get.toNamed(Routes.HI_MENU);
}
void openRightMenu() {
KRLogUtil.kr_d('openRightMenu', tag: 'SwipeController');
rightMenuOpen.value = true;
Get.toNamed(Routes.KR_SETTING);
}
void triggerLeft() {
final cfg = currentConfig.value;
if (cfg == null || !cfg.enableLeft) return;
KRLogUtil.kr_d('call11 onLeft$cfg', tag: 'SwipeController');
if (cfg.onLeft != null) {
KRLogUtil.kr_d('call onLeft', tag: 'SwipeController');
cfg.onLeft!.call();
return;
}
Get.back();
}
void triggerRight() {
final cfg = currentConfig.value;
if (cfg == null || !cfg.enableRight) return;
if (cfg.onRight != null) {
KRLogUtil.kr_d('call onRight', tag: 'SwipeController');
cfg.onRight!.call();
return;
}
openRightMenu();
}
}
+45
View File
@@ -0,0 +1,45 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../hi_edge_swipe_detector.dart';
import 'swipe_config.dart';
import 'has_swipe_config.dart';
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
class SwipeWrapper extends StatelessWidget {
final Widget child;
final SwipeConfig config;
const SwipeWrapper({super.key, required this.child, required this.config});
static Widget detect(Widget Function() builder) {
final w = builder();
SwipeConfig effective = const SwipeConfig();
if (w is HasSwipeConfig) {
effective = (w as HasSwipeConfig).swipeConfig;
} else if (GetPlatform.isIOS) {
effective = SwipeConfig(enableLeft: true, onLeft: () => Get.back());
}
final enabled = effective.enableLeft ||
effective.enableRight ||
effective.onLeft != null ||
effective.onRight != null;
KRLogUtil.kr_d(
'SwipeWrapper.detect enabled=$enabled left=${effective.enableLeft} right=${effective.enableRight}',
tag: 'SwipeWrapper');
if (!enabled) return w;
return HIEdgeSwipeDetector(child: w, config: effective);
}
@override
Widget build(BuildContext context) {
final effective = config;
final enabled = effective.enableLeft ||
effective.enableRight ||
effective.onLeft != null ||
effective.onRight != null;
KRLogUtil.kr_d(
'SwipeWrapper.build enabled=$enabled left=${effective.enableLeft} right=${effective.enableRight}',
tag: 'SwipeWrapper');
if (!enabled) return child;
return HIEdgeSwipeDetector(child: child, config: effective);
}
}
+15
View File
@@ -3,10 +3,12 @@ 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';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter_stripe/flutter_stripe.dart';
import 'package:kaer_with_panels/app/themes/kr_theme_service.dart'; import 'package:kaer_with_panels/app/themes/kr_theme_service.dart';
import 'package:kaer_with_panels/app/localization/getx_translations.dart'; import 'package:kaer_with_panels/app/localization/getx_translations.dart';
@@ -37,12 +39,24 @@ 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();
Stripe.publishableKey = 'pk_live_51SbuYxAawOMH8rEEkz6f4mnxAUjGC72eQ6qdm5tT6whC4hULkxxdbiPsB4gSCIMnNIGCsIgeASTXBakUcbOuUwQO00jSWjuufx';
Stripe.merchantIdentifier = 'merchant.com.taw.hifastvpn';
if (Platform.isIOS) {
await Stripe.instance.applySettings();
}
// Hive // Hive
await KRSecureStorage().kr_initHive(); await KRSecureStorage().kr_initHive();
// //
await KRThemeService().init(); await KRThemeService().init();
@@ -175,6 +189,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);
+5
View File
@@ -0,0 +1,5 @@
create-dmg "HiFastVPN.app" \
--overwrite \
--dmg-title="HiFastVPN" \
--app-drop-link \
--dmg "HiFastVPN.dmg"
@@ -12,6 +12,7 @@ import flutter_udid
import package_info_plus import package_info_plus
import path_provider_foundation import path_provider_foundation
import screen_retriever_macos import screen_retriever_macos
import share_plus
import tray_manager import tray_manager
import url_launcher_macos import url_launcher_macos
import webview_flutter_wkwebview import webview_flutter_wkwebview
@@ -25,6 +26,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
TrayManagerPlugin.register(with: registry.registrar(forPlugin: "TrayManagerPlugin")) TrayManagerPlugin.register(with: registry.registrar(forPlugin: "TrayManagerPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin")) WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin"))
+13
View File
@@ -11,6 +11,9 @@ PODS:
- FlutterMacOS - FlutterMacOS
- SAMKeychain - SAMKeychain
- FlutterMacOS (1.0.0) - FlutterMacOS (1.0.0)
- in_app_purchase_storekit (0.0.1):
- Flutter
- FlutterMacOS
- OrderedSet (6.0.3) - OrderedSet (6.0.3)
- package_info_plus (0.0.1): - package_info_plus (0.0.1):
- FlutterMacOS - FlutterMacOS
@@ -21,6 +24,8 @@ PODS:
- SAMKeychain (1.5.3) - SAMKeychain (1.5.3)
- screen_retriever_macos (0.0.1): - screen_retriever_macos (0.0.1):
- FlutterMacOS - FlutterMacOS
- share_plus (0.0.1):
- FlutterMacOS
- tray_manager (0.0.1): - tray_manager (0.0.1):
- FlutterMacOS - FlutterMacOS
- url_launcher_macos (0.0.1): - url_launcher_macos (0.0.1):
@@ -37,9 +42,11 @@ DEPENDENCIES:
- flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`) - flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`)
- flutter_udid (from `Flutter/ephemeral/.symlinks/plugins/flutter_udid/macos`) - flutter_udid (from `Flutter/ephemeral/.symlinks/plugins/flutter_udid/macos`)
- FlutterMacOS (from `Flutter/ephemeral`) - FlutterMacOS (from `Flutter/ephemeral`)
- in_app_purchase_storekit (from `Flutter/ephemeral/.symlinks/plugins/in_app_purchase_storekit/darwin`)
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`) - screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`)
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
- tray_manager (from `Flutter/ephemeral/.symlinks/plugins/tray_manager/macos`) - tray_manager (from `Flutter/ephemeral/.symlinks/plugins/tray_manager/macos`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
- webview_flutter_wkwebview (from `Flutter/ephemeral/.symlinks/plugins/webview_flutter_wkwebview/darwin`) - webview_flutter_wkwebview (from `Flutter/ephemeral/.symlinks/plugins/webview_flutter_wkwebview/darwin`)
@@ -62,12 +69,16 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/flutter_udid/macos :path: Flutter/ephemeral/.symlinks/plugins/flutter_udid/macos
FlutterMacOS: FlutterMacOS:
:path: Flutter/ephemeral :path: Flutter/ephemeral
in_app_purchase_storekit:
:path: Flutter/ephemeral/.symlinks/plugins/in_app_purchase_storekit/darwin
package_info_plus: package_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
path_provider_foundation: path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
screen_retriever_macos: screen_retriever_macos:
:path: Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos
share_plus:
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
tray_manager: tray_manager:
:path: Flutter/ephemeral/.symlinks/plugins/tray_manager/macos :path: Flutter/ephemeral/.symlinks/plugins/tray_manager/macos
url_launcher_macos: url_launcher_macos:
@@ -83,12 +94,14 @@ SPEC CHECKSUMS:
flutter_inappwebview_macos: bdf207b8f4ebd58e86ae06cd96b147de99a67c9b flutter_inappwebview_macos: bdf207b8f4ebd58e86ae06cd96b147de99a67c9b
flutter_udid: 2e7b3da4b5fdfba86a396b97898f5fe8f4ec1a52 flutter_udid: 2e7b3da4b5fdfba86a396b97898f5fe8f4ec1a52
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
in_app_purchase_storekit: a1ce04056e23eecc666b086040239da7619cd783
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
ReachabilitySwift: 32793e867593cfc1177f5d16491e3a197d2fccda ReachabilitySwift: 32793e867593cfc1177f5d16491e3a197d2fccda
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
screen_retriever_macos: 776e0fa5d42c6163d2bf772d22478df4b302b161 screen_retriever_macos: 776e0fa5d42c6163d2bf772d22478df4b302b161
share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7
tray_manager: 9064e219c56d75c476e46b9a21182087930baf90 tray_manager: 9064e219c56d75c476e46b9a21182087930baf90
url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404
webview_flutter_wkwebview: a4af96a051138e28e29f60101d094683b9f82188 webview_flutter_wkwebview: a4af96a051138e28e29f60101d094683b9f82188
+69 -63
View File
@@ -21,6 +21,7 @@
/* End PBXAggregateTarget section */ /* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
1B50E024F09C0847B322E7A5 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 89F6149C36E7AFE5A3ABB11E /* Pods_Runner.framework */; };
27D5969F2A92B6AB00E2BE2B /* libcore.dylib in Bundle Framework */ = {isa = PBXBuildFile; fileRef = 27D5969E2A92B6AB00E2BE2B /* libcore.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 27D5969F2A92B6AB00E2BE2B /* libcore.dylib in Bundle Framework */ = {isa = PBXBuildFile; fileRef = 27D5969E2A92B6AB00E2BE2B /* libcore.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; };
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
@@ -28,8 +29,7 @@
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
C2CEC907B854CCA50E3CB29E /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7774D5331C4F7054E7047DF2 /* Pods_RunnerTests.framework */; }; 735847C11F818DB2659EAEBB /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CC0A1864A66B0EBDB3650093 /* Pods_RunnerTests.framework */; };
FEE8413B259D2FDED8BE933A /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A1027315C9B3E3F83410A09 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@@ -64,8 +64,7 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
1A1027315C9B3E3F83410A09 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0E9F7018ADFB388D1A0119C2 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
1C05D6984C93FE0E5C9038D0 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
27D5969E2A92B6AB00E2BE2B /* libcore.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcore.dylib; path = ../libcore/bin/libcore.dylib; sourceTree = "<group>"; }; 27D5969E2A92B6AB00E2BE2B /* libcore.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcore.dylib; path = ../libcore/bin/libcore.dylib; sourceTree = "<group>"; };
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
@@ -83,14 +82,15 @@
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; }; 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
3E235443DDCB73D48BD2341C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; }; 5204411A9795E11DF5FEF8FA /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
49CD96905E177C4420930499 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
7774D5331C4F7054E7047DF2 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
7C0B4F177A93E89661E9B0CB /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; }; 7C48389468CADEF5153B6C38 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
89F6149C36E7AFE5A3ABB11E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
D96FD811DCD14FAF8EC1F071 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; }; AD61A6C2536BCECF39E38B5A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
DBFB795F5E72237D66B9D391 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; }; CC0A1864A66B0EBDB3650093 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
CF623D4C3CE0EB191363F491 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
E21A6DACEC73D25BDC109FA6 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@@ -98,7 +98,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
C2CEC907B854CCA50E3CB29E /* Pods_RunnerTests.framework in Frameworks */, 735847C11F818DB2659EAEBB /* Pods_RunnerTests.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -106,7 +106,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
FEE8413B259D2FDED8BE933A /* Pods_Runner.framework in Frameworks */, 1B50E024F09C0847B322E7A5 /* Pods_Runner.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -116,12 +116,12 @@
0B357514C188DFB6739B421A /* Pods */ = { 0B357514C188DFB6739B421A /* Pods */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
3E235443DDCB73D48BD2341C /* Pods-Runner.debug.xcconfig */, AD61A6C2536BCECF39E38B5A /* Pods-Runner.debug.xcconfig */,
DBFB795F5E72237D66B9D391 /* Pods-Runner.release.xcconfig */, CF623D4C3CE0EB191363F491 /* Pods-Runner.release.xcconfig */,
49CD96905E177C4420930499 /* Pods-Runner.profile.xcconfig */, 0E9F7018ADFB388D1A0119C2 /* Pods-Runner.profile.xcconfig */,
7C0B4F177A93E89661E9B0CB /* Pods-RunnerTests.debug.xcconfig */, E21A6DACEC73D25BDC109FA6 /* Pods-RunnerTests.debug.xcconfig */,
1C05D6984C93FE0E5C9038D0 /* Pods-RunnerTests.release.xcconfig */, 7C48389468CADEF5153B6C38 /* Pods-RunnerTests.release.xcconfig */,
D96FD811DCD14FAF8EC1F071 /* Pods-RunnerTests.profile.xcconfig */, 5204411A9795E11DF5FEF8FA /* Pods-RunnerTests.profile.xcconfig */,
); );
path = Pods; path = Pods;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -153,8 +153,8 @@
33CEB47122A05771004F2AC0 /* Flutter */, 33CEB47122A05771004F2AC0 /* Flutter */,
331C80D6294CF71000263BE5 /* RunnerTests */, 331C80D6294CF71000263BE5 /* RunnerTests */,
33CC10EE2044A3C60003C045 /* Products */, 33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */,
0B357514C188DFB6739B421A /* Pods */, 0B357514C188DFB6739B421A /* Pods */,
E047C3A4031E4DE5BC5E27C2 /* Frameworks */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@@ -202,11 +202,11 @@
path = Runner; path = Runner;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
D73912EC22F37F3D000D13A0 /* Frameworks */ = { E047C3A4031E4DE5BC5E27C2 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
1A1027315C9B3E3F83410A09 /* Pods_Runner.framework */, 89F6149C36E7AFE5A3ABB11E /* Pods_Runner.framework */,
7774D5331C4F7054E7047DF2 /* Pods_RunnerTests.framework */, CC0A1864A66B0EBDB3650093 /* Pods_RunnerTests.framework */,
); );
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -218,7 +218,7 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = ( buildPhases = (
33C54E5BCA15F9B3C8DDCCD2 /* [CP] Check Pods Manifest.lock */, 4F52B9FF0CECBBDA5E76144D /* [CP] Check Pods Manifest.lock */,
331C80D1294CF70F00263BE5 /* Sources */, 331C80D1294CF70F00263BE5 /* Sources */,
331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D2294CF70F00263BE5 /* Frameworks */,
331C80D3294CF70F00263BE5 /* Resources */, 331C80D3294CF70F00263BE5 /* Resources */,
@@ -237,13 +237,13 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = ( buildPhases = (
40CB9C5051F17943B75EF3A9 /* [CP] Check Pods Manifest.lock */, 7E02E54BFA9F182BD91FA4B4 /* [CP] Check Pods Manifest.lock */,
33CC10E92044A3C60003C045 /* Sources */, 33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */, 33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */, 33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */, 3399D490228B24CF009A79C7 /* ShellScript */,
DDB714AFDEF721265C0C3AF7 /* [CP] Embed Pods Frameworks */, B6717AA3293389E8D76FAF29 /* [CP] Embed Pods Frameworks */,
); );
buildRules = ( buildRules = (
); );
@@ -342,7 +342,27 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
}; };
33C54E5BCA15F9B3C8DDCCD2 /* [CP] Check Pods Manifest.lock */ = { 33CC111E2044C6BF0003C045 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
Flutter/ephemeral/FlutterInputs.xcfilelist,
);
inputPaths = (
Flutter/ephemeral/tripwire,
);
outputFileListPaths = (
Flutter/ephemeral/FlutterOutputs.xcfilelist,
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
};
4F52B9FF0CECBBDA5E76144D /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
@@ -364,27 +384,7 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
33CC111E2044C6BF0003C045 /* ShellScript */ = { 7E02E54BFA9F182BD91FA4B4 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
Flutter/ephemeral/FlutterInputs.xcfilelist,
);
inputPaths = (
Flutter/ephemeral/tripwire,
);
outputFileListPaths = (
Flutter/ephemeral/FlutterOutputs.xcfilelist,
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
};
40CB9C5051F17943B75EF3A9 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
@@ -406,7 +406,7 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
DDB714AFDEF721265C0C3AF7 /* [CP] Embed Pods Frameworks */ = { B6717AA3293389E8D76FAF29 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
@@ -474,7 +474,7 @@
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
331C80DB294CF71000263BE5 /* Debug */ = { 331C80DB294CF71000263BE5 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 7C0B4F177A93E89661E9B0CB /* Pods-RunnerTests.debug.xcconfig */; baseConfigurationReference = E21A6DACEC73D25BDC109FA6 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
@@ -489,7 +489,7 @@
}; };
331C80DC294CF71000263BE5 /* Release */ = { 331C80DC294CF71000263BE5 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 1C05D6984C93FE0E5C9038D0 /* Pods-RunnerTests.release.xcconfig */; baseConfigurationReference = 7C48389468CADEF5153B6C38 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
@@ -504,7 +504,7 @@
}; };
331C80DD294CF71000263BE5 /* Profile */ = { 331C80DD294CF71000263BE5 /* Profile */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = D96FD811DCD14FAF8EC1F071 /* Pods-RunnerTests.profile.xcconfig */; baseConfigurationReference = 5204411A9795E11DF5FEF8FA /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
@@ -571,11 +571,12 @@
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*]" = "Developer ID Application";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = NO; "DEVELOPMENT_TEAM[sdk=macosx*]" = NJRRF427XB;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN; INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
@@ -583,8 +584,9 @@
"@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 = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "HiFastVPN-Mac-Pord";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };
name = Profile; name = Profile;
@@ -704,11 +706,12 @@
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*]" = "Developer ID Application";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = NO; "DEVELOPMENT_TEAM[sdk=macosx*]" = NJRRF427XB;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN; INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
@@ -716,8 +719,9 @@
"@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 = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "HiFastVPN-Mac-Pord";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };
@@ -731,11 +735,12 @@
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*]" = "Developer ID Application";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = NO; "DEVELOPMENT_TEAM[sdk=macosx*]" = NJRRF427XB;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN; INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
@@ -743,8 +748,9 @@
"@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 = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "HiFastVPN-Mac-Pord";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };
name = Release; name = Release;

Some files were not shown because too many files have changed in this diff Show More