feat: bugfix
This commit is contained in:
parent
1b1b38aba5
commit
86687cac58
@ -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 |
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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";
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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');
|
// 优势:不重启VPN,保持连接状态,切换瞬间完成,VPN开关不闪烁
|
||||||
await KRSingBoxImp.instance.kr_stop(); // 先停止VPN(已跳过DNS恢复)
|
KRLogUtil.kr_i('🔄 [热切换] 调用 selectOutbound 切换节点...', tag: 'HomeController');
|
||||||
|
|
||||||
// 🚀 优化:减少等待时间(DNS操作已优化,无需过长等待)
|
// 🔧 关键修复:确定正确的 selector 组 tag
|
||||||
KRLogUtil.kr_i('⏳ [优化] 等待VPN完全停止(800ms)...', tag: 'HomeController');
|
// selectOutbound(groupTag, outboundTag) - 第一个参数是组的tag,不是节点的tag
|
||||||
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(); // 重新启动VPN(已跳过DNS备份)
|
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');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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((_) {
|
||||||
// 页面返回后恢复默认主题色
|
// 页面返回后恢复默认主题色
|
||||||
|
|||||||
@ -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(),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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(),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -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),
|
||||||
|
|||||||
@ -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,
|
||||||
) {
|
) {
|
||||||
// 由于移除了 Obx,isSelected 的值在构建时就固定了。
|
// 由于移除了 Obx,isSelected 的值在构建时就固定了。
|
||||||
// 它将不再响应用户的点击事件来改变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,
|
||||||
),
|
),
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]
|
),
|
||||||
|
]
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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"))
|
||||||
|
|||||||
28
pubspec.lock
28
pubspec.lock
@ -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:
|
||||||
|
|||||||
@ -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"
|
||||||
|
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user