diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements
index 1d2cf40..3f9f560 100755
--- a/ios/Runner/Runner.entitlements
+++ b/ios/Runner/Runner.entitlements
@@ -6,7 +6,6 @@
development
com.apple.developer.associated-domains
- applinks:alf57p.openinstall.com
applinks:alf57p.oplinking.com
com.apple.developer.networking.networkextension
diff --git a/ios/Runner/RunnerRelease.entitlements b/ios/Runner/RunnerRelease.entitlements
index 1d2cf40..3f9f560 100755
--- a/ios/Runner/RunnerRelease.entitlements
+++ b/ios/Runner/RunnerRelease.entitlements
@@ -6,7 +6,6 @@
development
com.apple.developer.associated-domains
- applinks:alf57p.openinstall.com
applinks:alf57p.oplinking.com
com.apple.developer.networking.networkextension
diff --git a/lib/app/model/response/kr_node_list.dart b/lib/app/model/response/kr_node_list.dart
index 6b25e72..c7589d8 100755
--- a/lib/app/model/response/kr_node_list.dart
+++ b/lib/app/model/response/kr_node_list.dart
@@ -150,9 +150,9 @@ class KrNodeListItem {
final protocolsList = jsonDecode(protocols) as List;
final currentProtocol = json['protocol']?.toString().toLowerCase() ?? '';
- KRLogUtil.kr_i('📋 protocols 字段内容 (${protocolsList.length} 个协议):', tag: 'NodeList');
+ // KRLogUtil.kr_i('📋 protocols 字段内容 (${protocolsList.length} 个协议):', tag: 'NodeList');
for (var i = 0; i < protocolsList.length; i++) {
- KRLogUtil.kr_i(' 协议 $i: ${jsonEncode(protocolsList[i])}', tag: 'NodeList');
+ // KRLogUtil.kr_i(' 协议 $i: ${jsonEncode(protocolsList[i])}', tag: 'NodeList');
}
if (protocolsList.isNotEmpty) {
@@ -170,7 +170,7 @@ class KrNodeListItem {
(currentProtocol == 'shadowsocks' && protocolType == 'ss') ||
(currentProtocol == 'ss' && protocolType == 'shadowsocks')) {
matchedProtocolConfig = configMap;
- KRLogUtil.kr_i('🎯 找到匹配的协议配置: $protocolType', tag: 'NodeList');
+ // KRLogUtil.kr_i('🎯 找到匹配的协议配置: $protocolType', tag: 'NodeList');
break;
}
}
@@ -182,22 +182,22 @@ class KrNodeListItem {
// 这样可以保留顶层的正确端口(如 53441),不被 protocols 数组中的端口(如 287)覆盖
if (port == 0 && targetProtocol['port'] != null) {
port = _parseIntSafely(targetProtocol['port']);
- KRLogUtil.kr_i('✅ 从 protocols 解析端口: $port', tag: 'NodeList');
+ // KRLogUtil.kr_i('✅ 从 protocols 解析端口: $port', tag: 'NodeList');
} else {
- KRLogUtil.kr_i('✅ 保留顶层端口: $port (protocols中的端口: ${targetProtocol['port']})', tag: 'NodeList');
+ // KRLogUtil.kr_i('✅ 保留顶层端口: $port (protocols中的端口: ${targetProtocol['port']})', tag: 'NodeList');
}
// 提取 cipher(加密方法)
if (targetProtocol['cipher'] != null && targetProtocol['cipher'].toString().isNotEmpty) {
method = targetProtocol['cipher'].toString();
- KRLogUtil.kr_i('✅ 从 protocols 解析 cipher: $method', tag: 'NodeList');
+ // KRLogUtil.kr_i('✅ 从 protocols 解析 cipher: $method', tag: 'NodeList');
}
}
} catch (e) {
KRLogUtil.kr_w('⚠️ 解析 protocols 字段失败: $e', tag: 'NodeList');
}
}
- KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'NodeList');
+ // KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'NodeList');
return KrNodeListItem(
id: _parseIntSafely(json['id']),
diff --git a/lib/app/modules/hi_node_list/views/hi_page_node_view.dart b/lib/app/modules/hi_node_list/views/hi_page_node_view.dart
index bc6384e..ebcfd14 100644
--- a/lib/app/modules/hi_node_list/views/hi_page_node_view.dart
+++ b/lib/app/modules/hi_node_list/views/hi_page_node_view.dart
@@ -132,12 +132,12 @@ class _HINodePageViewState extends State {
TextSpan(
children: [
TextSpan(
- text: '${AppTranslations.kr_setting.connectionTypeRule}:',
+ text: '智能模式:',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14.sp, color: Colors.black),
),
TextSpan(
- text: AppTranslations.kr_setting.connectionTypeRuleRemark,
- style: TextStyle(fontSize: 14.sp, color: Colors.black54),
+ text: '绕过所在地IP,只代理外网服务器,速度更快',
+ style: TextStyle(fontSize: 14.sp,),
),
],
),
@@ -147,12 +147,12 @@ class _HINodePageViewState extends State {
TextSpan(
children: [
TextSpan(
- text: '${AppTranslations.kr_setting.connectionTypeGlobal}:',
- style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14.sp, color: Colors.black),
+ text: '全局模式:',
+ style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14.sp),
),
TextSpan(
- text: AppTranslations.kr_setting.connectionTypeGlobalRemark,
- style: TextStyle(fontSize: 14.sp, color: Colors.black54),
+ text: '代理所有网络服务器,保障隐私',
+ style: TextStyle(fontSize: 14.sp),
),
],
),
diff --git a/lib/app/modules/hi_user_info/controllers/hi_user_info_controller.dart b/lib/app/modules/hi_user_info/controllers/hi_user_info_controller.dart
index e8aeb44..518a80a 100755
--- a/lib/app/modules/hi_user_info/controllers/hi_user_info_controller.dart
+++ b/lib/app/modules/hi_user_info/controllers/hi_user_info_controller.dart
@@ -16,6 +16,7 @@ import 'dart:convert';
import 'package:kaer_with_panels/utils/snackbar_util.dart';
import 'package:kaer_with_panels/app/routes/app_pages.dart';
import 'package:kaer_with_panels/app/common/app_config.dart';
+import 'package:kaer_with_panels/app/utils/kr_common_util.dart';
class HIUserInfoController extends GetxController {
/// 订阅服务
@@ -212,7 +213,8 @@ class HIUserInfoController extends GetxController {
KRLogUtil.kr_e('订阅信息刷新失败: $e', tag: 'DeviceManagement');
}
- KRSnackBarUtil.show(AppTranslations.kr_dialog.success, '退出登录成功');
+ // KRSnackBarUtil.show(AppTranslations.kr_dialog.success, '退出登录成功');
+ KRCommonUtil.kr_showToast('退出登录成功');
Get.offAllNamed(Routes.KR_HOME);
},
);
diff --git a/lib/app/modules/hi_user_info/views/hi_user_info_view.dart b/lib/app/modules/hi_user_info/views/hi_user_info_view.dart
index 1705bd7..045a703 100755
--- a/lib/app/modules/hi_user_info/views/hi_user_info_view.dart
+++ b/lib/app/modules/hi_user_info/views/hi_user_info_view.dart
@@ -273,6 +273,7 @@ class HIUserInfoView extends GetView {
confirmText: '返回',
// 3. onCancel 对应“确认注销”按钮的点击事件
onCancel: () {
+ Get.back();
// 执行页面跳转到注销账户页面
Get.toNamed(Routes.KR_DELETE_ACCOUNT);
},
@@ -547,7 +548,7 @@ class HIUserInfoView extends GetView {
RichText(
text: TextSpan(
style: TextStyle(
- fontSize: 10.sp,
+ fontSize: 12.sp,
color: Colors.black, //
),
children: [
diff --git a/lib/app/modules/kr_home/controllers/kr_home_controller.dart b/lib/app/modules/kr_home/controllers/kr_home_controller.dart
index 8845046..d8cc687 100755
--- a/lib/app/modules/kr_home/controllers/kr_home_controller.dart
+++ b/lib/app/modules/kr_home/controllers/kr_home_controller.dart
@@ -206,6 +206,7 @@ class KRHomeController extends GetxController with WidgetsBindingObserver {
return;
}
bool isExpired = false;
+ print('current.expireTime${current.expireTime}');
if (current.expireTime.isNotEmpty) {
try {
isExpired =
diff --git a/lib/app/modules/kr_invite/controllers/kr_invite_controller.dart b/lib/app/modules/kr_invite/controllers/kr_invite_controller.dart
index aa13d1f..89570e8 100755
--- a/lib/app/modules/kr_invite/controllers/kr_invite_controller.dart
+++ b/lib/app/modules/kr_invite/controllers/kr_invite_controller.dart
@@ -152,7 +152,7 @@ class KRInviteController extends GetxController {
/// 处理绑定邀请码
Future kr_handleBindInviteCode() async {
- final text = otherInviteCodeController.text;
+ final text = otherInviteCodeController.text.trim();
if (text.isEmpty) {
KRCommonUtil.kr_showToast('请输入邀请码');
return;
diff --git a/lib/app/modules/kr_invite/views/kr_invite_view.dart b/lib/app/modules/kr_invite/views/kr_invite_view.dart
index 5fec251..0dff52d 100755
--- a/lib/app/modules/kr_invite/views/kr_invite_view.dart
+++ b/lib/app/modules/kr_invite/views/kr_invite_view.dart
@@ -172,9 +172,10 @@ class KRInviteView extends GetView {
textInputAction: TextInputAction.done,
onSubmitted: (_) => controller.kr_handleBindInviteCode(),
textAlign: TextAlign.center,
+ maxLength: 10,
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
decoration: InputDecoration(
- hintText: '填入邀请人邀请码兑换免费时长...',
+ hintText: '填码领免费时长...',
hintStyle: const TextStyle(color: Color(0xFFA6A6A6)),
filled: true,
fillColor: Colors.transparent,
diff --git a/lib/app/modules/kr_login/controllers/kr_login_controller.dart b/lib/app/modules/kr_login/controllers/kr_login_controller.dart
index f82a49c..2fd8729 100755
--- a/lib/app/modules/kr_login/controllers/kr_login_controller.dart
+++ b/lib/app/modules/kr_login/controllers/kr_login_controller.dart
@@ -1,4 +1,5 @@
import 'dart:async';
+import 'dart:convert';
import 'package:flutter/widgets.dart';
import 'package:get/get.dart';
@@ -11,6 +12,7 @@ import 'package:kaer_with_panels/app/localization/app_translations.dart';
import 'package:kaer_with_panels/app/utils/kr_event_bus.dart';
import 'package:kaer_with_panels/app/services/kr_site_config_service.dart';
import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart';
+import 'package:kaer_with_panels/app/utils/kr_secure_storage.dart';
import '../../../localization/kr_language_utils.dart';
import 'package:kaer_with_panels/app/services/kr_subscribe_service.dart';
@@ -97,6 +99,9 @@ class KRLoginController extends GetxController
RxList kr_emailList = [].obs;
RxBool kr_isDropdownVisible = false.obs;
+ /// 邮箱历史记录
+ RxList kr_emailHistory = [].obs;
+
/// 定位
final LayerLink kr_layerLink = LayerLink();
OverlayEntry? overlayEntry; // 悬浮框
@@ -169,14 +174,13 @@ class KRLoginController extends GetxController
} else if (entry == 'bind_email') {
// 如果是从“绑定邮箱”进入,直接设置为“注册-发送验证码”状态
kr_loginStatus.value = KRLoginProgressStatus.kr_registerSetPsd;
- }else if (entry == 'login') {
+ } else if (entry == 'login') {
// 登录
kr_loginStatus.value = KRLoginProgressStatus.kr_loginByPsd;
}
// 如果没有匹配的 entry 值,则保持默认的 kr_loginByPsd 状态
}
-
// 初始化计时器
_timer = Timer(Duration.zero, () {});
@@ -279,6 +283,20 @@ class KRLoginController extends GetxController
});*/
kr_initFocus();
+
+ // 加载邮箱历史记录
+ KRSecureStorage()
+ .kr_readData(key: KRSecureStorage.EMAIL_HISTORY_KEY)
+ .then((historyString) {
+ if (historyString != null && historyString.isNotEmpty) {
+ try {
+ final List decodedList = jsonDecode(historyString);
+ kr_emailHistory.assignAll(decodedList.cast());
+ } catch (e) {
+ print('Error decoding email history: $e');
+ }
+ }
+ });
}
// 判断是否是手机号
@@ -338,8 +356,7 @@ class KRLoginController extends GetxController
}, (r) async {
HIDialog.show(
title: '*重要提示',
- message:
- '验证邮件已发送至邮箱,如无法找到,请检查垃圾邮件箱或营销邮件箱。',
+ message: '验证邮件已发送至邮箱,如无法找到,请检查垃圾邮件箱或营销邮件箱。',
);
_startCountdown();
@@ -353,9 +370,8 @@ class KRLoginController extends GetxController
return;
}
- final either = await KRAuthApi().kr_login(
- accountController.text,
- psdController.text);
+ final either =
+ await KRAuthApi().kr_login(accountController.text, psdController.text);
either.fold((l) {
KRCommonUtil.kr_showToast(l.msg);
}, (r) async {
@@ -403,45 +419,49 @@ class KRLoginController extends GetxController
// 定义注册请求逻辑
Future performRegister() async {
final either = await KRAuthApi().kr_register(
- accountController.text,
- psdController.text,
- code: codeController.text.isEmpty ? null : codeController.text,
- inviteCode: inviteCodeController.text.isEmpty ? null : inviteCodeController.text,
+ accountController.text,
+ psdController.text,
+ code: codeController.text.isEmpty ? null : codeController.text,
+ inviteCode: inviteCodeController.text.isEmpty
+ ? null
+ : inviteCodeController.text,
);
either.fold(
- (l) {
- HIDialog.show(
- message: l.msg,
- preventBackDismiss: true,
- confirmText: '确定',
- );
- return;
- },
- (r) async {
+ (l) {
+ HIDialog.show(
+ message: l.msg,
+ preventBackDismiss: true,
+ confirmText: '确定',
+ );
+ return;
+ },
+ (r) async {
_saveLoginData(r);
KRCommonUtil.kr_showToast('登录成功');
},
);
}
+
// 检查是否已有订阅
- final subscriptionResult = await KRAuthApi().kr_checkSubscription(accountController.text);
+ final subscriptionResult =
+ await KRAuthApi().kr_checkSubscription(accountController.text);
subscriptionResult.fold(
- (error) {
+ (error) {
KRCommonUtil.kr_showToast(error.msg);
},
- (isFullySubscribed) async {
- if (isFullySubscribed) {
- HIDialog.show(
- message: '当前邮箱已有套餐,继续绑定会丢失设备当前套餐,是否继续?',
- confirmText: '继续',
- cancelText: '取消',
- onConfirm: performRegister,
- );
- } else {
- await performRegister();
- }
+ (isFullySubscribed) async {
+ if (isFullySubscribed) {
+ HIDialog.show(
+ message: '当前邮箱已有套餐,继续绑定会丢失设备当前套餐,是否继续?',
+ confirmText: '继续',
+ cancelText: '取消',
+ onConfirm: performRegister,
+ );
+ } else {
+ await performRegister();
+ }
},
);
}
@@ -479,7 +499,7 @@ class KRLoginController extends GetxController
accountController.text,
codeController.text,
kr_loginStatus.value == KRLoginProgressStatus.kr_registerSendCode
- ? 2 // 注册验证码类型为2
+ ? 2 // 注册验证码类型为2
: 3); // 重置密码验证码类型为3
either.fold((l) {
KRCommonUtil.kr_showToast(l.msg);
@@ -508,9 +528,7 @@ class KRLoginController extends GetxController
}
final either = await KRAuthApi().kr_setNewPsdByForgetPsd(
- accountController.text,
- codeController.text,
- psdController.text);
+ accountController.text, codeController.text, psdController.text);
either.fold((l) {
KRCommonUtil.kr_showToast(l.msg);
}, (r) async {
@@ -547,15 +565,43 @@ class KRLoginController extends GetxController
/// 验证邮箱格式
bool validateEmail(String str) {
- return RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$').hasMatch(str);
+ return RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
+ .hasMatch(str);
}
/// 设置登录数据(仅支持邮箱)
void _saveLoginData(String token) {
KRAppRunData.getInstance().kr_saveUserInfo(
- token,
- accountController.text,
- );
+ token,
+ accountController.text,
+ );
+
+ // 保存邮箱到历史记录
+ KRSecureStorage()
+ .kr_readData(key: KRSecureStorage.EMAIL_HISTORY_KEY)
+ .then((historyString) async {
+ List history = [];
+ if (historyString != null && historyString.isNotEmpty) {
+ try {
+ final List decodedList = jsonDecode(historyString);
+ history = decodedList.cast();
+ } catch (e) {
+ print('Error decoding email history for saving: $e');
+ }
+ }
+
+ // 确保不重复添加
+ if (!history.contains(accountController.text)) {
+ history.add(accountController.text);
+ }
+ // 限制历史记录数量,例如只保留最新的5个
+ if (history.length > 5) {
+ history = history.sublist(history.length - 5);
+ }
+ await KRSecureStorage().kr_saveData(
+ key: KRSecureStorage.EMAIL_HISTORY_KEY, value: jsonEncode(history));
+ });
+
kr_loginStatus.value = KRLoginProgressStatus.kr_check;
// 登录/注册成功后,发送消息触发订阅服务刷新
diff --git a/lib/app/modules/kr_login/views/kr_login_view.dart b/lib/app/modules/kr_login/views/kr_login_view.dart
index 71d767c..bacf572 100755
--- a/lib/app/modules/kr_login/views/kr_login_view.dart
+++ b/lib/app/modules/kr_login/views/kr_login_view.dart
@@ -124,10 +124,17 @@ class KRLoginView extends GetView {
constraints: BoxConstraints(minHeight: 300.w),
child: Column(
children: [
- _buildStandardInputField(
- controller: controller.accountController,
- hintText: 'Email',
- ),
+ Obx(() => _buildStandardInputField(
+ controller: controller.accountController,
+ hintText: '请输入邮箱地址',
+ suffixes: const [
+ '@gmail.com',
+ '@outlook.com',
+ '@qq.com',
+ '@163.com'
+ ],
+ historyEmails: controller.kr_emailHistory.toList(),
+ )),
// SizedBox(height: 10.w),
// _buildStandardInputField(
// controller: controller.psdController,
@@ -157,11 +164,18 @@ class KRLoginView extends GetView {
required TextEditingController controller,
required String hintText,
bool isPassword = false,
+ List? suffixes,
+ List? historyEmails,
}) {
- return SizedBox(
- // height: 50, // 固定高度
- child: TextField(
+ // 基础输入框构建逻辑提取
+ Widget buildTextField({
+ FocusNode? focusNode,
+ VoidCallback? onEditingComplete,
+ }) {
+ return TextField(
controller: controller,
+ focusNode: focusNode,
+ onEditingComplete: onEditingComplete,
obscureText: isPassword,
style: KrAppTextStyle(
fontSize: 16,
@@ -189,6 +203,122 @@ class KRLoginView extends GetView {
borderSide: BorderSide(color: Colors.white, width: 2),
),
),
+ );
+ }
+
+ // 如果即没有后缀配置也没有历史记录,直接返回普通输入框
+ if ((suffixes == null || suffixes.isEmpty) &&
+ (historyEmails == null || historyEmails.isEmpty)) {
+ return SizedBox(
+ child: buildTextField(),
+ );
+ }
+
+ // 使用 RawAutocomplete 实现带提示的输入框
+ return SizedBox(
+ child: RawAutocomplete(
+ textEditingController: controller,
+ focusNode: FocusNode(),
+ optionsBuilder: (TextEditingValue textEditingValue) {
+ final inputText = textEditingValue.text;
+
+ // 结果列表
+ List options = [];
+
+ // 1. 匹配历史记录 (只要输入内容匹配历史记录的开头,或者输入为空)
+ if (historyEmails != null) {
+ if (inputText.isEmpty) {
+ // 理论上 RawAutocomplete 默认不显示空输入的 options,除非自定义 fieldViewBuilder 监听
+ // 但 RawAutocomplete 的 optionsBuilder 在 text 变化时触发。
+ // 若要空内容显示,通常需要 Focus 触发。这里先处理有内容的情况,
+ // 或者如果 RawAutocomplete 支持空内容(通过 initialValue? 不行,得看 triggerMode)
+ // 简单处理:如果为空,返回所有历史记录
+ options.addAll(historyEmails);
+ } else {
+ options.addAll(historyEmails
+ .where((email) => email.startsWith(inputText)));
+ }
+ }
+
+ // 2. 匹配后缀 (仅当有输入且不含 @ 或含 @ 但未完整时)
+ if (suffixes != null && inputText.isNotEmpty) {
+ if (!inputText.contains('@')) {
+ options.addAll(suffixes.map((suffix) => '$inputText$suffix'));
+ } else {
+ final atIndex = inputText.indexOf('@');
+ final prefix = inputText.substring(0, atIndex);
+ final domainInput = inputText.substring(atIndex); // 包含 @
+
+ options.addAll(suffixes
+ .where((suffix) => suffix.startsWith(domainInput))
+ .map((suffix) => '$prefix$suffix'));
+ }
+ }
+
+ // 去重
+ return options.toSet().toList();
+ },
+ fieldViewBuilder: (
+ BuildContext context,
+ TextEditingController textEditingController,
+ FocusNode focusNode,
+ VoidCallback onFieldSubmitted,
+ ) {
+ // 这里我们使用传入的 controller,而不是 fieldViewBuilder 提供的 textEditingController
+ // 因为我们需要外部控制 controller。
+ // 注意:RawAutocomplete 默认会监听 textEditingController,
+ // 如果我们传入了自己的 controller 给 RawAutocomplete,
+ // fieldViewBuilder 的 textEditingController 其实就是我们传入的那个。
+ return buildTextField(
+ focusNode: focusNode,
+ onEditingComplete: onFieldSubmitted,
+ );
+ },
+ optionsViewBuilder: (
+ BuildContext context,
+ AutocompleteOnSelected onSelected,
+ Iterable options,
+ ) {
+ return Align(
+ alignment: Alignment.topLeft,
+ child: Material(
+ elevation: 4.0,
+ color: const Color(0xFF1E1E1E), // 深色背景
+ borderRadius: BorderRadius.circular(8.r),
+ child: Container(
+ constraints: BoxConstraints(
+ maxHeight: 200.w,
+ maxWidth: 300.w, //略小于屏幕宽度,或者根据父级宽度动态计算更好,这里简单给定
+ ),
+ width: MediaQuery.of(context).size.width - 80.w, // 减去两边 padding
+ child: ListView.builder(
+ padding: EdgeInsets.zero,
+ shrinkWrap: true,
+ itemCount: options.length,
+ itemBuilder: (BuildContext context, int index) {
+ final String option = options.elementAt(index);
+ return InkWell(
+ onTap: () {
+ onSelected(option);
+ },
+ child: Padding(
+ padding: EdgeInsets.symmetric(
+ horizontal: 16.w, vertical: 12.w),
+ child: Text(
+ option,
+ style: KrAppTextStyle(
+ fontSize: 14,
+ color: Colors.white,
+ ),
+ ),
+ ),
+ );
+ },
+ ),
+ ),
+ ),
+ );
+ },
),
);
}
diff --git a/lib/app/modules/kr_purchase_membership/views/kr_purchase_membership_view.dart b/lib/app/modules/kr_purchase_membership/views/kr_purchase_membership_view.dart
index 5906c25..bb7cef6 100755
--- a/lib/app/modules/kr_purchase_membership/views/kr_purchase_membership_view.dart
+++ b/lib/app/modules/kr_purchase_membership/views/kr_purchase_membership_view.dart
@@ -247,38 +247,6 @@ class KRPurchaseMembershipView extends GetView
);
}
-
- // 账号部分
- Widget _kr_buildAccountSection(BuildContext context) {
- return Container(
- margin: EdgeInsets.all(16),
- padding: EdgeInsets.all(16),
- decoration: BoxDecoration(
- color: Theme.of(context).cardColor,
- borderRadius: BorderRadius.circular(12),
- ),
- child: Row(
- children: [
- Text(
- AppTranslations.kr_purchaseMembership.myAccount,
- style: KrAppTextStyle(
- fontSize: 14,
- color: Theme.of(context).textTheme.bodyMedium?.color,
- ),
- ),
- const Spacer(),
- Obx(() => Text(
- controller.kr_userEmail.value,
- style: KrAppTextStyle(
- fontSize: 14,
- color: Theme.of(context).textTheme.bodySmall?.color,
- ),
- )),
- ],
- ),
- );
- }
-
// 套餐选项卡片
Widget _kr_buildPlanOptionCard(
KRPackageListItem plan,
@@ -428,9 +396,7 @@ class KRPurchaseMembershipView extends GetView
style: TextStyle(
color: Colors.black, // 白色文字更清晰
fontSize: 14,
- fontWeight: index == 1
- ? FontWeight.w300
- : FontWeight.w600,
+ fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
),
diff --git a/lib/app/network/http_util.dart b/lib/app/network/http_util.dart
index 9ef6945..08004e0 100755
--- a/lib/app/network/http_util.dart
+++ b/lib/app/network/http_util.dart
@@ -259,7 +259,7 @@ class HttpUtil {
/// 自定义简洁 HTTP 拦截器(无边框符号)
class _KRSimpleHttpInterceptor extends Interceptor {
/// 常量:手动控制是否打印拦截器日志与解密结果
- static const bool KR_HTTP_PRINT = true;
+ static const bool KR_HTTP_PRINT = false;
final Dio _dio;
_KRSimpleHttpInterceptor(this._dio);
static String? _lastPath;
diff --git a/lib/app/services/singbox_imp/kr_sing_box_imp.dart b/lib/app/services/singbox_imp/kr_sing_box_imp.dart
index 633f9be..aed02f2 100755
--- a/lib/app/services/singbox_imp/kr_sing_box_imp.dart
+++ b/lib/app/services/singbox_imp/kr_sing_box_imp.dart
@@ -607,6 +607,15 @@ class KRSingBoxImp {
KRLogUtil.kr_i('✅ [智能代理模式] region 设为 $effectiveRegion', tag: 'SingBox');
}
+ final useWindowsDnsDefaults = Platform.isWindows;
+ final remoteDnsAddress = useWindowsDnsDefaults
+ ? 'udp://1.1.1.1'
+ : 'https://dns.google/dns-query';
+ final directDnsAddress = useWindowsDnsDefaults
+ ? (effectiveRegion == 'cn' ? 'udp://223.5.5.5' : 'udp://1.1.1.1')
+ : 'local';
+ final dnsDomainStrategy = useWindowsDnsDefaults ? '' : 'prefer_ipv4';
+
final op = {
"region": effectiveRegion, // 🔧 修复:根据出站模式动态设置 region
"block-ads": false, // 参考 hiddify-app: 默认关闭广告拦截
@@ -616,11 +625,10 @@ class KRSingBoxImp {
"resolve-destination": false,
"ipv6-mode":
"ipv4_only", // 参考 hiddify-app: 仅使用 IPv4 (有效值: ipv4_only, prefer_ipv4, prefer_ipv6, ipv6_only)
- "remote-dns-address":
- "https://dns.google/dns-query", // 使用 Google DoH,避免中转节点 DNS 死锁
- "remote-dns-domain-strategy": "prefer_ipv4",
- "direct-dns-address": "local", // 使用系统 DNS,确保中转服务器域名能被解析
- "direct-dns-domain-strategy": "prefer_ipv4",
+ "remote-dns-address": remoteDnsAddress, // 使用 Google DoH,避免中转节点 DNS 死锁
+ "remote-dns-domain-strategy": dnsDomainStrategy,
+ "direct-dns-address": directDnsAddress, // 使用系统 DNS,确保中转服务器域名能被解析
+ "direct-dns-domain-strategy": dnsDomainStrategy,
"mixed-port": kr_port,
"tproxy-port": kr_port,
"local-dns-port": 36450,
diff --git a/lib/app/utils/kr_common_util.dart b/lib/app/utils/kr_common_util.dart
index 3e1125c..6693363 100755
--- a/lib/app/utils/kr_common_util.dart
+++ b/lib/app/utils/kr_common_util.dart
@@ -22,7 +22,6 @@ class KRCommonUtil {
_kr_isOpeningCustomerService = true;
try {
- kr_showToast('userInfo.openingCustomerService'.tr);
await Get.toNamed(Routes.KR_CHATWOOT);
} catch (e) {
debugPrint('打开客服失败: $e');
diff --git a/lib/app/utils/kr_secure_storage.dart b/lib/app/utils/kr_secure_storage.dart
index fabe6b5..7380c14 100755
--- a/lib/app/utils/kr_secure_storage.dart
+++ b/lib/app/utils/kr_secure_storage.dart
@@ -17,6 +17,9 @@ class KRSecureStorage {
// 存储箱名称
static const String _boxName = 'kaer_secure_storage';
+ // 邮箱历史记录的键
+ static const String EMAIL_HISTORY_KEY = 'email_history';
+
// 加密密钥
static const String _encryptionKey = 'kaer_secure_storage_key';
diff --git a/lib/app/widgets/hi_base_scaffold.dart b/lib/app/widgets/hi_base_scaffold.dart
index b7b4d7e..4fbf01a 100644
--- a/lib/app/widgets/hi_base_scaffold.dart
+++ b/lib/app/widgets/hi_base_scaffold.dart
@@ -179,7 +179,6 @@ class HIBaseScaffold extends StatelessWidget {
style: TextStyle(
color: Colors.black, // 副标题使用带透明度的白色
fontSize: 14,
- fontWeight: FontWeight.w100,
),
),
),
diff --git a/lib/singbox/service/ffi_singbox_service.dart b/lib/singbox/service/ffi_singbox_service.dart
index 7883b0f..5fd8b5c 100755
--- a/lib/singbox/service/ffi_singbox_service.dart
+++ b/lib/singbox/service/ffi_singbox_service.dart
@@ -53,20 +53,21 @@ class FFISingboxService with InfraLogger implements SingboxService {
return p.join("libcore", libName);
}
- // 🔧 修复:开发环境使用绝对路径
- // 尝试开发环境路径(相对于当前工作目录)
- final devPath = p.join("libcore", "bin", libName);
- if (kDebugMode) {
- print('🔍 [FFI] 检查开发环境路径: $devPath');
- print('🔍 [FFI] 当前工作目录: ${Directory.current.path}');
- print('🔍 [FFI] 文件是否存在: ${File(devPath).existsSync()}');
- }
+ if (Platform.isMacOS) {
+ // 当前 app binary 路径
+ final execPath = File(Platform.resolvedExecutable).absolute.path;
- if (File(devPath).existsSync()) {
- if (kDebugMode) {
- print('✅ [FFI] 使用开发环境路径: $devPath');
+ // App binary 在 Contents/MacOS, libcore 在 Contents/Frameworks
+ final bundleDir = p.dirname(p.dirname(execPath)); // 上两级到 Contents
+ final frameworksPath = p.join(bundleDir, 'Frameworks', libName);
+
+ if (File(frameworksPath).existsSync()) {
+ if (kDebugMode) print('✅ [FFI] macOS bundle 相对路径: $frameworksPath');
+ return frameworksPath;
+ } else {
+ print('⚠️ [FFI] 未找到 libcore.dylib,fallback 到默认路径: $libName');
+ return libName;
}
- return devPath;
}
// 生产环境:使用相对路径(bundle中的路径)
@@ -82,22 +83,28 @@ class FFISingboxService with InfraLogger implements SingboxService {
print('🚀 [FFI] init() 开始');
}
loggy.debug("initializing");
+ final box = FFISingboxService._box;
+ box.setupOnce(NativeApi.initializeApiDLData);
// 注意:setupOnce 会在 worker isolate 中调用(见 _ffiLoadLibrary)
// 在主 isolate 中调用会导致阻塞,因此这里跳过
if (kDebugMode) {
print('⏭️ [FFI] 跳过主 isolate 中的 setupOnce(将在 worker isolate 中执行)');
}
-
+ print('📡 [FFI] 主 Isolate setupOnce 执行完毕,Port 已创建');
if (kDebugMode) {
print('📡 [FFI] 创建 ReceivePort');
}
_statusReceiver = ReceivePort('service status receiver');
+ print('📡 [FFI] Dart port created: ${_statusReceiver.sendPort.nativePort}');
if (kDebugMode) {
print('🔄 [FFI] 设置状态流');
}
- final source = _statusReceiver.asBroadcastStream().map((event) => jsonDecode(event as String)).map(SingboxStatus.fromEvent);
+ final source = _statusReceiver
+ .asBroadcastStream()
+ .map((event) => jsonDecode(event as String))
+ .map(SingboxStatus.fromEvent);
_status = ValueConnectableStream.seeded(
source,
const SingboxStopped(),
@@ -125,7 +132,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
_logger.debug('[黑屏调试] setup() 开始调用 libcore.dll - $startTime');
final err = await IsolateWorker().execute(
- () => _ffiSetup(baseDir, workingDir, tempDir, port, debugFlag),
+ () => _ffiSetup(baseDir, workingDir, tempDir, port, debugFlag),
allowSyncFallback: false,
);
@@ -137,9 +144,15 @@ class FFISingboxService with InfraLogger implements SingboxService {
_logger.error('[黑屏调试] setup() 错误: $err');
return left(err);
}
+ if (kDebugMode) {
+ print('✅ [FFI][ORDER] setup() finished OK');
+ }
return right(unit);
} catch (e) {
_logger.error('[黑屏调试] setup() 异常: $e');
+ if (kDebugMode) {
+ print('❌ [FFI][ORDER] setup() threw: $e');
+ }
return left(e.toString());
}
});
@@ -147,15 +160,15 @@ class FFISingboxService with InfraLogger implements SingboxService {
@override
TaskEither validateConfigByPath(
- String path,
- String tempPath,
- bool debug,
- ) {
+ String path,
+ String tempPath,
+ bool debug,
+ ) {
final debugFlag = debug ? 1 : 0;
return TaskEither(() async {
try {
final err = await IsolateWorker().execute(
- () => _ffiValidateConfig(path, tempPath, debugFlag),
+ () => _ffiValidateConfig(path, tempPath, debugFlag),
allowSyncFallback: false,
);
if (err != null && err.isNotEmpty) {
@@ -177,7 +190,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
_logger.debug('[黑屏调试] changeOptions 开始调用 libcore.dll - $startTime');
final err = await IsolateWorker().execute(
- () => _ffiChangeOptions(json),
+ () => _ffiChangeOptions(json),
allowSyncFallback: false,
);
@@ -199,12 +212,12 @@ class FFISingboxService with InfraLogger implements SingboxService {
@override
TaskEither generateFullConfigByPath(
- String path,
- ) {
+ String path,
+ ) {
return TaskEither(() async {
try {
final result = await IsolateWorker().execute(
- () => _ffiGenerateFullConfig(path),
+ () => _ffiGenerateFullConfig(path),
allowSyncFallback: false,
);
final ok = result.isNotEmpty && result[0] == true;
@@ -221,18 +234,21 @@ class FFISingboxService with InfraLogger implements SingboxService {
@override
TaskEither start(
- String configPath,
- String name,
- bool disableMemoryLimit,
- ) {
+ String configPath,
+ String name,
+ bool disableMemoryLimit,
+ ) {
loggy.debug("starting, memory limit: [${!disableMemoryLimit}]");
+ if (kDebugMode) {
+ print('🚦 [FFI][ORDER] start() entered (after setup)');
+ }
return TaskEither(() async {
try {
final startTime = DateTime.now();
_logger.debug('[黑屏调试] start() 开始调用 libcore.dll - $startTime');
final err = await IsolateWorker().execute(
- () => _ffiStart(configPath, disableMemoryLimit),
+ () => _ffiStart(configPath, disableMemoryLimit),
allowSyncFallback: false,
);
@@ -244,9 +260,15 @@ class FFISingboxService with InfraLogger implements SingboxService {
_logger.error('[黑屏调试] start() 错误: $err');
return left(err);
}
+ if (kDebugMode) {
+ print('✅ [FFI][ORDER] start() finished OK');
+ }
return right(unit);
} catch (e) {
_logger.error('[黑屏调试] start() 异常: $e');
+ if (kDebugMode) {
+ print('❌ [FFI][ORDER] start() threw: $e');
+ }
return left(e.toString());
}
});
@@ -282,15 +304,15 @@ class FFISingboxService with InfraLogger implements SingboxService {
@override
TaskEither restart(
- String configPath,
- String name,
- bool disableMemoryLimit,
- ) {
+ String configPath,
+ String name,
+ bool disableMemoryLimit,
+ ) {
loggy.debug("restarting, memory limit: [${!disableMemoryLimit}]");
return TaskEither(() async {
try {
final err = await IsolateWorker().execute(
- () => _ffiRestart(configPath, disableMemoryLimit),
+ () => _ffiRestart(configPath, disableMemoryLimit),
allowSyncFallback: false,
);
if (err != null && err.isNotEmpty) {
@@ -328,7 +350,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
_serviceStatsStream = null;
},
).map(
- (event) {
+ (event) {
if (event case String _) {
if (event.startsWith('error:')) {
loggy.error("[service stats client] error received: $event");
@@ -343,12 +365,18 @@ class FFISingboxService with InfraLogger implements SingboxService {
},
);
- final err = _box.startCommandClient(1, receiver.sendPort.nativePort).cast().toDartString();
+ if (kDebugMode) {
+ print(
+ '📡 [FFI][ORDER] watchStats startCommandClient port=${receiver.sendPort.nativePort}');
+ }
+ final err = _box
+ .startCommandClient(1, receiver.sendPort.nativePort)
+ .cast()
+ .toDartString();
if (err.isNotEmpty) {
loggy.error("error starting status command: $err");
throw err;
}
-
return _serviceStatsStream = statusStream;
}
@@ -368,7 +396,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
}
},
).map(
- (event) {
+ (event) {
if (event case String _) {
if (event.startsWith('error:')) {
logger.error("error received: $event");
@@ -385,7 +413,14 @@ class FFISingboxService with InfraLogger implements SingboxService {
);
try {
- final err = _box.startCommandClient(5, receiver.sendPort.nativePort).cast().toDartString();
+ if (kDebugMode) {
+ print(
+ '📡 [FFI][ORDER] watchGroups startCommandClient port=${receiver.sendPort.nativePort}');
+ }
+ final err = _box
+ .startCommandClient(5, receiver.sendPort.nativePort)
+ .cast()
+ .toDartString();
if (err.isNotEmpty) {
logger.error("error starting group command: $err");
throw err;
@@ -412,7 +447,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
}
},
).map(
- (event) {
+ (event) {
if (event case String _) {
if (event.startsWith('error:')) {
logger.error(event);
@@ -429,7 +464,14 @@ class FFISingboxService with InfraLogger implements SingboxService {
);
try {
- final err = _box.startCommandClient(13, receiver.sendPort.nativePort).cast().toDartString();
+ if (kDebugMode) {
+ print(
+ '📡 [FFI][ORDER] watchActiveGroups startCommandClient port=${receiver.sendPort.nativePort}');
+ }
+ final err = _box
+ .startCommandClient(13, receiver.sendPort.nativePort)
+ .cast()
+ .toDartString();
if (err.isNotEmpty) {
logger.error("error starting: $err");
throw err;
@@ -447,7 +489,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
return TaskEither(() async {
try {
final err = await IsolateWorker().execute(
- () => _ffiSelectOutbound(groupTag, outboundTag),
+ () => _ffiSelectOutbound(groupTag, outboundTag),
allowSyncFallback: false,
);
if (err != null && err.isNotEmpty) {
@@ -465,7 +507,7 @@ class FFISingboxService with InfraLogger implements SingboxService {
return TaskEither(() async {
try {
final err = await IsolateWorker().execute(
- () => _ffiUrlTest(groupTag),
+ () => _ffiUrlTest(groupTag),
allowSyncFallback: false,
);
if (err != null && err.isNotEmpty) {
@@ -484,7 +526,9 @@ class FFISingboxService with InfraLogger implements SingboxService {
@override
Stream> watchLogs(String path) async* {
yield await _readLogFile(File(path));
- yield* Watcher(path, pollingDelay: const Duration(seconds: 1)).events.asyncMap((event) async {
+ yield* Watcher(path, pollingDelay: const Duration(seconds: 1))
+ .events
+ .asyncMap((event) async {
if (event.type == ChangeType.MODIFY) {
await _readLogFile(File(path));
}
@@ -502,7 +546,8 @@ class FFISingboxService with InfraLogger implements SingboxService {
Future> _readLogFile(File file) async {
if (_logFilePosition == 0 && file.lengthSync() == 0) return [];
- final content = await file.openRead(_logFilePosition).transform(utf8.decoder).join();
+ final content =
+ await file.openRead(_logFilePosition).transform(utf8.decoder).join();
_logFilePosition = file.lengthSync();
final lines = const LineSplitter().convert(content);
if (lines.length > 300) {
@@ -527,7 +572,8 @@ class FFISingboxService with InfraLogger implements SingboxService {
return TaskEither(() async {
try {
final result = await IsolateWorker().execute(
- () => _ffiGenerateWarpConfig(licenseKey, previousAccountId, previousAccessToken),
+ () => _ffiGenerateWarpConfig(
+ licenseKey, previousAccountId, previousAccessToken),
allowSyncFallback: false,
);
final ok = result.isNotEmpty && result[0] == true;
@@ -552,38 +598,45 @@ SingboxNativeLibrary _ffiLoadLibrary() {
}
String? _ffiSetup(
- String baseDir,
- String workingDir,
- String tempDir,
- int statusPort,
- int debugFlag,
- ) {
+ String baseDir,
+ String workingDir,
+ String tempDir,
+ int statusPort,
+ int debugFlag,
+) {
+ if (kDebugMode) {
+ print(
+ '🧭 [FFI][ORDER] _ffiSetup inputs statusPort=$statusPort base="$baseDir" working="$workingDir" temp="$tempDir" debug="$debugFlag"');
+ }
final box = _ffiLoadLibrary();
final err = box
.setup(
- baseDir.toNativeUtf8().cast(),
- workingDir.toNativeUtf8().cast(),
- tempDir.toNativeUtf8().cast(),
- statusPort,
- debugFlag,
- )
+ baseDir.toNativeUtf8().cast(),
+ workingDir.toNativeUtf8().cast(),
+ tempDir.toNativeUtf8().cast(),
+ statusPort,
+ debugFlag,
+ )
.cast()
.toDartString();
+ if (kDebugMode) {
+ print('🧭 [FFI][ORDER] _ffiSetup returned err="${err.isEmpty ? '' : err}"');
+ }
return err.isEmpty ? null : err;
}
String? _ffiValidateConfig(
- String path,
- String tempPath,
- int debugFlag,
- ) {
+ String path,
+ String tempPath,
+ int debugFlag,
+) {
final box = _ffiLoadLibrary();
final err = box
.parse(
- path.toNativeUtf8().cast(),
- tempPath.toNativeUtf8().cast(),
- debugFlag,
- )
+ path.toNativeUtf8().cast(),
+ tempPath.toNativeUtf8().cast(),
+ debugFlag,
+ )
.cast()
.toDartString();
return err.isEmpty ? null : err;
@@ -591,7 +644,10 @@ String? _ffiValidateConfig(
String? _ffiChangeOptions(String optionsJson) {
final box = _ffiLoadLibrary();
- final err = box.changeHiddifyOptions(optionsJson.toNativeUtf8().cast()).cast().toDartString();
+ final err = box
+ .changeHiddifyOptions(optionsJson.toNativeUtf8().cast())
+ .cast()
+ .toDartString();
return err.isEmpty ? null : err;
}
@@ -599,8 +655,8 @@ List