fix: 修改样式
This commit is contained in:
parent
343891b702
commit
73480ce417
@ -6,7 +6,6 @@
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.associated-domains</key>
|
||||
<array>
|
||||
<string>applinks:alf57p.openinstall.com</string>
|
||||
<string>applinks:alf57p.oplinking.com</string>
|
||||
</array>
|
||||
<key>com.apple.developer.networking.networkextension</key>
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.associated-domains</key>
|
||||
<array>
|
||||
<string>applinks:alf57p.openinstall.com</string>
|
||||
<string>applinks:alf57p.oplinking.com</string>
|
||||
</array>
|
||||
<key>com.apple.developer.networking.networkextension</key>
|
||||
|
||||
@ -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']),
|
||||
|
||||
@ -132,12 +132,12 @@ class _HINodePageViewState extends State<HINodePageView> {
|
||||
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<HINodePageView> {
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@ -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);
|
||||
},
|
||||
);
|
||||
|
||||
@ -273,6 +273,7 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
|
||||
confirmText: '返回',
|
||||
// 3. onCancel 对应“确认注销”按钮的点击事件
|
||||
onCancel: () {
|
||||
Get.back();
|
||||
// 执行页面跳转到注销账户页面
|
||||
Get.toNamed(Routes.KR_DELETE_ACCOUNT);
|
||||
},
|
||||
@ -547,7 +548,7 @@ class HIUserInfoView extends GetView<HIUserInfoController> {
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
style: TextStyle(
|
||||
fontSize: 10.sp,
|
||||
fontSize: 12.sp,
|
||||
color: Colors.black, //
|
||||
),
|
||||
children: <TextSpan>[
|
||||
|
||||
@ -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 =
|
||||
|
||||
@ -152,7 +152,7 @@ class KRInviteController extends GetxController {
|
||||
|
||||
/// 处理绑定邀请码
|
||||
Future<void> kr_handleBindInviteCode() async {
|
||||
final text = otherInviteCodeController.text;
|
||||
final text = otherInviteCodeController.text.trim();
|
||||
if (text.isEmpty) {
|
||||
KRCommonUtil.kr_showToast('请输入邀请码');
|
||||
return;
|
||||
|
||||
@ -172,9 +172,10 @@ class KRInviteView extends GetView<KRInviteController> {
|
||||
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,
|
||||
|
||||
@ -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<String> kr_emailHistory = <String>[].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<dynamic> decodedList = jsonDecode(historyString);
|
||||
kr_emailHistory.assignAll(decodedList.cast<String>());
|
||||
} 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<void> 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<String> history = [];
|
||||
if (historyString != null && historyString.isNotEmpty) {
|
||||
try {
|
||||
final List<dynamic> decodedList = jsonDecode(historyString);
|
||||
history = decodedList.cast<String>();
|
||||
} 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;
|
||||
|
||||
// 登录/注册成功后,发送消息触发订阅服务刷新
|
||||
|
||||
@ -124,10 +124,17 @@ class KRLoginView extends GetView<KRLoginController> {
|
||||
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<KRLoginController> {
|
||||
required TextEditingController controller,
|
||||
required String hintText,
|
||||
bool isPassword = false,
|
||||
List<String>? suffixes,
|
||||
List<String>? 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<KRLoginController> {
|
||||
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<String>(
|
||||
textEditingController: controller,
|
||||
focusNode: FocusNode(),
|
||||
optionsBuilder: (TextEditingValue textEditingValue) {
|
||||
final inputText = textEditingValue.text;
|
||||
|
||||
// 结果列表
|
||||
List<String> 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<String> onSelected,
|
||||
Iterable<String> 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -247,38 +247,6 @@ class KRPurchaseMembershipView extends GetView<KRPurchaseMembershipController>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// 账号部分
|
||||
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<KRPurchaseMembershipController>
|
||||
style: TextStyle(
|
||||
color: Colors.black, // 白色文字更清晰
|
||||
fontSize: 14,
|
||||
fontWeight: index == 1
|
||||
? FontWeight.w300
|
||||
: FontWeight.w600,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -179,7 +179,6 @@ class HIBaseScaffold extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
color: Colors.black, // 副标题使用带透明度的白色
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w100,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@ -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<String, Unit> 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<String, String> 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<String, Unit> 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<String, Unit> 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<Utf8>().toDartString();
|
||||
if (kDebugMode) {
|
||||
print(
|
||||
'📡 [FFI][ORDER] watchStats startCommandClient port=${receiver.sendPort.nativePort}');
|
||||
}
|
||||
final err = _box
|
||||
.startCommandClient(1, receiver.sendPort.nativePort)
|
||||
.cast<Utf8>()
|
||||
.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<Utf8>().toDartString();
|
||||
if (kDebugMode) {
|
||||
print(
|
||||
'📡 [FFI][ORDER] watchGroups startCommandClient port=${receiver.sendPort.nativePort}');
|
||||
}
|
||||
final err = _box
|
||||
.startCommandClient(5, receiver.sendPort.nativePort)
|
||||
.cast<Utf8>()
|
||||
.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<Utf8>().toDartString();
|
||||
if (kDebugMode) {
|
||||
print(
|
||||
'📡 [FFI][ORDER] watchActiveGroups startCommandClient port=${receiver.sendPort.nativePort}');
|
||||
}
|
||||
final err = _box
|
||||
.startCommandClient(13, receiver.sendPort.nativePort)
|
||||
.cast<Utf8>()
|
||||
.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<List<String>> 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<List<String>> _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<Utf8>()
|
||||
.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<Utf8>()
|
||||
.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<Utf8>().toDartString();
|
||||
final err = box
|
||||
.changeHiddifyOptions(optionsJson.toNativeUtf8().cast())
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
return err.isEmpty ? null : err;
|
||||
}
|
||||
|
||||
@ -599,8 +655,8 @@ List<Object?> _ffiGenerateFullConfig(String path) {
|
||||
final box = _ffiLoadLibrary();
|
||||
final response = box
|
||||
.generateConfig(
|
||||
path.toNativeUtf8().cast(),
|
||||
)
|
||||
path.toNativeUtf8().cast(),
|
||||
)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
if (response.startsWith("error")) {
|
||||
@ -613,9 +669,9 @@ String? _ffiStart(String configPath, bool disableMemoryLimit) {
|
||||
final box = _ffiLoadLibrary();
|
||||
final err = box
|
||||
.start(
|
||||
configPath.toNativeUtf8().cast(),
|
||||
disableMemoryLimit ? 1 : 0,
|
||||
)
|
||||
configPath.toNativeUtf8().cast(),
|
||||
disableMemoryLimit ? 1 : 0,
|
||||
)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
return err.isEmpty ? null : err;
|
||||
@ -631,9 +687,9 @@ String? _ffiRestart(String configPath, bool disableMemoryLimit) {
|
||||
final box = _ffiLoadLibrary();
|
||||
final err = box
|
||||
.restart(
|
||||
configPath.toNativeUtf8().cast(),
|
||||
disableMemoryLimit ? 1 : 0,
|
||||
)
|
||||
configPath.toNativeUtf8().cast(),
|
||||
disableMemoryLimit ? 1 : 0,
|
||||
)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
return err.isEmpty ? null : err;
|
||||
@ -643,9 +699,9 @@ String? _ffiSelectOutbound(String groupTag, String outboundTag) {
|
||||
final box = _ffiLoadLibrary();
|
||||
final err = box
|
||||
.selectOutbound(
|
||||
groupTag.toNativeUtf8().cast(),
|
||||
outboundTag.toNativeUtf8().cast(),
|
||||
)
|
||||
groupTag.toNativeUtf8().cast(),
|
||||
outboundTag.toNativeUtf8().cast(),
|
||||
)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
return err.isEmpty ? null : err;
|
||||
@ -653,22 +709,23 @@ String? _ffiSelectOutbound(String groupTag, String outboundTag) {
|
||||
|
||||
String? _ffiUrlTest(String groupTag) {
|
||||
final box = _ffiLoadLibrary();
|
||||
final err = box.urlTest(groupTag.toNativeUtf8().cast()).cast<Utf8>().toDartString();
|
||||
final err =
|
||||
box.urlTest(groupTag.toNativeUtf8().cast()).cast<Utf8>().toDartString();
|
||||
return err.isEmpty ? null : err;
|
||||
}
|
||||
|
||||
List<Object?> _ffiGenerateWarpConfig(
|
||||
String licenseKey,
|
||||
String previousAccountId,
|
||||
String previousAccessToken,
|
||||
) {
|
||||
String licenseKey,
|
||||
String previousAccountId,
|
||||
String previousAccessToken,
|
||||
) {
|
||||
final box = _ffiLoadLibrary();
|
||||
final response = box
|
||||
.generateWarpConfig(
|
||||
licenseKey.toNativeUtf8().cast(),
|
||||
previousAccountId.toNativeUtf8().cast(),
|
||||
previousAccessToken.toNativeUtf8().cast(),
|
||||
)
|
||||
licenseKey.toNativeUtf8().cast(),
|
||||
previousAccountId.toNativeUtf8().cast(),
|
||||
previousAccessToken.toNativeUtf8().cast(),
|
||||
)
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
if (response.startsWith("error:")) {
|
||||
|
||||
@ -564,6 +564,12 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/usr/lib/swift,
|
||||
"'@executable_path/../Frameworks'",
|
||||
"\"${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}\"",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = macosx;
|
||||
@ -581,10 +587,11 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = NJRRF427XB;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = NJRRF427XB;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN;
|
||||
@ -595,6 +602,7 @@
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.taw.hifastvpn.mac;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "HiFastVPN-Mac-Pord";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Profile;
|
||||
@ -654,6 +662,12 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/usr/lib/swift,
|
||||
"'@executable_path/../Frameworks'",
|
||||
"\"${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}\"",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
@ -703,6 +717,12 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/usr/lib/swift,
|
||||
"'@executable_path/../Frameworks'",
|
||||
"\"${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}\"",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = macosx;
|
||||
@ -720,10 +740,11 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = NJRRF427XB;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = NJRRF427XB;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN;
|
||||
@ -734,6 +755,7 @@
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.taw.hifastvpn.mac;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "HiFastVPN-Mac-Pord";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
@ -748,10 +770,11 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = NJRRF427XB;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = NJRRF427XB;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = HiFastVPN;
|
||||
@ -762,6 +785,7 @@
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.taw.hifastvpn.mac;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "HiFastVPN-Mac-Pord";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@ -3,23 +3,19 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>$(TeamIdentifierPrefix)</string>
|
||||
</array>
|
||||
<key>com.apple.security.assets.pictures.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.personal-information.photos-library</key>
|
||||
<true/>
|
||||
<false/>
|
||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-executable-page-protection</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
|
||||
@ -3,19 +3,19 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.assets.pictures.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.personal-information.photos-library</key>
|
||||
<true/>
|
||||
<false/>
|
||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-executable-page-protection</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
|
||||
@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.0.0+102
|
||||
version: 1.0.0+103
|
||||
|
||||
environment:
|
||||
sdk: ">=3.5.0 <4.0.0"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user