feat: bugfix

This commit is contained in:
speakeloudest 2025-12-01 06:54:18 -08:00
parent 1b1b38aba5
commit 86687cac58
22 changed files with 827 additions and 493 deletions

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">
<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"/>
<svg width="32" height="58" viewBox="0 0 32 58" fill="none" xmlns="http://www.w3.org/2000/svg">
<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>

Before

Width:  |  Height:  |  Size: 343 B

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -26,6 +26,8 @@ PODS:
- Flutter
- ReachabilitySwift (5.2.4)
- SAMKeychain (1.5.3)
- share_plus (0.0.1):
- Flutter
- url_launcher_ios (0.0.1):
- Flutter
- webview_flutter_wkwebview (0.0.1):
@ -42,6 +44,7 @@ DEPENDENCIES:
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`)
@ -69,6 +72,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
webview_flutter_wkwebview:
@ -87,6 +92,7 @@ SPEC CHECKSUMS:
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
ReachabilitySwift: 32793e867593cfc1177f5d16491e3a197d2fccda
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
webview_flutter_wkwebview: a4af96a051138e28e29f60101d094683b9f82188

View File

@ -758,11 +758,9 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = PacketTunnel/HiddifyPacketTunnel.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = NJRRF427XB;
DEVELOPMENT_TEAM = NJRRF427XB;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
EXCLUDED_ARCHS = armv7;
GCC_C_LANGUAGE_STANDARD = gnu17;
@ -788,7 +786,6 @@
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).PacketTunnel";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "HiFastVPN-iOS-Pord-PacketTunnel";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
@ -814,11 +811,9 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = PacketTunnel/PacketTunnelRelease.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = NJRRF427XB;
DEVELOPMENT_TEAM = NJRRF427XB;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
EXCLUDED_ARCHS = armv7;
GCC_C_LANGUAGE_STANDARD = gnu17;
@ -844,7 +839,6 @@
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).PacketTunnel";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "HiFastVPN-iOS-Pord-PacketTunnel";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
@ -868,11 +862,9 @@
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_ENTITLEMENTS = PacketTunnel/HiddifyPacketTunnel.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = NJRRF427XB;
DEVELOPMENT_TEAM = NJRRF427XB;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
EXCLUDED_ARCHS = armv7;
GCC_C_LANGUAGE_STANDARD = gnu17;
@ -898,7 +890,6 @@
PRODUCT_BUNDLE_IDENTIFIER = "$(BASE_BUNDLE_IDENTIFIER).PacketTunnel";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "HiFastVPN-iOS-Pord-PacketTunnel";
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
@ -977,11 +968,9 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = NJRRF427XB;
DEVELOPMENT_TEAM = NJRRF427XB;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphoneos*]" = armv7;
INFOPLIST_FILE = Runner/Info.plist;
@ -1013,7 +1002,6 @@
"PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = com.taw.hifastvpn;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "HiFastVPN-iOS-Pord";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -1208,11 +1196,9 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = NJRRF427XB;
DEVELOPMENT_TEAM = NJRRF427XB;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphoneos*]" = armv7;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64";
@ -1244,7 +1230,6 @@
"PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = com.taw.hifastvpn;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "HiFastVPN-iOS-Pord";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -1265,11 +1250,9 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = NJRRF427XB;
DEVELOPMENT_TEAM = NJRRF427XB;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphoneos*]" = armv7;
INFOPLIST_FILE = Runner/Info.plist;
@ -1301,7 +1284,6 @@
"PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = com.taw.hifastvpn;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "HiFastVPN-iOS-Pord";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.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/widgets/hi_base_scaffold.dart';
import 'package:kaer_with_panels/app/widgets/kr_local_image.dart';
@ -15,52 +16,57 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
@override
Widget build(BuildContext context) {
return HIBaseScaffold(
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(40.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_buildUserInfoSection(),
SizedBox(height: 12.w),
//
ConstrainedBox(
constraints: BoxConstraints(
minHeight: 250.h, // 2. 300 (使 .h )
),
// 3. 使 Column MainAxisAlignment.center 使
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
_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),
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
FocusScope.of(context).unfocus();
},
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(40.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_buildUserInfoSection(),
SizedBox(height: 12.w),
//
ConstrainedBox(
constraints: BoxConstraints(
minHeight: 250.h, // 2. 300 (使 .h )
),
alignment: Alignment.center,
child: Text(
'注销账户',
style: TextStyle(
color: const Color(0xFFFF2ED1),
fontSize: 16.sp,
fontWeight: FontWeight.w700,
// 3. 使 Column MainAxisAlignment.center 使
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
_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,
child: Text(
'注销账户',
style: TextStyle(
color: const Color(0xFFFF2ED1),
fontSize: 16.sp,
fontWeight: FontWeight.w700,
),
),
),
),
),
/*SizedBox(
/*SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: controller.requestDeleteAccount,
@ -80,8 +86,9 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
),
),
),*/
SizedBox(height: 20.w),
],
SizedBox(height: 20.w),
],
),
),
),
),
@ -129,11 +136,9 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
);
}),
Obx(() {
final account = KRAppRunData.getInstance()
.kr_account
.value;
final isDeviceLogin = account != null &&
account.startsWith('9000');
final account = KRAppRunData.getInstance().kr_account.value;
final isDeviceLogin =
account != null && account.startsWith('9000');
final accountText = isDeviceLogin
? '待绑定'
: '${KRAppRunData.getInstance().kr_account.value.toString()}';
@ -164,11 +169,44 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
///
Widget _buildVerificationCodeField() {
return SizedBox(
height: 50.w, //
height: 50.w,
child: TextField(
controller: controller.kr_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(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(
fontSize: 16,
color: Colors.white,
@ -184,19 +222,20 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.w),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.w),
borderSide: BorderSide(color: Colors.white, width: 2),
borderSide: const BorderSide(color: Colors.white, width: 2),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.w),
borderSide: BorderSide(color: Colors.white, width: 2),
borderSide: const BorderSide(color: Colors.white, width: 2),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.w),
borderSide: BorderSide(color: Colors.white, width: 2),
borderSide: const BorderSide(color: Colors.white, width: 2),
),
isDense: true,
suffixIconConstraints: BoxConstraints(
maxHeight: 50.w, //
maxHeight: 50.w,
maxWidth: 190.w,
),
suffixIcon: Padding(
padding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 5.w),
@ -212,7 +251,7 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
color: controller.kr_canSendCode.value
? Theme.of(Get.context!).primaryColor
: const Color(0xFFD5D5D5),
borderRadius: BorderRadius.circular(100.r), //
borderRadius: BorderRadius.circular(100.r),
),
child: Text(
controller.kr_canSendCode.value

View File

@ -91,9 +91,9 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
final kr_isLatency = false.obs;
///
var kr_cutTag = 'auto'.obs;
var kr_cutSeletedTag = 'auto'.obs;
var kr_coutryText = 'auto'.obs;
var kr_cutTag = ''.obs;
var kr_cutSeletedTag = ''.obs;
var kr_coutryText = ''.obs;
var kr_selectedCountryTag = 'auto'.obs;
void kr_setSelectedCountryTag(String country) {
@ -216,6 +216,17 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
}
await _kr_prepareCountrySelectionBeforeStart();
final selectedAfter =
await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG');
KRLogUtil.kr_i('准备后 SELECTED_NODE_TAG_autoConnect: ${selectedAfter ?? ''}',
tag: 'HomeController');
KRLogUtil.kr_i('准备后 kr_currentNodeName_autoConnect: ${kr_currentNodeName.value}',
tag: 'HomeController');
KRLogUtil.kr_i('准备后 kr_cutTag_autoConnect: ${kr_cutTag.value}',
tag: 'HomeController');
KRLogUtil.kr_i('准备后 kr_cutSeletedTag_autoConnect: ${kr_cutSeletedTag.value}',
tag: 'HomeController');
await kr_performNodeSwitch(selectedAfter!);
await KRSingBoxImp.instance.kr_start();
KRLogUtil.kr_i('闪连自动连接执行完成', tag: 'QuickConnect');
} catch (e) {
@ -659,9 +670,9 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
break;
case KRSubscribeServiceStatus.kr_success:
KRLogUtil.kr_i('订阅服务成功', tag: 'HomeController');
kr_cutTag.value = 'auto';
kr_cutSeletedTag.value = 'auto';
kr_currentNodeName.value = "auto";
kr_cutTag.value = '';
kr_cutSeletedTag.value = '';
kr_currentNodeName.value = "";
if (kr_currentListStatus.value != KRHomeViewsListStatus.kr_none) {
kr_currentListStatus.value = KRHomeViewsListStatus.kr_none;
} else {
@ -779,8 +790,6 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
break;
case SingboxStopping():
KRLogUtil.kr_i('🟠 状态: 正在停止', tag: 'HomeController');
//
_cancelConnectionTimeout();
kr_connectText.value = AppTranslations.kr_home.disconnecting;
kr_isConnected.value = false;
kr_currentSpeed.value = "--";
@ -885,6 +894,17 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
KRLogUtil.kr_i('🔄 开始连接...', tag: 'HomeController');
if (kDebugMode) {}
await _kr_prepareCountrySelectionBeforeStart();
final selectedAfter =
await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG');
KRLogUtil.kr_i('准备后 SELECTED_NODE_TAG: ${selectedAfter ?? ''}',
tag: 'HomeController');
KRLogUtil.kr_i('准备后 kr_currentNodeName: ${kr_currentNodeName.value}',
tag: 'HomeController');
KRLogUtil.kr_i('准备后 kr_cutTag: ${kr_cutTag.value}',
tag: 'HomeController');
KRLogUtil.kr_i('准备后 kr_cutSeletedTag: ${kr_cutSeletedTag.value}',
tag: 'HomeController');
await kr_performNodeSwitch(selectedAfter!);
await KRSingBoxImp.instance.kr_start();
KRLogUtil.kr_i('✅ 连接命令已发送', tag: 'HomeController');
if (kDebugMode) {}
@ -958,6 +978,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG');
KRLogUtil.kr_i('写入后校验 SELECTED_NODE_TAG: $verify',
tag: 'CountrySelect');
kr_currentNodeName.value = best;
kr_cutTag.value = best;
kr_cutSeletedTag.value = best;
kr_updateConnectionInfo();
return;
@ -974,6 +996,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG');
KRLogUtil.kr_i('写入后校验 SELECTED_NODE_TAG: $verifyA',
tag: 'CountrySelect');
kr_currentNodeName.value = bestAfterTest;
kr_cutTag.value = bestAfterTest;
kr_cutSeletedTag.value = bestAfterTest;
kr_updateConnectionInfo();
return;
@ -991,6 +1015,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG');
KRLogUtil.kr_i('写入后校验 SELECTED_NODE_TAG: $verifyB',
tag: 'CountrySelect');
kr_currentNodeName.value = fallback;
kr_cutTag.value = fallback;
kr_cutSeletedTag.value = fallback;
kr_updateConnectionInfo();
} else {
@ -1027,6 +1053,8 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
await KRSecureStorage().kr_readData(key: 'SELECTED_NODE_TAG');
KRLogUtil.kr_i('写入后校验 SELECTED_NODE_TAG: $verify2',
tag: 'CountrySelect');
kr_currentNodeName.value = bestInCountry;
kr_cutTag.value = bestInCountry;
kr_cutSeletedTag.value = bestInCountry;
kr_updateConnectionInfo();
} else {
@ -1084,22 +1112,33 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
void _kr_handleSelectorProxy(dynamic element, List<dynamic> allGroups) {
try {
KRLogUtil.kr_d(
'处理选择器代理 - 当前选择: ${element.selected}, 用户选择: ${kr_cutTag.value}',
'处理选择器代理 - 内核选择: ${element.selected}, 用户选择: ${kr_cutTag.value}, 切换中: $_isSwitchingNode',
tag: 'HomeController');
// 🔧 UI
// kr_cutSeletedTag
if (element.selected.isNotEmpty) {
kr_cutSeletedTag.value = element.selected;
// 🔧 UI
// "跳两次"
if (_isSwitchingNode) {
KRLogUtil.kr_d('⏳ 节点切换进行中,跳过内核状态同步', tag: 'HomeController');
//
_kr_updateNodeLatency(element);
return;
}
// auto
if (kr_cutTag.value != "auto") {
// 🔧 selected
// kr_cutSeletedTag
if (element.selected == kr_cutTag.value) {
kr_cutSeletedTag.value = element.selected;
}
_kr_handleManualMode(element);
return;
}
// auto模式处理
// auto模式处理 - UI
if (element.selected.isNotEmpty) {
kr_cutSeletedTag.value = element.selected;
}
_kr_handleAutoMode(element, allGroups);
} catch (e) {
KRLogUtil.kr_e('处理选择器代理出错: $e', tag: 'HomeController');
@ -1109,15 +1148,25 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
///
void _kr_handleManualMode(dynamic element) {
try {
KRLogUtil.kr_d('处理手动模式 - 选择: ${element.selected}', tag: 'HomeController');
KRLogUtil.kr_d('处理手动模式 - 内核选择: ${element.selected}, 用户选择: ${kr_cutTag.value}', tag: 'HomeController');
// 🔧 UI
kr_cutSeletedTag.value = element.selected;
//
// 🔧 selected
//
// UI
//
_kr_updateNodeLatency(element);
kr_currentNodeName.value =
kr_truncateText(element.selected, maxLength: 25);
// kr_moveToSelectedNode();
// UI
if (element.selected == kr_cutTag.value) {
kr_cutSeletedTag.value = element.selected;
kr_currentNodeName.value = kr_truncateText(element.selected, maxLength: 25);
// kr_moveToSelectedNode();
KRLogUtil.kr_d('✅ 内核确认节点切换成功: ${element.selected}', tag: 'HomeController');
} else {
//
KRLogUtil.kr_d('⏳ 等待内核切换到用户选择的节点: ${kr_cutTag.value}', tag: 'HomeController');
}
} catch (e) {
KRLogUtil.kr_e('处理手动模式出错: $e', tag: 'HomeController');
}
@ -1315,12 +1364,9 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
// 🔒 2
final now = DateTime.now();
if (_lastSwitchTime != null &&
now.difference(_lastSwitchTime!) < _switchThrottleDuration) {
final remainingTime = _switchThrottleDuration.inMilliseconds -
now.difference(_lastSwitchTime!).inMilliseconds;
KRLogUtil.kr_w('⚠️ 切换过于频繁,请等待 ${remainingTime}ms',
tag: 'HomeController');
if (_lastSwitchTime != null && now.difference(_lastSwitchTime!) < _switchThrottleDuration) {
final remainingTime = _switchThrottleDuration.inMilliseconds - now.difference(_lastSwitchTime!).inMilliseconds;
KRLogUtil.kr_w('⚠️ 切换过于频繁,请等待 ${remainingTime}ms', tag: 'HomeController');
KRCommonUtil.kr_showToast('切换过于频繁,请稍后再试');
return false;
}
@ -1347,9 +1393,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
// 🔧 便VPN启动时应用
KRLogUtil.kr_i('💾 保存节点选择以便稍后应用: $tag', tag: 'HomeController');
KRSecureStorage()
.kr_saveData(key: 'SELECTED_NODE_TAG', value: tag)
.then((_) {
KRSecureStorage().kr_saveData(key: 'SELECTED_NODE_TAG', value: tag).then((_) {
KRLogUtil.kr_i('✅ 节点选择已保存: $tag', tag: 'HomeController');
}).catchError((e) {
KRLogUtil.kr_e('❌ 保存节点选择失败: $e', tag: 'HomeController');
@ -1358,91 +1402,93 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
return true;
}
// 4. VPN已连接VPN以断开现有连接并应用新节
// 4. VPN已连接使selectOutbound
try {
KRLogUtil.kr_i('🔌 VPN已连接将重启VPN以断开现有连接: $tag', tag: 'HomeController');
KRLogUtil.kr_i('🔌 VPN已连接使用热切换方式切换节点: $tag', tag: 'HomeController');
// 🔧
KRLogUtil.kr_i(
'📊 当前活动组数量: ${KRSingBoxImp.instance.kr_activeGroups.length}',
tag: 'HomeController');
for (var group in KRSingBoxImp.instance.kr_activeGroups) {
KRLogUtil.kr_i(
'📋 活动组: tag=${group.tag}, type=${group.type}, 节点数=${group.items.length}',
tag: 'HomeController');
for (var item in group.items) {
if (item.tag == tag) {
KRLogUtil.kr_i('✅ 找到目标节点: ${item.tag}', tag: 'HomeController');
}
}
}
// 🔧 VPN已连接时-1"正在连接"
// 🔧 "正在连接"
kr_currentNodeLatency.value = -1;
kr_isLatency.value = true; //
// 🔧
// 🔧
KRLogUtil.kr_i('💾 保存新节点选择: $tag', tag: 'HomeController');
await KRSecureStorage()
.kr_saveData(key: 'SELECTED_NODE_TAG', value: tag);
await KRSecureStorage().kr_saveData(key: 'SELECTED_NODE_TAG', value: tag);
// 🔧 A优化VPN连接以断开所有现有长连接
KRLogUtil.kr_i('🔄 [优化] 停止VPN连接以断开现有连接...', tag: 'HomeController');
await KRSingBoxImp.instance.kr_stop(); // VPNDNS恢复
// 🚀 使 selectOutbound hiddify-app
// VPNVPN开关不闪烁
KRLogUtil.kr_i('🔄 [热切换] 调用 selectOutbound 切换节点...', tag: 'HomeController');
// 🚀 DNS操作已优化
KRLogUtil.kr_i('⏳ [优化] 等待VPN完全停止800ms...', tag: 'HomeController');
await Future.delayed(
const Duration(milliseconds: 800)); // 1500ms减少到800ms
// 🔧 selector tag
// selectOutbound(groupTag, outboundTag) - tagtag
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
String selectorGroupTag = 'select'; //
KRLogUtil.kr_i('🔄 [优化] 启动VPN并应用新节点: $tag', tag: 'HomeController');
await KRSingBoxImp.instance.kr_start(); // VPNDNS备份
// selector
for (var group in activeGroups) {
if (group.type == ProxyType.selector) {
selectorGroupTag = group.tag;
KRLogUtil.kr_i('🔍 找到 selector 组: $selectorGroupTag', tag: 'HomeController');
break;
}
}
// 🚀 DNS操作已优化
KRLogUtil.kr_i('⏳ [优化] 等待VPN完全启动1200ms...', tag: 'HomeController');
await Future.delayed(
const Duration(milliseconds: 1200)); // 2500ms减少到1200ms
KRLogUtil.kr_i('📡 调用 selectOutbound("$selectorGroupTag", "$tag")', tag: 'HomeController');
// UI
// sing-box selectOutbound API
final result = await KRSingBoxImp.instance.kr_singBox
.selectOutbound(selectorGroupTag, tag)
.run();
//
result.fold(
(error) {
//
KRLogUtil.kr_e('❌ selectOutbound 调用失败: $error', tag: 'HomeController');
throw Exception('节点切换失败: $error');
},
(_) {
//
KRLogUtil.kr_i('✅ selectOutbound 调用成功', tag: 'HomeController');
},
);
// UI
kr_cutSeletedTag.value = tag;
kr_updateConnectionInfo();
kr_moveToSelectedNode();
// kr_moveToSelectedNode();
// 🚀
KRLogUtil.kr_i('⏳ [优化] 等待活动组更新300ms...', tag: 'HomeController');
await Future.delayed(
const Duration(milliseconds: 300)); // 500ms减少到300ms
// 🔧
KRLogUtil.kr_i('⏳ [热切换] 等待内核状态同步200ms...', tag: 'HomeController');
await Future.delayed(const Duration(milliseconds: 200));
// 🚀 A增强
KRLogUtil.kr_i('🔍 [增强] 验证节点切换是否成功...', tag: 'HomeController');
// 🔍
KRLogUtil.kr_i('🔍 [验证] 检查节点切换结果...', tag: 'HomeController');
try {
//
final activeGroups = KRSingBoxImp.instance.kr_activeGroups;
final selectGroup = activeGroups.firstWhere(
(group) => group.tag == 'select',
orElse: () => throw Exception('未找到 select 组'),
final updatedGroups = KRSingBoxImp.instance.kr_activeGroups;
final selectGroup = updatedGroups.firstWhere(
(group) => group.type == ProxyType.selector,
orElse: () => throw Exception('未找到 selector 组'),
);
KRLogUtil.kr_i('📊 [增强] Select组当前选中: ${selectGroup.selected}',
tag: 'HomeController');
KRLogUtil.kr_i('📊 [增强] 目标节点: $tag', tag: 'HomeController');
KRLogUtil.kr_i('📊 [验证] ${selectGroup.tag}组当前选中: ${selectGroup.selected}', tag: 'HomeController');
KRLogUtil.kr_i('📊 [验证] 目标节点: $tag', tag: 'HomeController');
if (selectGroup.selected != tag) {
KRLogUtil.kr_w('⚠️ [增强] 节点选择验证失败,实际选中: ${selectGroup.selected}',
tag: 'HomeController');
KRLogUtil.kr_w('⚠️ [验证] 节点选择验证失败,实际选中: ${selectGroup.selected}', tag: 'HomeController');
//
} else {
KRLogUtil.kr_i('✅ [增强] 节点选择验证成功!', tag: 'HomeController');
KRLogUtil.kr_i('✅ [验证] 节点选择验证成功!', tag: 'HomeController');
}
} catch (e) {
KRLogUtil.kr_w('⚠️ [增强] 节点验证过程出错: $e', tag: 'HomeController');
KRLogUtil.kr_w('⚠️ [验证] 节点验证过程出错: $e', tag: 'HomeController');
}
//
_kr_updateLatencyOnConnected();
KRLogUtil.kr_i('✅ 节点切换成功(已重启VPN断开旧连接): $tag', tag: 'HomeController');
KRLogUtil.kr_i('✅ 节点热切换成功VPN保持连接: $tag', tag: 'HomeController');
return true;
} catch (switchError) {
//
KRLogUtil.kr_e('❌ 后台节点切换失败: $switchError', tag: 'HomeController');
@ -1454,8 +1500,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
//
try {
await KRSecureStorage()
.kr_saveData(key: 'SELECTED_NODE_TAG', value: originalTag);
await KRSecureStorage().kr_saveData(key: 'SELECTED_NODE_TAG', value: originalTag);
} catch (e) {
KRLogUtil.kr_e('❌ 恢复节点选择失败: $e', tag: 'HomeController');
}

View File

@ -55,7 +55,7 @@ class CustomCircleRoute extends PageRouteBuilder {
required this.child,
required this.startOffset,
required this.transitionColor,
this.customDuration = const Duration(milliseconds: 1500),
this.customDuration = const Duration(milliseconds: 250),
}) : super(
opaque: false,
transitionDuration: customDuration,
@ -162,7 +162,7 @@ class _HISubscriptionCornerButtonState extends State<HISubscriptionCornerButton>
child: KRPurchaseMembershipView(),
startOffset: startOffset,
transitionColor: transitionColor,
customDuration: const Duration(milliseconds: 1500),
customDuration: const Duration(milliseconds: 500),
),
).then((_) {
//

View File

@ -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 '../controllers/kr_invite_controller.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/widgets/hi_base_scaffold.dart';
import 'package:kaer_with_panels/app/widgets/hi_help_entrance.dart';
@ -15,7 +16,6 @@ class KRInviteView extends GetView<KRInviteController> {
@override
Widget build(BuildContext context) {
final isKeyboardVisible = MediaQuery.of(context).viewInsets.bottom > 0;
return HIBaseScaffold(
@ -35,7 +35,8 @@ class KRInviteView extends GetView<KRInviteController> {
// 🟢
Container(
width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.w),
padding:
EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.w),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(25.r),
@ -68,13 +69,14 @@ class KRInviteView extends GetView<KRInviteController> {
SizedBox(height: 26.w),
// 🟢
Container(
padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 2.w),
padding:
EdgeInsets.symmetric(horizontal: 4.w, vertical: 2.w),
decoration: BoxDecoration(
border: Border.all(color: Colors.white, width: 2.0),
borderRadius: BorderRadius.circular(1000.r),
),
child: Obx(
() => Row(
() => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
@ -113,12 +115,27 @@ class KRInviteView extends GetView<KRInviteController> {
),
onPressed: () {
if (controller.kr_referCode.value.isNotEmpty) {
Clipboard.setData(
ClipboardData(text: controller.kr_referCode.value),
);
KRCommonUtil.kr_showToast(
AppTranslations.kr_invite.inviteCodeCopied,
);
if (GetPlatform.isIOS) {
final code = controller.kr_referCode.value;
final text = '#您的好友邀请您使用Hi快网络加速器\n'
'安装完毕后,在软件内<邀请好友>页面粘贴以下邀请码\n'
'$code\n'
'您和您的好友将会分别获得3天免费时长\n\n'
'点击此处进入下载页面\n'
'或在浏览器输入hifastvpn.com下载#';
Share.share(
text,
subject: '直接分享Hi快VPN邀请链接',
);
} else {
Clipboard.setData(
ClipboardData(
text: controller.kr_referCode.value),
);
KRCommonUtil.kr_showToast(
AppTranslations.kr_invite.inviteCodeCopied,
);
}
}
},
),
@ -143,24 +160,29 @@ class KRInviteView extends GetView<KRInviteController> {
TextField(
controller: controller.otherInviteCodeController,
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
style: const TextStyle(
color: Colors.white, fontWeight: FontWeight.bold),
decoration: InputDecoration(
hintText: '填入邀请人邀请码兑换免费时长...',
hintStyle: const TextStyle(color: Color(0xFFA6A6A6)),
filled: true,
fillColor: Colors.transparent,
contentPadding: EdgeInsets.symmetric(horizontal: 22.w),
contentPadding:
EdgeInsets.symmetric(horizontal: 22.w),
border: OutlineInputBorder(
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(
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(
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),
),
@ -199,8 +221,7 @@ class KRInviteView extends GetView<KRInviteController> {
),
// 5. HIHelpEntrance Stack
if (!isKeyboardVisible)
const HIHelpEntrance(),
if (!isKeyboardVisible) const HIHelpEntrance(),
],
),
);

View File

@ -13,6 +13,7 @@ import 'package:kaer_with_panels/app/modules/kr_home/controllers/kr_home_control
import 'package:kaer_with_panels/app/services/kr_site_config_service.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> {
const KRLoginView({super.key});
@ -27,23 +28,29 @@ class KRLoginView extends GetView<KRLoginController> {
resizeToAvoidBottomInset: true,
child: Stack(
children: [
SingleChildScrollView(
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: 40.w,
),
child: Column(
children: [
// Text(
// '${controller.kr_loginStatus.value}',
// style: TextStyle(color: Colors.white), // 使 TextStyle()
// ),
SizedBox(height: 20.w),
_buildUserInfoSection(),
SizedBox(height: 12.w),
_buildContentByEntry(),
SizedBox(height: 100.w), //
],
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
FocusScope.of(context).unfocus();
},
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: 40.w,
),
child: Column(
children: [
// Text(
// '${controller.kr_loginStatus.value}',
// style: TextStyle(color: Colors.white), // 使 TextStyle()
// ),
SizedBox(height: 20.w),
_buildUserInfoSection(),
SizedBox(height: 12.w),
_buildContentByEntry(),
SizedBox(height: 100.w), //
],
),
),
),
),
@ -79,11 +86,9 @@ class KRLoginView extends GetView<KRLoginController> {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Obx(() {
final account = KRAppRunData.getInstance()
.kr_account
.value;
final isDeviceLogin = account != null &&
account.startsWith('9000');
final account = KRAppRunData.getInstance().kr_account.value;
final isDeviceLogin =
account != null && account.startsWith('9000');
final accountText = isDeviceLogin
? '待绑定'
: '${KRAppRunData.getInstance().kr_account.value.toString()}';
@ -270,6 +275,41 @@ class KRLoginView extends GetView<KRLoginController> {
height: 50.w, //
child: TextField(
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(
fontSize: 16,
color: Colors.white,

View File

@ -13,6 +13,7 @@ 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_fixed_scrollbar.dart';
import '../../../widgets/kr_simple_loading.dart';
class KRMessageView extends GetView<KRMessageController> {
const KRMessageView({super.key});
@ -25,62 +26,77 @@ class KRMessageView extends GetView<KRMessageController> {
children: [
//
Obx(() {
return EasyRefresh(
controller: controller.refreshController,
onRefresh: controller.kr_onRefresh,
onLoad: controller.kr_onLoadMore,
header: ClassicHeader(
dragText: '下拉刷新',
armedText: '释放刷新',
readyText: '正在刷新...',
processingText: '正在刷新...',
processedText: '刷新成功',
failedText: '刷新失败',
messageText: '最后更新于 %T',
textStyle: TextStyle(color: Colors.white.withOpacity(0.7)),
messageStyle: TextStyle(color: Colors.white.withOpacity(0.5), fontSize: 12.sp),
iconTheme: IconThemeData(color: Colors.white.withOpacity(0.7)),
),
// 3. Footer UI提示
footer: ClassicFooter(
dragText: '上拉加载',
armedText: '释放加载',
readyText: '正在加载...',
processingText: '正在加载...',
processedText: '加载成功',
failedText: '加载失败',
noMoreText: '没有更多数据了',
messageText: '最后更新于 %T',
textStyle: TextStyle(color: Colors.white.withOpacity(0.7)),
messageStyle: TextStyle(color: Colors.white.withOpacity(0.5), fontSize: 12.sp),
iconTheme: IconThemeData(color: Colors.white.withOpacity(0.7)),
),
child: Padding(
padding: EdgeInsets.only(right: 0.w, bottom: 90.w), // HIHelpEntrance留出空间
child: HiFixedScrollbar(
controller: scrollController,
isShowScrollbar: controller.kr_messages.length > 0,
child: ListView.builder(
controller: scrollController,
padding: EdgeInsets.symmetric(horizontal: 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),
);
},
return Column(
children: [
Expanded(
child: Padding(
padding: EdgeInsets.only(bottom: 90.w),
child: EasyRefresh(
controller: controller.refreshController,
onRefresh: controller.kr_onRefresh,
onLoad: controller.kr_onLoadMore,
header: ClassicHeader(
dragText: '下拉刷新',
armedText: '释放刷新',
readyText: '正在刷新...',
processingText: '正在刷新...',
processedText: '刷新成功',
failedText: '刷新失败',
messageText: '最后更新于 %T',
textStyle:
TextStyle(color: Colors.white.withOpacity(0.7)),
messageStyle: TextStyle(
color: Colors.white.withOpacity(0.5),
fontSize: 12.sp),
iconTheme:
IconThemeData(color: Colors.white.withOpacity(0.7)),
),
footer: ClassicFooter(
dragText: '上拉加载',
armedText: '释放加载',
readyText: '正在加载...',
processingText: '正在加载...',
processedText: '加载成功',
failedText: '加载失败',
noMoreText: '没有更多数据了',
messageText: '最后更新于 %T',
textStyle:
TextStyle(color: Colors.white.withOpacity(0.7)),
messageStyle: TextStyle(
color: Colors.white.withOpacity(0.5),
fontSize: 12.sp),
iconTheme:
IconThemeData(color: Colors.white.withOpacity(0.7)),
),
child: Padding(
padding: EdgeInsets.only(right: 0.w),
child: HiFixedScrollbar(
controller: scrollController,
child: ListView.builder(
controller: scrollController,
padding: EdgeInsets.symmetric(horizontal: 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(),
],

View File

@ -25,13 +25,10 @@ class KROrderStatusView extends GetView<KROrderStatusController> {
children: [
Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.w),
padding: EdgeInsets.symmetric(horizontal: 0.w),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
//
// _buildStatusIcon(),
SizedBox(height: 122.h),
//
_buildStatusText(),
SizedBox(height: 16.h),

View File

@ -92,14 +92,14 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
padding:
EdgeInsets.only(bottom: 8.0),
child: SizedBox(
height: 140.0,
height: 130.0,
child: _kr_buildPlanOptionCard(
plan,
controller
.kr_selectedPlanIndex.value,
discountIndex,
context,
index == 0,
index,
),
),
);
@ -167,11 +167,12 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
int planIndex,
int? discountIndex,
BuildContext context,
bool isFirst,
int index,
) {
// ObxisSelected
// UI
// isSelected isFirst
bool isFirst = 0 == index;
bool isSelected = !isFirst;
return GestureDetector(
@ -190,16 +191,16 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
child: ClipRRect(
borderRadius: BorderRadius.circular(38.0), //
child: Container(
padding: EdgeInsets.fromLTRB(45.0, 10.0, 0.0, 10.0),
padding: EdgeInsets.fromLTRB(45.0, 0.0, 0.0, 0.0),
decoration: BoxDecoration(
color: isSelected ? Colors.black : Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(38.0),
),
child: Stack(
alignment: Alignment.centerLeft,
clipBehavior: Clip.none,
children: [
Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@ -214,25 +215,80 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
: Theme.of(context).textTheme.bodyMedium?.color,
),
),
Text(
'¥${controller.kr_getPlanPrice(plan, discountIndex: discountIndex).toStringAsFixed(2)}',
style: TextStyle(
fontSize: 40,
height: 1,
fontWeight: FontWeight.w600,
Row(
mainAxisSize: MainAxisSize.min,
children: [
KrLocalImage(
imageName: 'money-icon',
imageType: ImageType.svg,
width: 36,
height: 36,
color: isSelected
? Colors.white
: Theme.of(context).textTheme.bodyMedium?.color,
)),
Text(
'约¥${controller.kr_getDayPlanPrice(plan, discountIndex: discountIndex).toStringAsFixed(2)}/天',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
SizedBox(width: 2),
Text(
controller
.kr_getPlanPrice(plan,
discountIndex: discountIndex)
.toStringAsFixed(2),
style: TextStyle(
fontSize: 40,
height: 1,
fontWeight: FontWeight.w600,
color: isSelected
? Colors.white
: Theme.of(context)
.textTheme
.bodyMedium
?.color,
),
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: isSelected
? Colors.white
: Theme.of(context)
.textTheme
.bodyMedium
?.color,
),
),
SizedBox(width: 2),
KrLocalImage(
imageName: 'money-icon',
imageType: ImageType.svg,
width: 16,
height: 16,
color: isSelected
? Colors.white
: Theme.of(context).textTheme.bodyMedium?.color,
)),
),
SizedBox(width: 2),
Text(
'${controller.kr_getDayPlanPrice(plan, discountIndex: discountIndex).toStringAsFixed(2)}/天',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: isSelected
? Colors.white
: Theme.of(context)
.textTheme
.bodyMedium
?.color,
),
),
],
),
]),
if (!isFirst)
Positioned(
@ -242,7 +298,7 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
angle: 0.785398, // 45 (π/4)
child: Container(
//
transform: Matrix4.translationValues(30, -10, 0),
transform: Matrix4.translationValues(40, -10, 0),
width: 130, //
height: 20, //
decoration: BoxDecoration(
@ -254,7 +310,9 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController> {
style: TextStyle(
color: Colors.black, //
fontSize: 14,
fontWeight: FontWeight.w300,
fontWeight: index == 1
? FontWeight.w300
: FontWeight.w600,
),
textAlign: TextAlign.center,
),

View File

@ -110,7 +110,7 @@ class SlideTransparentTransition extends CustomTransition {
SlideTransparentTransition({
this.direction = SlideDirection.rightToLeft,
this.duration = const Duration(milliseconds: 2000),
this.duration = const Duration(milliseconds: 1000),
});
@override

View File

@ -4,7 +4,7 @@ import 'slide_transparent_transition.dart';
///
class TransitionConfig {
static Duration _defaultDuration = const Duration(milliseconds: 350);
static Duration _defaultDuration = const Duration(milliseconds: 150);
static const SlideDirection _defaultDirection = SlideDirection.rightToLeft;
static const Curve _defaultCurve = Curves.easeOutCubic;

View File

@ -88,15 +88,9 @@ class GlobalOverlayService extends GetxService {
},
child: IgnorePointer(
ignoring: false, //
child: SizedBox(
width: 44,
height: 44,
child: KrLocalImage(
imageName: 'money-icon',
width: 44,
height: 44,
imageType: ImageType.svg,
),
child: KrLocalImage(
imageName: 'money-icon',
imageType: ImageType.svg,
),
),
),

View File

@ -185,14 +185,14 @@ class KRSingBoxImp {
KRLogUtil.kr_i('iOS 路径获取完成: $paths');
kr_configDics = (
baseDir: Directory(paths?["base"]! as String),
workingDir: Directory(paths?["working"]! as String),
tempDir: Directory(paths?["temp"]! as String),
baseDir: Directory(paths?["base"]! as String),
workingDir: Directory(paths?["working"]! as String),
tempDir: Directory(paths?["temp"]! as String),
);
} else {
final baseDir = await getApplicationSupportDirectory();
final workingDir =
Platform.isAndroid ? await getExternalStorageDirectory() : baseDir;
Platform.isAndroid ? await getExternalStorageDirectory() : baseDir;
final tempDir = await getTemporaryDirectory();
// Windows 使
@ -200,7 +200,8 @@ class KRSingBoxImp {
if (Platform.isWindows) {
final normalized = dir.path.replaceAll('/', '\\');
if (normalized != dir.path) {
KRLogUtil.kr_i('路径规范化: ${dir.path} -> $normalized', tag: 'SingBox');
KRLogUtil.kr_i('路径规范化: ${dir.path} -> $normalized',
tag: 'SingBox');
return Directory(normalized);
}
}
@ -208,16 +209,17 @@ class KRSingBoxImp {
}
kr_configDics = (
baseDir: normalizePath(baseDir),
workingDir: normalizePath(workingDir!),
tempDir: normalizePath(tempDir),
baseDir: normalizePath(baseDir),
workingDir: normalizePath(workingDir!),
tempDir: normalizePath(tempDir),
);
KRLogUtil.kr_i('其他平台路径初始化完成');
}
KRLogUtil.kr_i('开始创建目录');
KRLogUtil.kr_i('baseDir: ${kr_configDics.baseDir.path}', tag: 'SingBox');
KRLogUtil.kr_i('workingDir: ${kr_configDics.workingDir.path}', tag: 'SingBox');
KRLogUtil.kr_i('workingDir: ${kr_configDics.workingDir.path}',
tag: 'SingBox');
KRLogUtil.kr_i('tempDir: ${kr_configDics.tempDir.path}', tag: 'SingBox');
//
@ -253,12 +255,14 @@ class KRSingBoxImp {
break;
} else {
retryCount++;
KRLogUtil.kr_i('⚠️ data 目录创建后验证失败,重试 $retryCount/$maxRetries', tag: 'SingBox');
KRLogUtil.kr_i('⚠️ data 目录创建后验证失败,重试 $retryCount/$maxRetries',
tag: 'SingBox');
await Future.delayed(const Duration(milliseconds: 200));
}
} catch (e) {
retryCount++;
KRLogUtil.kr_e('❌ 创建 data 目录失败 (尝试 $retryCount/$maxRetries): $e', tag: 'SingBox');
KRLogUtil.kr_e('❌ 创建 data 目录失败 (尝试 $retryCount/$maxRetries): $e',
tag: 'SingBox');
if (retryCount >= maxRetries) {
throw Exception('无法创建 libcore 数据库目录: ${dataDir.path},错误: $e');
}
@ -294,32 +298,40 @@ class KRSingBoxImp {
// libcore Setup() os.Chdir(workingPath)使 "./data"
// os.Chdir() 访
if (!kr_configDics.workingDir.existsSync()) {
final error = '❌ workingDir 不存在,无法调用 setup(): ${kr_configDics.workingDir.path}';
final error =
'❌ workingDir 不存在,无法调用 setup(): ${kr_configDics.workingDir.path}';
KRLogUtil.kr_e(error, tag: 'SingBox');
throw Exception(error);
}
// workingDir
try {
final testWorkingFile = File(p.join(kr_configDics.workingDir.path, '.test_working_dir'));
final testWorkingFile =
File(p.join(kr_configDics.workingDir.path, '.test_working_dir'));
await testWorkingFile.writeAsString('test');
await testWorkingFile.delete();
KRLogUtil.kr_i('✅ workingDir 写入权限验证通过', tag: 'SingBox');
} catch (e) {
final error = '❌ workingDir 无写入权限: ${kr_configDics.workingDir.path}, 错误: $e';
final error =
'❌ workingDir 无写入权限: ${kr_configDics.workingDir.path}, 错误: $e';
KRLogUtil.kr_e(error, tag: 'SingBox');
throw Exception(error);
}
final finalDataDir = Directory(p.join(kr_configDics.workingDir.path, 'data'));
final finalDataDir =
Directory(p.join(kr_configDics.workingDir.path, 'data'));
if (!finalDataDir.existsSync()) {
KRLogUtil.kr_e('❌ 最终验证失败data 目录不存在', tag: 'SingBox');
KRLogUtil.kr_e('路径: ${finalDataDir.path}', tag: 'SingBox');
KRLogUtil.kr_e('workingDir 是否存在: ${kr_configDics.workingDir.existsSync()}', tag: 'SingBox');
KRLogUtil.kr_e(
'workingDir 是否存在: ${kr_configDics.workingDir.existsSync()}',
tag: 'SingBox');
if (kr_configDics.workingDir.existsSync()) {
try {
final workingDirContents = kr_configDics.workingDir.listSync();
KRLogUtil.kr_e('workingDir 内容: ${workingDirContents.map((e) => e.path.split(Platform.pathSeparator).last).join(", ")}', tag: 'SingBox');
KRLogUtil.kr_e(
'workingDir 内容: ${workingDirContents.map((e) => e.path.split(Platform.pathSeparator).last).join(", ")}',
tag: 'SingBox');
} catch (e) {
KRLogUtil.kr_e('无法列出 workingDir 内容: $e', tag: 'SingBox');
}
@ -338,11 +350,13 @@ class KRSingBoxImp {
// setup()
}
final configsDir = Directory(p.join(kr_configDics.workingDir.path, "configs"));
final configsDir =
Directory(p.join(kr_configDics.workingDir.path, "configs"));
if (!configsDir.existsSync()) {
try {
await configsDir.create(recursive: true);
KRLogUtil.kr_i('✅ 已创建 configs 目录: ${configsDir.path}', tag: 'SingBox');
KRLogUtil.kr_i('✅ 已创建 configs 目录: ${configsDir.path}',
tag: 'SingBox');
} catch (e) {
KRLogUtil.kr_e('⚠️ configs 目录创建失败: $e', tag: 'SingBox');
//
@ -354,8 +368,10 @@ class KRSingBoxImp {
// extensionData.db (Windows特定)
if (Platform.isWindows) {
try {
final extensionDataDbPath = p.join(finalDataDir.path, 'extensionData.db');
KRLogUtil.kr_i('👉 准备处理 extensionData.db 路径: $extensionDataDbPath', tag: 'SingBox');
final extensionDataDbPath =
p.join(finalDataDir.path, 'extensionData.db');
KRLogUtil.kr_i('👉 准备处理 extensionData.db 路径: $extensionDataDbPath',
tag: 'SingBox');
// extensionData.db
final extensionDataParent = Directory(p.dirname(extensionDataDbPath));
@ -365,7 +381,8 @@ class KRSingBoxImp {
}
//
final testFile = File(p.join(extensionDataParent.path, '.test_extension'));
final testFile =
File(p.join(extensionDataParent.path, '.test_extension'));
await testFile.writeAsString('test');
await testFile.delete();
KRLogUtil.kr_i('✅ extensionData 目录权限验证通过', tag: 'SingBox');
@ -377,18 +394,26 @@ class KRSingBoxImp {
KRLogUtil.kr_i('✅ 目录创建完成', tag: 'SingBox');
KRLogUtil.kr_i('开始设置 SingBox', tag: 'SingBox');
KRLogUtil.kr_i(' - baseDir: ${kr_configDics.baseDir.path}', tag: 'SingBox');
KRLogUtil.kr_i(' - workingDir: ${kr_configDics.workingDir.path}', tag: 'SingBox');
KRLogUtil.kr_i(' - tempDir: ${kr_configDics.tempDir.path}', tag: 'SingBox');
KRLogUtil.kr_i(' - data 目录: ${p.join(kr_configDics.workingDir.path, "data")}', tag: 'SingBox');
KRLogUtil.kr_i(' - data 目录存在: ${finalDataDir.existsSync()}', tag: 'SingBox');
KRLogUtil.kr_i(' - baseDir: ${kr_configDics.baseDir.path}',
tag: 'SingBox');
KRLogUtil.kr_i(' - workingDir: ${kr_configDics.workingDir.path}',
tag: 'SingBox');
KRLogUtil.kr_i(' - tempDir: ${kr_configDics.tempDir.path}',
tag: 'SingBox');
KRLogUtil.kr_i(
' - data 目录: ${p.join(kr_configDics.workingDir.path, "data")}',
tag: 'SingBox');
KRLogUtil.kr_i(' - data 目录存在: ${finalDataDir.existsSync()}',
tag: 'SingBox');
// Windows data
if (Platform.isWindows && finalDataDir.existsSync()) {
try {
final dataContents = finalDataDir.listSync();
if (dataContents.isNotEmpty) {
KRLogUtil.kr_i(' - data 目录现有文件: ${dataContents.map((e) => e.path.split(Platform.pathSeparator).last).join(", ")}', tag: 'SingBox');
KRLogUtil.kr_i(
' - data 目录现有文件: ${dataContents.map((e) => e.path.split(Platform.pathSeparator).last).join(", ")}',
tag: 'SingBox');
} else {
KRLogUtil.kr_i(' - data 目录为空', tag: 'SingBox');
}
@ -397,19 +422,23 @@ class KRSingBoxImp {
}
}
KRLogUtil.kr_i(' - libcore 将通过 os.Chdir() 切换到: ${kr_configDics.workingDir.path}', tag: 'SingBox');
KRLogUtil.kr_i(
' - libcore 将通过 os.Chdir() 切换到: ${kr_configDics.workingDir.path}',
tag: 'SingBox');
KRLogUtil.kr_i(' - 然后使用相对路径 "./data" 访问数据库', tag: 'SingBox');
// Windows
if (Platform.isWindows) {
final workingPath = kr_configDics.workingDir.path;
if (workingPath.contains('/')) {
KRLogUtil.kr_e('⚠️ 警告Windows 路径包含正斜杠,可能导致问题: $workingPath', tag: 'SingBox');
KRLogUtil.kr_e('⚠️ 警告Windows 路径包含正斜杠,可能导致问题: $workingPath',
tag: 'SingBox');
}
// 使Windows
final normalizedPath = workingPath.replaceAll('/', '\\');
if (normalizedPath != workingPath) {
KRLogUtil.kr_e('⚠️ 路径格式可能需要规范化: $workingPath -> $normalizedPath', tag: 'SingBox');
KRLogUtil.kr_e('⚠️ 路径格式可能需要规范化: $workingPath -> $normalizedPath',
tag: 'SingBox');
}
}
@ -418,11 +447,11 @@ class KRSingBoxImp {
KRLogUtil.kr_i('📡 开始调用 setup() 注册 FFI 端口', tag: 'SingBox');
final setupResult = await kr_singBox.setup(kr_configDics, false).run();
setupResult.match(
(error) {
(error) {
KRLogUtil.kr_e('❌ setup() 失败: $error', tag: 'SingBox');
throw Exception('FFI setup 失败: $error');
},
(_) {
(_) {
KRLogUtil.kr_i('✅ setup() 成功FFI 端口已注册', tag: 'SingBox');
},
);
@ -457,19 +486,25 @@ class KRSingBoxImp {
KRLogUtil.kr_e('🔍 Windows 路径诊断信息:', tag: 'SingBox');
KRLogUtil.kr_e(' - workingDir: ${workingDir.path}', tag: 'SingBox');
KRLogUtil.kr_e(' - workingDir 存在: ${workingDir.existsSync()}', tag: 'SingBox');
KRLogUtil.kr_e(' - workingDir 存在: ${workingDir.existsSync()}',
tag: 'SingBox');
KRLogUtil.kr_e(' - data 目录: ${dataDir.path}', tag: 'SingBox');
KRLogUtil.kr_e(' - data 目录存在: ${dataDir.existsSync()}', tag: 'SingBox');
KRLogUtil.kr_e(' - data 目录存在: ${dataDir.existsSync()}',
tag: 'SingBox');
KRLogUtil.kr_e(' - configs 目录: ${configsDir.path}', tag: 'SingBox');
KRLogUtil.kr_e(' - configs 目录存在: ${configsDir.existsSync()}', tag: 'SingBox');
KRLogUtil.kr_e(' - configs 目录存在: ${configsDir.existsSync()}',
tag: 'SingBox');
//
if (workingDir.existsSync()) {
try {
final contents = workingDir.listSync();
KRLogUtil.kr_e(' - workingDir 内容: ${contents.map((e) => "${e.path.split(Platform.pathSeparator).last}${e is Directory ? "/" : ""}").join(", ")}', tag: 'SingBox');
KRLogUtil.kr_e(
' - workingDir 内容: ${contents.map((e) => "${e.path.split(Platform.pathSeparator).last}${e is Directory ? "/" : ""}").join(", ")}',
tag: 'SingBox');
} catch (listErr) {
KRLogUtil.kr_e(' - 无法列出 workingDir 内容: $listErr', tag: 'SingBox');
KRLogUtil.kr_e(' - 无法列出 workingDir 内容: $listErr',
tag: 'SingBox');
}
}
} catch (diagErr) {
@ -492,7 +527,8 @@ class KRSingBoxImp {
Future<void> _kr_extractGeositeFiles() async {
try {
// geosite
final geositeDir = Directory(p.join(kr_configDics.workingDir.path, 'geosite'));
final geositeDir =
Directory(p.join(kr_configDics.workingDir.path, 'geosite'));
if (!geositeDir.existsSync()) {
await geositeDir.create(recursive: true);
KRLogUtil.kr_i('✅ 已创建 geosite 目录: ${geositeDir.path}', tag: 'SingBox');
@ -509,7 +545,8 @@ class KRSingBoxImp {
//
if (targetFile.existsSync()) {
final fileSize = await targetFile.length();
KRLogUtil.kr_i('📄 $filename 已存在 (${fileSize} bytes), 跳过', tag: 'SingBox');
KRLogUtil.kr_i('📄 $filename 已存在 (${fileSize} bytes), 跳过',
tag: 'SingBox');
continue;
}
@ -522,7 +559,8 @@ class KRSingBoxImp {
await targetFile.writeAsBytes(bytes);
final writtenSize = await targetFile.length();
KRLogUtil.kr_i('✅ 提取成功: $filename (${writtenSize} bytes)', tag: 'SingBox');
KRLogUtil.kr_i('✅ 提取成功: $filename (${writtenSize} bytes)',
tag: 'SingBox');
}
KRLogUtil.kr_i('🎉 所有 geosite 文件提取完成', tag: 'SingBox');
@ -542,24 +580,27 @@ class KRSingBoxImp {
// 🔧 region 'other'libcore
final String effectiveRegion;
if (kr_connectionType.value == KRConnectionType.global) {
effectiveRegion = 'other'; //
effectiveRegion = 'other'; //
KRLogUtil.kr_i('🌐 [全局代理模式] region 设为 other所有流量走代理', tag: 'SingBox');
} else {
effectiveRegion = KRCountryUtil.kr_getCurrentCountryCode(); // 使
effectiveRegion =
KRCountryUtil.kr_getCurrentCountryCode(); // 使
KRLogUtil.kr_i('✅ [智能代理模式] region 设为 $effectiveRegion', tag: 'SingBox');
}
final op = {
"region": effectiveRegion, // 🔧 region
"block-ads": false, // hiddify-app: 广
"region": effectiveRegion, // 🔧 region
"block-ads": false, // hiddify-app: 广
"use-xray-core-when-possible": false,
"execute-config-as-is": true, // 🔧 使 libcore
"log-level": "info", // 使 info warn
"execute-config-as-is": true, // 🔧 使 libcore
"log-level": "info", // 使 info warn
"resolve-destination": false,
"ipv6-mode": "ipv4_only", // hiddify-app: 使 IPv4 (: ipv4_only, prefer_ipv4, prefer_ipv6, ipv6_only)
"remote-dns-address": "https://dns.google/dns-query", // 使 Google DoH DNS
"ipv6-mode":
"ipv4_only", // hiddify-app: 使 IPv4 (: ipv4_only, prefer_ipv4, prefer_ipv6, ipv6_only)
"remote-dns-address":
"https://dns.google/dns-query", // 使 Google DoH DNS
"remote-dns-domain-strategy": "prefer_ipv4",
"direct-dns-address": "local", // 使 DNS
"direct-dns-address": "local", // 使 DNS
"direct-dns-domain-strategy": "prefer_ipv4",
"mixed-port": kr_port,
"tproxy-port": kr_port,
@ -567,14 +608,15 @@ class KRSingBoxImp {
"tun-implementation": "gvisor",
"mtu": 9000,
"strict-route": true,
"connection-test-url": "http://cp.cloudflare.com", // hiddify-app: 使 Cloudflare
"connection-test-url":
"http://cp.cloudflare.com", // hiddify-app: 使 Cloudflare
"url-test-interval": 30,
"enable-clash-api": true,
"clash-api-port": 36756,
"enable-tun": Platform.isIOS || Platform.isAndroid,
"enable-tun-service": false,
"set-system-proxy":
Platform.isWindows || Platform.isLinux || Platform.isMacOS,
Platform.isWindows || Platform.isLinux || Platform.isMacOS,
"bypass-lan": false,
"allow-connection-from-lan": false,
"enable-fake-dns": false,
@ -628,11 +670,14 @@ class KRSingBoxImp {
// 🔧
final rules = op["rules"] as List?;
KRLogUtil.kr_i('✅ HiddifyOptions 已生成,包含 ${rules?.length ?? 0} 条自定义路由规则', tag: 'SingBox');
KRLogUtil.kr_i('✅ HiddifyOptions 已生成,包含 ${rules?.length ?? 0} 条自定义路由规则',
tag: 'SingBox');
if (rules != null && rules.isNotEmpty) {
for (var rule in rules) {
final ruleMap = rule as Map<String, dynamic>;
KRLogUtil.kr_i(' - 规则: domains=${ruleMap["domains"]}, outbound=${ruleMap["outbound"]}', tag: 'SingBox');
KRLogUtil.kr_i(
' - 规则: domains=${ruleMap["domains"]}, outbound=${ruleMap["outbound"]}',
tag: 'SingBox');
}
}
@ -641,21 +686,16 @@ class KRSingBoxImp {
List<Map<String, dynamic>> _kr_buildHiddifyRules() {
final rules = <Map<String, dynamic>>[];
rules.add({
"domains": "domain:api.hifast.biz",
"outbound": "bypass"
});
rules.add({"domains": "domain:api.hifast.biz", "outbound": "bypass"});
final nodeDomains = _kr_collectNodeDomains();
for (final d in nodeDomains) {
rules.add({
"domains": "domain:$d",
"outbound": "bypass"
});
rules.add({"domains": "domain:$d", "outbound": "bypass"});
}
KRLogUtil.kr_i('✅ 节点域名白名单数量: ${nodeDomains.length}', tag: 'SingBox');
KRLogUtil.kr_i('✅ 节点域名白名单集合: ${jsonEncode(nodeDomains.toList())}', tag: 'SingBox');
KRLogUtil.kr_i('✅ 节点域名白名单集合: ${jsonEncode(nodeDomains.toList())}',
tag: 'SingBox');
return rules;
}
@ -664,21 +704,25 @@ class KRSingBoxImp {
void addFromOutbound(Map<String, dynamic> o) {
final server = o['server']?.toString();
if (server != null && server.isNotEmpty && InternetAddress.tryParse(server) == null) {
if (server != null &&
server.isNotEmpty &&
InternetAddress.tryParse(server) == null) {
set.add(server.toLowerCase());
}
final tls = o['tls'];
if (tls is Map) {
final sni = tls['server_name']?.toString();
if (sni != null && sni.isNotEmpty && InternetAddress.tryParse(sni) == null) {
if (sni != null &&
sni.isNotEmpty &&
InternetAddress.tryParse(sni) == null) {
set.add(sni.toLowerCase());
}
}
}
for (final g in kr_outbounds) {
addFromOutbound(g);
addFromOutbound(g);
}
return set;
@ -706,7 +750,7 @@ class KRSingBoxImp {
_kr_subscriptions.add(
kr_singBox.watchStatus().listen(
(status) {
(status) {
if (kDebugMode) {
print('🔵 收到 Native 状态更新: ${status.runtimeType}');
}
@ -744,7 +788,7 @@ class KRSingBoxImp {
// try-catch
final stream = kr_singBox.watchStats();
final subscription = stream.listen(
(stats) {
(stats) {
kr_stats.value = stats;
},
onError: (error) {
@ -769,7 +813,7 @@ class KRSingBoxImp {
_kr_subscriptions.add(
kr_singBox.watchActiveGroups().listen(
(groups) {
(groups) {
print('[watchActiveGroups] 📡 收到活动组更新,数量: ${groups.length}');
KRLogUtil.kr_i('📡 收到活动组更新,数量: ${groups.length}', tag: 'SingBox');
kr_activeGroups.value = groups;
@ -777,10 +821,14 @@ class KRSingBoxImp {
//
for (int i = 0; i < groups.length; i++) {
final group = groups[i];
KRLogUtil.kr_i('📋 活动组[$i]: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'SingBox');
KRLogUtil.kr_i(
'📋 活动组[$i]: tag=${group.tag}, type=${group.type}, selected=${group.selected}',
tag: 'SingBox');
for (int j = 0; j < group.items.length; j++) {
final item = group.items[j];
KRLogUtil.kr_i(' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', tag: 'SingBox');
KRLogUtil.kr_i(
' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}',
tag: 'SingBox');
}
}
@ -796,13 +844,14 @@ class KRSingBoxImp {
_kr_subscriptions.add(
kr_singBox.watchGroups().listen(
(groups) {
(groups) {
print('[watchGroups] 📡 收到所有组更新,数量: ${groups.length}');
kr_allGroups.value = groups;
//
for (int i = 0; i < groups.length; i++) {
final group = groups[i];
print('[watchGroups] 组[$i]: tag=${group.tag}, type=${group.type}, selected=${group.selected}');
print(
'[watchGroups] 组[$i]: tag=${group.tag}, type=${group.type}, selected=${group.selected}');
}
},
onError: (error) {
@ -812,7 +861,8 @@ class KRSingBoxImp {
cancelOnError: false,
),
);
print('[_kr_subscribeToGroups] ✅ 分组数据流订阅完成,当前订阅数: ${_kr_subscriptions.length}');
print(
'[_kr_subscribeToGroups] ✅ 分组数据流订阅完成,当前订阅数: ${_kr_subscriptions.length}');
}
///
@ -824,7 +874,7 @@ class KRSingBoxImp {
// "select"
final selectGroup = kr_activeGroups.firstWhere(
(group) => group.tag == 'select',
(group) => group.tag == 'select',
orElse: () => throw Exception('未找到 "select" 选择器组'),
);
@ -853,7 +903,8 @@ class KRSingBoxImp {
throw Exception('节点切换失败:实际选中 ${selectGroup.selected},期望 $targetTag');
}
KRLogUtil.kr_i('✅ 节点切换验证成功: ${selectGroup.selected} == $targetTag', tag: 'SingBox');
KRLogUtil.kr_i('✅ 节点切换验证成功: ${selectGroup.selected} == $targetTag',
tag: 'SingBox');
} catch (e) {
KRLogUtil.kr_e('❌ 节点验证异常: $e', tag: 'SingBox');
//
@ -864,11 +915,11 @@ class KRSingBoxImp {
///
/// command.sock
Future<void> _kr_selectOutboundWithRetry(
String groupTag,
String outboundTag, {
int maxAttempts = 3,
int initialDelay = 100,
}) async {
String groupTag,
String outboundTag, {
int maxAttempts = 3,
int initialDelay = 100,
}) async {
int attempt = 0;
int delay = initialDelay;
@ -931,7 +982,8 @@ class KRSingBoxImp {
try {
// command.sock 访
final socketFile = File(p.join(kr_configDics.baseDir.path, 'command.sock'));
final socketFile =
File(p.join(kr_configDics.baseDir.path, 'command.sock'));
KRLogUtil.kr_i('🔍 检查 socket 文件: ${socketFile.path}', tag: 'SingBox');
if (!socketFile.existsSync()) {
@ -949,7 +1001,7 @@ class KRSingBoxImp {
//
try {
KRLogUtil.kr_i('📊 订阅统计数据流...', tag: 'SingBox');
await Future.delayed(Duration.zero); // UI 线
await Future.delayed(Duration.zero); // UI 线
_kr_subscribeToStats();
statsSubscribed = true;
KRLogUtil.kr_i('✅ 统计数据流订阅成功', tag: 'SingBox');
@ -959,7 +1011,7 @@ class KRSingBoxImp {
try {
KRLogUtil.kr_i('📋 订阅分组数据流...', tag: 'SingBox');
await Future.delayed(Duration.zero); // UI 线
await Future.delayed(Duration.zero); // UI 线
_kr_subscribeToGroups();
groupsSubscribed = true;
KRLogUtil.kr_i('✅ 分组数据流订阅成功', tag: 'SingBox');
@ -981,7 +1033,8 @@ class KRSingBoxImp {
throw Exception('订阅列表为空command client 未成功连接');
}
KRLogUtil.kr_i('✅ 命令客户端初始化成功,活跃订阅数: ${_kr_subscriptions.length}', tag: 'SingBox');
KRLogUtil.kr_i('✅ 命令客户端初始化成功,活跃订阅数: ${_kr_subscriptions.length}',
tag: 'SingBox');
return;
} catch (e, stackTrace) {
//
@ -1024,7 +1077,8 @@ class KRSingBoxImp {
await Future.delayed(const Duration(milliseconds: 1500));
// socket
final socketFile = File(p.join(kr_configDics.baseDir.path, 'command.sock'));
final socketFile =
File(p.join(kr_configDics.baseDir.path, 'command.sock'));
if (!socketFile.existsSync()) {
KRLogUtil.kr_w('⚠️ command.sock 尚未创建,预连接取消', tag: 'SingBox');
return;
@ -1047,7 +1101,6 @@ class KRSingBoxImp {
} catch (e) {
KRLogUtil.kr_w('⚠️ 分组流预订阅失败正常UI 调用时会重试): $e', tag: 'SingBox');
}
} catch (e) {
//
KRLogUtil.kr_w('⚠️ 后台预连接任务失败(不影响正常使用): $e', tag: 'SingBox');
@ -1061,7 +1114,8 @@ class KRSingBoxImp {
Future<void> _kr_ensureCommandClientInitialized() async {
// , command client
if (_kr_subscriptions.isNotEmpty) {
KRLogUtil.kr_i('✅ Command client 已初始化(订阅数: ${_kr_subscriptions.length}', tag: 'SingBox');
KRLogUtil.kr_i('✅ Command client 已初始化(订阅数: ${_kr_subscriptions.length}',
tag: 'SingBox');
return;
}
@ -1096,7 +1150,8 @@ class KRSingBoxImp {
// command client
await Future.delayed(const Duration(milliseconds: 2000));
final savedNode = await KRSecureStorage().kr_readData(key: _keySelectedNode);
final savedNode =
await KRSecureStorage().kr_readData(key: _keySelectedNode);
if (savedNode != null && savedNode.isNotEmpty && savedNode != 'auto') {
KRLogUtil.kr_i('🔄 恢复用户选择的节点: $savedNode', tag: 'SingBox');
@ -1104,7 +1159,8 @@ class KRSingBoxImp {
await _kr_selectOutboundWithRetry("select", savedNode);
KRLogUtil.kr_i('✅ 节点恢复完成', tag: 'SingBox');
} catch (e) {
KRLogUtil.kr_w('⚠️ 节点恢复失败(可能 command client 未就绪): $e', tag: 'SingBox');
KRLogUtil.kr_w('⚠️ 节点恢复失败(可能 command client 未就绪): $e',
tag: 'SingBox');
}
} else {
KRLogUtil.kr_i(' 使用默认节点选择 (auto)', tag: 'SingBox');
@ -1151,7 +1207,8 @@ class KRSingBoxImp {
KRLogUtil.kr_i(' - type: ${outbound['type']}', tag: 'SingBox');
KRLogUtil.kr_i(' - tag: ${outbound['tag']}', tag: 'SingBox');
KRLogUtil.kr_i(' - server: ${outbound['server']}', tag: 'SingBox');
KRLogUtil.kr_i(' - server_port: ${outbound['server_port']}', tag: 'SingBox');
KRLogUtil.kr_i(' - server_port: ${outbound['server_port']}',
tag: 'SingBox');
if (outbound['method'] != null) {
KRLogUtil.kr_i(' - method: ${outbound['method']}', tag: 'SingBox');
}
@ -1159,10 +1216,14 @@ class KRSingBoxImp {
KRLogUtil.kr_i(' - interval: ${outbound['interval']}', tag: 'SingBox');
}
if (outbound['password'] != null) {
KRLogUtil.kr_i(' - password: ${outbound['password']?.toString().substring(0, 8)}...', tag: 'SingBox');
KRLogUtil.kr_i(
' - password: ${outbound['password']?.toString().substring(0, 8)}...',
tag: 'SingBox');
}
if (outbound['uuid'] != null) {
KRLogUtil.kr_i(' - uuid: ${outbound['uuid']?.toString().substring(0, 8)}...', tag: 'SingBox');
KRLogUtil.kr_i(
' - uuid: ${outbound['uuid']?.toString().substring(0, 8)}...',
tag: 'SingBox');
}
KRLogUtil.kr_i(' - 完整配置: ${jsonEncode(outbound)}', tag: 'SingBox');
}
@ -1171,22 +1232,21 @@ class KRSingBoxImp {
kr_outbounds = outbounds.where((outbound) {
final type = outbound['type'];
if (type == 'hysteria2' || type == 'hysteria') {
KRLogUtil.kr_w('⚠️ 跳过 Hysteria2 节点: ${outbound['tag']} (libcore bug)', tag: 'SingBox');
KRLogUtil.kr_w('⚠️ 跳过 Hysteria2 节点: ${outbound['tag']} (libcore bug)',
tag: 'SingBox');
return false;
}
return true;
}).toList();
KRLogUtil.kr_i('✅ 过滤后节点数量: ${kr_outbounds.length}/${outbounds.length}', tag: 'SingBox');
KRLogUtil.kr_i('✅ 过滤后节点数量: ${kr_outbounds.length}/${outbounds.length}',
tag: 'SingBox');
// 🔧 SingBox
// {"outbounds": [...]}, libcore
//
final Map<String, dynamic> fullConfig = {
"log": {
"level": "debug",
"timestamp": true
},
"log": {"level": "debug", "timestamp": true},
"dns": {
"servers": [
{
@ -1194,13 +1254,9 @@ class KRSingBoxImp {
"address": "https://1.1.1.1/dns-query",
"address_resolver": "dns-direct"
},
{
"tag": "dns-direct",
"address": "local",
"detour": "direct"
}
{"tag": "dns-direct", "address": "local", "detour": "direct"}
],
"rules": _kr_buildDnsRules(), // 使 DNS
"rules": _kr_buildDnsRules(), // 使 DNS
"final": "dns-remote",
"strategy": "prefer_ipv4"
},
@ -1222,26 +1278,18 @@ class KRSingBoxImp {
"type": "selector",
"tag": "proxy",
"outbounds": kr_outbounds.map((o) => o['tag'] as String).toList(),
"default": kr_outbounds.isNotEmpty ? kr_outbounds[0]['tag'] : "direct",
"default":
kr_outbounds.isNotEmpty ? kr_outbounds[0]['tag'] : "direct",
},
...kr_outbounds,
{
"type": "direct",
"tag": "direct"
},
{
"type": "block",
"tag": "block"
},
{
"type": "dns",
"tag": "dns-out"
}
{"type": "direct", "tag": "direct"},
{"type": "block", "tag": "block"},
{"type": "dns", "tag": "dns-out"}
],
"route": {
"rules": _kr_buildRouteRules(), // 使
"rule_set": _kr_buildRuleSets(), // 使
"final": "proxy", // 🔧 使 selector
"rules": _kr_buildRouteRules(), // 使
"rule_set": _kr_buildRuleSets(), // 使
"final": "proxy", // 🔧 使 selector
"auto_detect_interface": true
}
};
@ -1252,7 +1300,9 @@ class KRSingBoxImp {
KRLogUtil.kr_i('📄 完整配置文件长度: ${mapStr.length}', tag: 'SingBox');
KRLogUtil.kr_i('📄 Outbounds 数量: ${kr_outbounds.length}', tag: 'SingBox');
KRLogUtil.kr_i('📄 配置前800字符:\n${mapStr.substring(0, mapStr.length > 800 ? 800 : mapStr.length)}', tag: 'SingBox');
KRLogUtil.kr_i(
'📄 配置前800字符:\n${mapStr.substring(0, mapStr.length > 800 ? 800 : mapStr.length)}',
tag: 'SingBox');
await file.writeAsString(mapStr);
await temp.writeAsString(mapStr);
@ -1276,7 +1326,8 @@ class KRSingBoxImp {
// 🔧 "智能代理"
// "全局代理"使
if (kr_connectionType.value == KRConnectionType.rule && currentCountryCode != 'other') {
if (kr_connectionType.value == KRConnectionType.rule &&
currentCountryCode != 'other') {
rules.add({
"rule_set": [
"geoip-$currentCountryCode",
@ -1285,7 +1336,8 @@ class KRSingBoxImp {
"server": "dns-direct"
});
KRLogUtil.kr_i('✅ [智能代理模式] 添加 DNS 规则: $currentCountryCode 域名使用直连 DNS', tag: 'SingBox');
KRLogUtil.kr_i('✅ [智能代理模式] 添加 DNS 规则: $currentCountryCode 域名使用直连 DNS',
tag: 'SingBox');
} else if (kr_connectionType.value == KRConnectionType.global) {
KRLogUtil.kr_i('🌐 [全局代理模式] 跳过国家 DNS 规则所有DNS查询走代理', tag: 'SingBox');
}
@ -1299,10 +1351,7 @@ class KRSingBoxImp {
final rules = <Map<String, dynamic>>[];
// : DNS dns-out
rules.add({
"protocol": "dns",
"outbound": "dns-out"
});
rules.add({"protocol": "dns", "outbound": "dns-out"});
//
// rules.add({
@ -1314,7 +1363,8 @@ class KRSingBoxImp {
// 🔧 "智能代理"
// "全局代理"使
if (kr_connectionType.value == KRConnectionType.rule && currentCountryCode != 'other') {
if (kr_connectionType.value == KRConnectionType.rule &&
currentCountryCode != 'other') {
rules.add({
"rule_set": [
"geoip-$currentCountryCode",
@ -1323,7 +1373,8 @@ class KRSingBoxImp {
"outbound": "direct"
});
KRLogUtil.kr_i('✅ [智能代理模式] 添加路由规则: $currentCountryCode IP/域名直连', tag: 'SingBox');
KRLogUtil.kr_i('✅ [智能代理模式] 添加路由规则: $currentCountryCode IP/域名直连',
tag: 'SingBox');
} else if (kr_connectionType.value == KRConnectionType.global) {
KRLogUtil.kr_i('🌐 [全局代理模式] 跳过国家路由规则,所有流量走代理', tag: 'SingBox');
}
@ -1338,10 +1389,13 @@ class KRSingBoxImp {
// 🔧 "智能代理"
// "全局代理"使
if (kr_connectionType.value == KRConnectionType.rule && currentCountryCode != 'other') {
if (kr_connectionType.value == KRConnectionType.rule &&
currentCountryCode != 'other') {
//
final geoipFile = File(p.join(kr_configDics.workingDir.path, 'geosite', 'geoip-$currentCountryCode.srs'));
final geositeFile = File(p.join(kr_configDics.workingDir.path, 'geosite', 'geosite-$currentCountryCode.srs'));
final geoipFile = File(p.join(kr_configDics.workingDir.path, 'geosite',
'geoip-$currentCountryCode.srs'));
final geositeFile = File(p.join(kr_configDics.workingDir.path, 'geosite',
'geosite-$currentCountryCode.srs'));
if (geoipFile.existsSync() && geositeFile.existsSync()) {
// 使
@ -1349,7 +1403,7 @@ class KRSingBoxImp {
"type": "local",
"tag": "geoip-$currentCountryCode",
"format": "binary",
"path": "./geosite/geoip-$currentCountryCode.srs" // workingDir
"path": "./geosite/geoip-$currentCountryCode.srs" // workingDir
});
ruleSets.add({
@ -1360,19 +1414,25 @@ class KRSingBoxImp {
});
KRLogUtil.kr_i('✅ 使用本地规则集: $currentCountryCode', tag: 'SingBox');
KRLogUtil.kr_i(' - geoip: ./geosite/geoip-$currentCountryCode.srs', tag: 'SingBox');
KRLogUtil.kr_i(' - geosite: ./geosite/geosite-$currentCountryCode.srs', tag: 'SingBox');
KRLogUtil.kr_i(' - geoip: ./geosite/geoip-$currentCountryCode.srs',
tag: 'SingBox');
KRLogUtil.kr_i(
' - geosite: ./geosite/geosite-$currentCountryCode.srs',
tag: 'SingBox');
} else {
// ,使
KRLogUtil.kr_w('⚠️ 本地规则集不存在,使用远程规则集', tag: 'SingBox');
KRLogUtil.kr_w(' - geoip 文件存在: ${geoipFile.existsSync()}', tag: 'SingBox');
KRLogUtil.kr_w(' - geosite 文件存在: ${geositeFile.existsSync()}', tag: 'SingBox');
KRLogUtil.kr_w(' - geoip 文件存在: ${geoipFile.existsSync()}',
tag: 'SingBox');
KRLogUtil.kr_w(' - geosite 文件存在: ${geositeFile.existsSync()}',
tag: 'SingBox');
ruleSets.add({
"type": "remote",
"tag": "geoip-$currentCountryCode",
"format": "binary",
"url": "https://raw.githubusercontent.com/hiddify/hiddify-geo/rule-set/country/geoip-$currentCountryCode.srs",
"url":
"https://raw.githubusercontent.com/hiddify/hiddify-geo/rule-set/country/geoip-$currentCountryCode.srs",
"download_detour": "direct",
"update_interval": "7d"
});
@ -1381,7 +1441,8 @@ class KRSingBoxImp {
"type": "remote",
"tag": "geosite-$currentCountryCode",
"format": "binary",
"url": "https://raw.githubusercontent.com/hiddify/hiddify-geo/rule-set/country/geosite-$currentCountryCode.srs",
"url":
"https://raw.githubusercontent.com/hiddify/hiddify-geo/rule-set/country/geosite-$currentCountryCode.srs",
"download_detour": "direct",
"update_interval": "7d"
});
@ -1418,7 +1479,8 @@ class KRSingBoxImp {
if (Platform.isWindows && !_dnsBackedUp) {
KRLogUtil.kr_i('🪟 Windows 平台,首次启动备份 DNS 设置...', tag: 'SingBox');
try {
final backupSuccess = await KRWindowsDnsUtil.instance.kr_backupDnsSettings();
final backupSuccess =
await KRWindowsDnsUtil.instance.kr_backupDnsSettings();
if (backupSuccess) {
_dnsBackedUp = true; //
KRLogUtil.kr_i('✅ Windows DNS 备份成功', tag: 'SingBox');
@ -1449,11 +1511,11 @@ class KRSingBoxImp {
final oOption = SingboxConfigOption.fromJson(_getConfigOption());
final changeResult = await kr_singBox.changeOptions(oOption).run();
changeResult.match(
(error) {
(error) {
KRLogUtil.kr_e('❌ changeOptions() 失败: $error', tag: 'SingBox');
throw Exception('初始化 HiddifyOptions 失败: $error');
},
(_) {
(_) {
KRLogUtil.kr_i('✅ HiddifyOptions 初始化成功', tag: 'SingBox');
},
);
@ -1463,7 +1525,9 @@ class KRSingBoxImp {
if (await configFile.exists()) {
final configContent = await configFile.readAsString();
KRLogUtil.kr_i('📄 配置文件内容长度: ${configContent.length}', tag: 'SingBox');
KRLogUtil.kr_i('📄 配置文件前500字符: ${configContent.substring(0, configContent.length > 500 ? 500 : configContent.length)}', tag: 'SingBox');
KRLogUtil.kr_i(
'📄 配置文件前500字符: ${configContent.substring(0, configContent.length > 500 ? 500 : configContent.length)}',
tag: 'SingBox');
} else {
KRLogUtil.kr_w('⚠️ 配置文件不存在: $_cutPath', tag: 'SingBox');
}
@ -1472,7 +1536,7 @@ class KRSingBoxImp {
_kr_subscribeToStatus();
await kr_singBox.start(_cutPath, kr_configName, false).map(
(r) {
(r) {
KRLogUtil.kr_i('✅ SingBox 启动成功', tag: 'SingBox');
},
).mapLeft((err) {
@ -1528,7 +1592,8 @@ class KRSingBoxImp {
// 🔧
try {
final selectedNode = await KRSecureStorage().kr_readData(key: _keySelectedNode);
final selectedNode =
await KRSecureStorage().kr_readData(key: _keySelectedNode);
if (selectedNode != null && selectedNode.isNotEmpty) {
KRLogUtil.kr_i('🎯 恢复用户选择的节点: $selectedNode', tag: 'SingBox');
if (kDebugMode) {
@ -1588,7 +1653,7 @@ class KRSingBoxImp {
// 🔧 10 Windows DNS
try {
await kr_singBox.stop().run().timeout(
const Duration(seconds: 10), // 3 10
const Duration(seconds: 10), // 3 10
onTimeout: () {
KRLogUtil.kr_w('⚠️ 停止操作超时(10秒),强制继续', tag: 'SingBox');
return const Left('timeout');
@ -1638,7 +1703,8 @@ class KRSingBoxImp {
try {
// DNS
final restoreSuccess = await KRWindowsDnsUtil.instance.kr_restoreDnsSettings();
final restoreSuccess =
await KRWindowsDnsUtil.instance.kr_restoreDnsSettings();
if (restoreSuccess) {
KRLogUtil.kr_i('✅ Windows DNS 恢复成功', tag: 'SingBox');
} else {
@ -1765,9 +1831,9 @@ class KRSingBoxImp {
mode = KRCountryUtil.kr_getCurrentCountryCode();
KRLogUtil.kr_i('🎯 切换到规则代理模式: $mode', tag: 'SingBox');
break;
// case KRConnectionType.direct:
// mode = "direct";
// break;
// case KRConnectionType.direct:
// mode = "direct";
// break;
}
oOption["region"] = mode;
KRLogUtil.kr_i('📝 更新 region 配置: $mode', tag: 'SingBox');
@ -1842,6 +1908,25 @@ class KRSingBoxImp {
KRLogUtil.kr_i('🎯 [v2.1] 开始选择出站节点: $tag', tag: 'SingBox');
KRLogUtil.kr_i('📊 当前活动组数量: ${kr_activeGroups.length}', tag: 'SingBox');
//
if (kr_activeGroups.isEmpty) {
for (int i = 0; i < 20; i++) {
await Future.delayed(const Duration(milliseconds: 100));
if (kr_activeGroups.isNotEmpty) break;
}
if (kr_activeGroups.isEmpty) {
KRLogUtil.kr_w('⚠️ 活动组为空,跳过即时切换,仅保存选择: $tag', tag: 'SingBox');
try {
await KRSecureStorage()
.kr_saveData(key: _keySelectedNode, value: tag);
KRLogUtil.kr_i('✅ 节点选择已保存: $tag', tag: 'SingBox');
} catch (e) {
KRLogUtil.kr_e('❌ 保存节点选择失败: $e', tag: 'SingBox');
}
return;
}
}
// 🔧
KRLogUtil.kr_i('🔍 搜索目标节点 "$tag" 在活动组中...', tag: 'SingBox');
bool foundNode = false;
@ -1880,9 +1965,12 @@ class KRSingBoxImp {
// 🔧 使 group tag
// libcore selector组的tag是"proxy""select"
final selectorGroupTag = kr_activeGroups.any((g) => g.tag == 'select') ? 'select' : 'proxy';
KRLogUtil.kr_i('⏳ 调用 selectOutbound("$selectorGroupTag", "$tag")...', tag: 'SingBox');
await _kr_selectOutboundWithRetry(selectorGroupTag, tag, maxAttempts: 3, initialDelay: 50);
final selectorGroupTag =
kr_activeGroups.any((g) => g.tag == 'select') ? 'select' : 'proxy';
KRLogUtil.kr_i('⏳ 调用 selectOutbound("$selectorGroupTag", "$tag")...',
tag: 'SingBox');
await _kr_selectOutboundWithRetry(selectorGroupTag, tag,
maxAttempts: 3, initialDelay: 50);
KRLogUtil.kr_i('✅ 节点切换API调用完成: $tag', tag: 'SingBox');
// 🔧
@ -1900,13 +1988,14 @@ class KRSingBoxImp {
_nodeSelectionTimer?.cancel();
if (tag != 'auto') {
KRLogUtil.kr_i('🔁 启动节点选择监控,防止被 auto 覆盖', tag: 'SingBox');
_nodeSelectionTimer = Timer.periodic(const Duration(seconds: 20), (timer) {
_nodeSelectionTimer =
Timer.periodic(const Duration(seconds: 20), (timer) {
// 20
// 使 then/catchError UI
kr_singBox.selectOutbound("select", tag).run().then((result) {
result.match(
(error) => KRLogUtil.kr_w('🔁 定时器重选节点失败: $error', tag: 'SingBox'),
(_) => KRLogUtil.kr_d('🔁 定时器重选节点成功', tag: 'SingBox'),
(error) => KRLogUtil.kr_w('🔁 定时器重选节点失败: $error', tag: 'SingBox'),
(_) => KRLogUtil.kr_d('🔁 定时器重选节点成功', tag: 'SingBox'),
);
}).catchError((error) {
KRLogUtil.kr_w('🔁 定时器重选节点异常: $error', tag: 'SingBox');
@ -1935,10 +2024,14 @@ class KRSingBoxImp {
//
for (int i = 0; i < kr_activeGroups.length; i++) {
final group = kr_activeGroups[i];
KRLogUtil.kr_i('📋 活动组[$i]: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'SingBox');
KRLogUtil.kr_i(
'📋 活动组[$i]: tag=${group.tag}, type=${group.type}, selected=${group.selected}',
tag: 'SingBox');
for (int j = 0; j < group.items.length; j++) {
final item = group.items[j];
KRLogUtil.kr_i(' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', tag: 'SingBox');
KRLogUtil.kr_i(
' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}',
tag: 'SingBox');
}
}
@ -1954,10 +2047,14 @@ class KRSingBoxImp {
KRLogUtil.kr_i('🔄 测试后活动组状态检查:', tag: 'SingBox');
for (int i = 0; i < kr_activeGroups.length; i++) {
final group = kr_activeGroups[i];
KRLogUtil.kr_i('📋 活动组[$i]: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'SingBox');
KRLogUtil.kr_i(
'📋 活动组[$i]: tag=${group.tag}, type=${group.type}, selected=${group.selected}',
tag: 'SingBox');
for (int j = 0; j < group.items.length; j++) {
final item = group.items[j];
KRLogUtil.kr_i(' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', tag: 'SingBox');
KRLogUtil.kr_i(
' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}',
tag: 'SingBox');
}
}
} catch (e) {

View File

@ -5,7 +5,7 @@ class KRCommonUtil {
/// meesage: , toastPosition: , timeout:
static kr_showToast(String message,
{KRToastPosition toastPosition = KRToastPosition.center,
int timeout = 1500}) {
int timeout = 2500}) {
KRToast.kr_showToast(
message,
position: toastPosition,

View File

@ -9,12 +9,16 @@ class HiFixedScrollbar extends StatefulWidget {
/// 18
final double right;
///
final double thickness;
/// 30%
final Color thumbColor;
/// 15%
final Color trackColor;
/// 50
final double thumbHeight;
@ -36,6 +40,7 @@ class HiFixedScrollbar extends StatefulWidget {
class _HiFixedScrollbarState extends State<HiFixedScrollbar> {
double _thumbOffset = 0.0;
bool _hasScrolled = false;
@override
void initState() {
@ -58,6 +63,7 @@ class _HiFixedScrollbarState extends State<HiFixedScrollbar> {
setState(() {
_thumbOffset = (trackHeight * scrollRatio).clamp(0, trackHeight);
_hasScrolled = offset > 0;
});
}
@ -71,41 +77,44 @@ class _HiFixedScrollbarState extends State<HiFixedScrollbar> {
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (_, constraints) {
final showScrollbar = widget.isShowScrollbar &&
widget.controller.hasClients &&
widget.controller.position.maxScrollExtent > 0 &&
_hasScrolled;
return Stack(
children: [
//
widget.child,
if(widget.isShowScrollbar)
...[
//
Positioned(
right: widget.right.w,
top: 0,
bottom: 0,
child: Container(
width: widget.thickness.w,
decoration: BoxDecoration(
color: widget.trackColor,
borderRadius: BorderRadius.circular(4),
),
if (showScrollbar) ...[
//
Positioned(
right: widget.right.w,
top: 0,
bottom: 0,
child: Container(
width: widget.thickness.w,
decoration: BoxDecoration(
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),
),
//
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),
),
),
]
),
]
],
);
},

View File

@ -12,6 +12,7 @@ import flutter_udid
import package_info_plus
import path_provider_foundation
import screen_retriever_macos
import share_plus
import tray_manager
import url_launcher_macos
import webview_flutter_wkwebview
@ -25,6 +26,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
TrayManagerPlugin.register(with: registry.registrar(forPlugin: "TrayManagerPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin"))

View File

@ -254,6 +254,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
cross_file:
dependency: transitive
description:
name: cross_file
sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
url: "https://pub.dev"
source: hosted
version: "0.3.4+2"
crypto:
dependency: "direct main"
description:
@ -1049,10 +1057,10 @@ packages:
dependency: transitive
description:
name: mime
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
version: "1.0.6"
nm:
dependency: transitive
description:
@ -1413,6 +1421,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.2.0"
share_plus:
dependency: "direct main"
description:
name: share_plus
sha256: "3ef39599b00059db0990ca2e30fca0a29d8b37aae924d60063f8e0184cf20900"
url: "https://pub.dev"
source: hosted
version: "7.2.2"
share_plus_platform_interface:
dependency: transitive
description:
name: share_plus_platform_interface
sha256: "251eb156a8b5fa9ce033747d73535bf53911071f8d3b6f4f0b578505ce0d4496"
url: "https://pub.dev"
source: hosted
version: "3.4.0"
shelf:
dependency: transitive
description:

View File

@ -79,6 +79,7 @@ dependencies:
flutter_inappwebview: ^6.1.5 # 最新稳定版本
crisp_sdk: ^1.1.0 # 使用 crisp_sdk配合最新的 flutter_inappwebview
protocol_handler_windows: ^0.2.0
share_plus: ^7.2.2
# 国际化
slang: ^3.30.1
@ -302,4 +303,3 @@ ffigen:
headers:
entry-points:
- "libcore/bin/libcore.h"

View File

@ -12,6 +12,7 @@
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <protocol_handler_windows/protocol_handler_windows_plugin_c_api.h>
#include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
#include <share_plus/share_plus_windows_plugin_c_api.h>
#include <tray_manager/tray_manager_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h>
#include <window_manager/window_manager_plugin.h>
@ -29,6 +30,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("ProtocolHandlerWindowsPluginCApi"));
ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi"));
SharePlusWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
TrayManagerPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("TrayManagerPlugin"));
UrlLauncherWindowsRegisterWithRegistrar(

View File

@ -9,6 +9,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
permission_handler_windows
protocol_handler_windows
screen_retriever_windows
share_plus
tray_manager
url_launcher_windows
window_manager