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"> <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

View File

@ -26,6 +26,8 @@ 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
- 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 +44,7 @@ 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`)
- 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`)
@ -69,6 +72,8 @@ 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"
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 +92,7 @@ SPEC CHECKSUMS:
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
ReachabilitySwift: 32793e867593cfc1177f5d16491e3a197d2fccda ReachabilitySwift: 32793e867593cfc1177f5d16491e3a197d2fccda
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
webview_flutter_wkwebview: a4af96a051138e28e29f60101d094683b9f82188 webview_flutter_wkwebview: a4af96a051138e28e29f60101d094683b9f82188

View File

@ -758,11 +758,9 @@
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 Distribution"; CODE_SIGN_STYLE = Automatic;
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = NJRRF427XB;
"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 +786,6 @@
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;
@ -814,11 +811,9 @@
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_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Automatic;
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = NJRRF427XB;
"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;
@ -844,7 +839,6 @@
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;
@ -868,11 +862,9 @@
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 Distribution"; CODE_SIGN_STYLE = Automatic;
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = NJRRF427XB;
"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;
@ -898,7 +890,6 @@
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;
@ -977,11 +968,9 @@
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 Distribution"; CODE_SIGN_STYLE = Automatic;
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = NJRRF427XB;
"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;
@ -1013,7 +1002,6 @@
"PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = com.taw.hifastvpn; "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";
@ -1208,11 +1196,9 @@
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 Distribution"; CODE_SIGN_STYLE = Automatic;
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = NJRRF427XB;
"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";
@ -1244,7 +1230,6 @@
"PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = com.taw.hifastvpn; "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";
@ -1265,11 +1250,9 @@
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_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Automatic;
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = NJRRF427XB;
"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;
@ -1301,7 +1284,6 @@
"PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = com.taw.hifastvpn; "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";

View File

@ -1,6 +1,7 @@
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';
@ -15,52 +16,57 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
@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,
@ -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(() { Obx(() {
final account = KRAppRunData.getInstance() final account = KRAppRunData.getInstance().kr_account.value;
.kr_account final isDeviceLogin =
.value; account != null && account.startsWith('9000');
final isDeviceLogin = account != null &&
account.startsWith('9000');
final accountText = isDeviceLogin final accountText = isDeviceLogin
? '待绑定' ? '待绑定'
: '${KRAppRunData.getInstance().kr_account.value.toString()}'; : '${KRAppRunData.getInstance().kr_account.value.toString()}';
@ -164,11 +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, 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,
@ -184,19 +222,20 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.w), 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),
@ -212,7 +251,7 @@ class KRDeleteAccountView extends GetView<KRDeleteAccountController> {
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

View File

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

View File

@ -55,7 +55,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,
@ -162,7 +162,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((_) {
// //

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 '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(
@ -35,7 +35,8 @@ class KRInviteView extends GetView<KRInviteController> {
// 🟢 // 🟢
Container( Container(
width: double.infinity, width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.w), padding:
EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.w),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(25.r), borderRadius: BorderRadius.circular(25.r),
@ -68,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(
@ -113,12 +115,27 @@ class KRInviteView extends GetView<KRInviteController> {
), ),
onPressed: () { onPressed: () {
if (controller.kr_referCode.value.isNotEmpty) { if (controller.kr_referCode.value.isNotEmpty) {
Clipboard.setData( if (GetPlatform.isIOS) {
ClipboardData(text: controller.kr_referCode.value), final code = controller.kr_referCode.value;
); final text = '#您的好友邀请您使用Hi快网络加速器\n'
KRCommonUtil.kr_showToast( '安装完毕后,在软件内<邀请好友>页面粘贴以下邀请码\n'
AppTranslations.kr_invite.inviteCodeCopied, '$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( 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),
), ),
@ -199,8 +221,7 @@ class KRInviteView extends GetView<KRInviteController> {
), ),
// 5. HIHelpEntrance Stack // 5. HIHelpEntrance Stack
if (!isKeyboardVisible) if (!isKeyboardVisible) const HIHelpEntrance(),
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: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: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});
@ -27,23 +28,29 @@ class KRLoginView extends GetView<KRLoginController> {
resizeToAvoidBottomInset: true, resizeToAvoidBottomInset: true,
child: Stack( child: Stack(
children: [ children: [
SingleChildScrollView( GestureDetector(
child: Padding( behavior: HitTestBehavior.translucent,
padding: EdgeInsets.symmetric( onTap: () {
horizontal: 40.w, FocusScope.of(context).unfocus();
), },
child: Column( child: SingleChildScrollView(
children: [ child: Padding(
// Text( padding: EdgeInsets.symmetric(
// '${controller.kr_loginStatus.value}', horizontal: 40.w,
// style: TextStyle(color: Colors.white), // 使 TextStyle() ),
// ), child: Column(
SizedBox(height: 20.w), children: [
_buildUserInfoSection(), // Text(
SizedBox(height: 12.w), // '${controller.kr_loginStatus.value}',
_buildContentByEntry(), // style: TextStyle(color: Colors.white), // 使 TextStyle()
SizedBox(height: 100.w), // // ),
], 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, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Obx(() { Obx(() {
final account = KRAppRunData.getInstance() final account = KRAppRunData.getInstance().kr_account.value;
.kr_account final isDeviceLogin =
.value; account != null && account.startsWith('9000');
final isDeviceLogin = account != null &&
account.startsWith('9000');
final accountText = isDeviceLogin final accountText = isDeviceLogin
? '待绑定' ? '待绑定'
: '${KRAppRunData.getInstance().kr_account.value.toString()}'; : '${KRAppRunData.getInstance().kr_account.value.toString()}';
@ -270,6 +275,41 @@ class KRLoginView extends GetView<KRLoginController> {
height: 50.w, // height: 50.w, //
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,

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

View File

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

View File

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

View File

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

View File

@ -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;

View File

@ -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,
),
), ),
), ),
), ),

View File

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

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,

View File

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

View File

@ -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"))

View File

@ -254,6 +254,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" 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: crypto:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1049,10 +1057,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: mime name: mime
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "1.0.6"
nm: nm:
dependency: transitive dependency: transitive
description: description:
@ -1413,6 +1421,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.0" 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: shelf:
dependency: transitive dependency: transitive
description: description:

View File

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

View File

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

View File

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