fix: 修改样式

This commit is contained in:
2026-01-22 16:58:54 -08:00
parent 343891b702
commit 73480ce417
22 changed files with 450 additions and 219 deletions
@@ -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,
),