feat: 更新代码仓库全部修改
Build Android APK / 编译 libcore.aar (push) Has been cancelled
Build Android APK / 编译 Android APK (release) (push) Has been cancelled
Build Android APK / 创建 GitHub Release (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Android) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Windows) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (macOS) (push) Has been cancelled
Build Multi-Platform / 编译 libcore (Linux) (push) Has been cancelled
Build Multi-Platform / 构建 Android APK (push) Has been cancelled
Build Multi-Platform / 构建 Windows (push) Has been cancelled
Build Multi-Platform / 构建 macOS (push) Has been cancelled
Build Multi-Platform / 构建 Linux (push) Has been cancelled
Build Multi-Platform / 创建 Release (push) Has been cancelled
Build Windows / build (push) Has been cancelled

This commit is contained in:
2025-10-30 04:47:53 -07:00
parent 145832093e
commit f42a481452
134 changed files with 8032 additions and 4270 deletions
+593
View File
@@ -0,0 +1,593 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import '../../../model/response/kr_message_list.dart';
import '../controllers/hi_user_info_controller.dart';
import 'package:kaer_with_panels/app/widgets/kr_app_text_style.dart';
import 'package:kaer_with_panels/app/localization/app_translations.dart';
import 'package:kaer_with_panels/app/widgets/hi_base_scaffold.dart';
import 'package:kaer_with_panels/app/widgets/hi_collapsible_list.dart';
import 'package:kaer_with_panels/app/widgets/hi_fixed_scrollbar.dart';
import 'package:kaer_with_panels/app/modules/hi_menu/widgets/hi_menu_list_item.dart';
import '../../../routes/app_pages.dart';
import 'package:kaer_with_panels/app/widgets/kr_local_image.dart';
import 'package:kaer_with_panels/app/widgets/hi_help_entrance.dart';
import 'package:kaer_with_panels/app/common/app_run_data.dart';
import 'package:kaer_with_panels/app/modules/kr_home/controllers/kr_home_controller.dart';
import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart';
class HIUserInfoView extends GetView<HIUserInfoController> {
const HIUserInfoView({super.key});
@override
Widget build(BuildContext context) {
final isDeviceLogin = KRAppRunData.getInstance().isDeviceLogin();
return HIBaseScaffold(
child: Stack(
children: [
// 👇 核心改动 1: 使用 Column 分割滚动区和固定区
Column(
children: [
// 👇 核心改动 2: 使用 Expanded 包裹滚动区,使其填充可用空间
Expanded(
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 40.w),
child: Column(
children: [
SizedBox(height: 20.w),
// he
Padding(
padding: EdgeInsets.symmetric(horizontal: 0.w),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 60.w,
height: 60.w,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(18.r),
),
alignment: Alignment.center,
child: KrLocalImage(
imageName: 'hi-home-logo',
imageType: ImageType.svg,
width: 30.w,
height: 30.w,
color: Colors.black,
),
),
SizedBox(width: 13.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Obx(() {
final account = KRAppRunData.getInstance().kr_account.value;
return Text(
(account != null && account.isNotEmpty) ? account : '未绑定',
style: TextStyle(
color: Colors.white,
fontSize: 20.sp,
fontWeight: FontWeight.bold,
height: 0.9,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
);
}),
Obx(() {
final userId = KRAppRunData.getInstance().kr_userId.value;
return Text(
'ID: ${(userId != null && userId.toString().isNotEmpty) ? userId : ''}',
style: TextStyle(
color: Colors.white.withOpacity(0.85),
fontSize: 14.sp,
fontWeight: FontWeight.w500,
),
);
}),
Obx(() {
final currentSubscribe =
controller.kr_subscribeService.kr_currentSubscribe.value;
String expiryText;
if (currentSubscribe == null) {
expiryText = '尚未购买套餐';
} else {
final now = DateTime.now();
DateTime? expireDateTime;
try {
expireDateTime =
DateTime.parse(currentSubscribe.expireTime);
} catch (e) {
expireDateTime = null;
}
if (expireDateTime == null) {
expiryText = '套餐信息无效';
} else if (expireDateTime.isBefore(now)) {
final formattedExpireDate =
'${expireDateTime.year}/${expireDateTime.month.toString().padLeft(2, '0')}/${expireDateTime.day.toString().padLeft(2, '0')}';
expiryText = '已于 $formattedExpireDate 到期';
} else {
final year = expireDateTime.year;
final month = expireDateTime.month.toString().padLeft(2, '0');
final day = expireDateTime.day.toString().padLeft(2, '0');
final hour = expireDateTime.hour.toString().padLeft(2, '0');
final minute = expireDateTime.minute.toString().padLeft(2, '0');
final second = expireDateTime.second.toString().padLeft(2, '0');
// 2. 拼接成最终的字符串
final formattedDateTime = '$year/$month/$day $hour:$minute:$second';
expiryText = '到期时间:$formattedDateTime';
}
}
return Text(
expiryText,
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
),
);
}),
],
),
),
],
),
),
SizedBox(height: 12.w),
// 动态:如果已有账号,显示“修改密码”,否则显示“绑定邮箱”
Obx(() {
final isDeviceLogin = KRAppRunData.getInstance().isDeviceLogin();
if (!isDeviceLogin) return SizedBox.shrink();
return InkWell(
onTap: () {
if (isDeviceLogin) {
Get.toNamed(
Routes.MR_LOGIN,
arguments: {'entry': 'bind_email'},
);
} else {
Get.toNamed(
Routes.MR_LOGIN,
arguments: {'entry': 'forget_psd'},
);
}
},
child: Container(
margin: EdgeInsets.symmetric(horizontal: 0.w).copyWith(bottom: 10.w), // 在这里增加底部间距
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(22.w),
border: Border.all(color: Colors.white, width: 2),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
isDeviceLogin ? '绑定邮箱' : '修改密码',
style: TextStyle(
color: Colors.white,
fontSize: 16.sp,
fontWeight: FontWeight.w600,
),
),
const KrLocalImage(
imageName: 'arrow-right-icon',
imageType: ImageType.svg,
color: Colors.white,
),
],
),
),
);
}),
// 设备卡片列表
Obx(() {
if (controller.isLoading.value) {
// 使用 Padding 在加载指示器上方添加间距
return Padding(
padding: EdgeInsets.only(top: 20.w),
child: const Center(
child: CircularProgressIndicator(),
),
);
}
return Padding(
padding: EdgeInsets.symmetric(horizontal: 0.w),
child: GridView.builder(
// 1. 禁止 GridView 自身的滚动,因为它已经在 SingleChildScrollView 内部
physics: const NeverScrollableScrollPhysics(),
// 2. 让 GridView 根据内容自动调整高度
shrinkWrap: true,
// 3. 设置网格的配置
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, // 强制两列
crossAxisSpacing: 10.w, // 水平间距
mainAxisSpacing: 10.w, // 垂直间距
childAspectRatio: 1.5, // 宽高比,需要微调以获得最佳视觉效果
),
// 4. itemCount 和 itemBuilder 的逻辑保持不变
itemCount: controller.devices.length + 1,
// 5. 构建每个网格项
itemBuilder: (context, index) {
if (index < controller.devices.length) {
// 假设 _buildDeviceCard 接收的是 Map<String, dynamic>
return _buildDeviceCard(
context: context,
device: controller.devices[index],
onDelete: (id) {
HIDialog.show(
customMessageWidget: Padding(
padding: EdgeInsets.only(top: 16.w),
child: Text(
'请确认是否移除此设备?',
style: KrAppTextStyle(
color:
Theme.of(context).textTheme.bodyMedium?.color,
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
),
),
cancelText: '确认',
confirmText: '返回',
onConfirm: () {
// 关闭对话框
},
onCancel: () {
controller.deleteDevice(id);
}
);
},
);
} else {
// 如果是最后一个,显示“添加设备”卡片
return _buildAddDeviceCard();
}
},
),
);
}),
// 在滚动区域底部增加一些空间,避免内容紧贴按钮
SizedBox(height: 20.w),
],
),
),
),
),
// 👇 核心改动 3: 将固定在底部的按钮放在 Expanded 外部
Padding(
padding: EdgeInsets.symmetric(horizontal: 40.w),
child: Column(
children: [
GestureDetector(
onTap: () {
// 点击“注销账户”时,显示一个确认对话框
HIDialog.show(
customMessageWidget: Padding(
padding: EdgeInsets.only(top: 16.w),
child: Text(
'注销账号后,所有此账号内的剩余套餐和账户数据将被清空,无法找回。', // 1. 修改为正确的提示信息
textAlign: TextAlign.left, // 文本居中
style: KrAppTextStyle(
color: Theme.of(context).textTheme.bodyMedium?.color,
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
),
),
cancelText: '注销', // 2. 修改按钮文字,使其含义清晰
confirmText: '返回',
// 3. onCancel 对应“确认注销”按钮的点击事件
onCancel: () {
// 执行页面跳转到注销账户页面
Get.toNamed(Routes.KR_DELETE_ACCOUNT);
},
// 4. onConfirm 对应“返回”按钮的点击事件
onConfirm: () {
},
);
},
child: Container(
width: double.infinity,
padding: EdgeInsets.symmetric(vertical: 12.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.w),
border: Border.all(color: const Color(0xFFFF2ED1), width: 2),
),
alignment: Alignment.center,
child: Text(
'注销账户',
style: TextStyle(
color: const Color(0xFFFF2ED1),
fontSize: 16.sp,
fontWeight: FontWeight.w700,
),
),
),
),
SizedBox(height: 10.w),
Obx(() {
return !(KRAppRunData.getInstance().kr_account.value != null &&
KRAppRunData.getInstance().kr_account.value!.startsWith('9000'))
?
GestureDetector(
onTap: () {
HIDialog.show(
customMessageWidget: Padding(
padding: EdgeInsets.only(top: 16.w),
child: Text(
'确认要退出您的账号?', // 1. 修改为正确的提示信息
textAlign: TextAlign.center, // 文本居中
style: KrAppTextStyle(
color: Theme.of(context).textTheme.bodyMedium?.color,
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
),
),
cancelText: '确认',
confirmText: '返回',
onCancel: () async {
final currentDevice = controller.devices.firstWhere(
(device) => device['is_current'] == true,
);
final deviceId = currentDevice['id'] as String;
await controller.deleteDevice(deviceId);
},
onConfirm: () {
},
);
},
child: Container(
width: double.infinity,
padding: EdgeInsets.symmetric(vertical: 12.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.w),
border: Border.all(color: Colors.white, width: 2),
),
alignment: Alignment.center,
child: Text(
'退出登录',
style: TextStyle(
color: Colors.white,
fontSize: 16.sp,
fontWeight: FontWeight.w700,
),
),
),
)
: SizedBox.shrink();
})
],
),
),
// 为底部按钮和 HIHelpEntrance 留出空间
SizedBox(height: 100.h),
],
),
// HIHelpEntrance 仍然在 Stack 的顶层,可以正确定位
const HIHelpEntrance(),
],
),
);
}
/// 构建“当前设备信息卡”的私有方法
Widget _buildDeviceCard({
required BuildContext context,
required Map<String, dynamic> device,
required void Function(String) onDelete,
}) {
final identifier = device['identifier'] ?? '';
final isCurrentDevice = device['is_current'] ?? false;
return Stack( // 使用 Stack 来放置删除按钮
children: [
// 主体内容容器
Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 14.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24.w), // 可以适当调整圆角
border: Border.all(color: Colors.white, width: 2),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center, // 垂直居中内容
children: [
Row(
children: [
Icon(Icons.smartphone, color: Colors.white, size: 18.w),
SizedBox(width: 6.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_extractDeviceModel(device['device_name']),
style: TextStyle(
color: Colors.white,
fontSize: 10.sp, //
fontWeight: FontWeight.w600),
),
SizedBox(height: 4.h),
Text(
'ID$identifier',
style: TextStyle(
color: Colors.white.withOpacity(0.9),
fontSize: 10.sp,
fontWeight: FontWeight.w500),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
// 如果是当前设备,显示一个标记
// if (device.isCurrentDevice)
// Padding(
// padding: EdgeInsets.only(top: 0.h),
// child: Text(
// '本机设备',
// style: TextStyle(
// color: Theme.of(context).primaryColor,
// fontSize: 12.sp,
// fontWeight: FontWeight.bold,
// ),
// ),
// ),
],
),
),
// 如果不是本机设备,则显示删除按钮
if (!isCurrentDevice)
Positioned(
top: 8.w, // 调整删除按钮的位置
right: 8.w,
child: GestureDetector(
onTap: () {
final deviceId = device['id'];
onDelete(deviceId);
},
child: Container(
padding: EdgeInsets.all(4.w),
child: Icon(
Icons.close,
color: Colors.white,
size: 16.w,
),
),
),
),
],
);
}
/// 构建“添加卡”的私有方法
Widget _buildAddDeviceCard() {
return GestureDetector(
onTap: () {
print('可用设备 2222');
print('可用设备 ${controller.kr_subscribeService.kr_currentSubscribe}');
// 在这里处理点击“添加设备”的逻辑
HIDialog.show(
customMessageWidget: Padding(
padding: EdgeInsets.only(top: 16.w, bottom: 16.w),
child: Column(
mainAxisSize: MainAxisSize.min, // 让 Column 高度包裹内容
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 2. 第一个 Text 组件
Text(
'请使用邮箱登录新设备',
style: KrAppTextStyle(
color: Colors.black, // 可以使用稍浅
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 20.w),
Obx(() {
final current = controller.kr_subscribeService.kr_currentSubscribe.value;
// 如果 current 为 null,显示默认值 0
final deviceLimit = current?.deviceLimit ?? 0;
return Text(
'每个账号最多允许同时使用$deviceLimit台设备同时在线',
style: KrAppTextStyle(
color: Colors.black,
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
);
}),
],
),
),
);
},
child: Container(
// 设置背景色和圆角
decoration: BoxDecoration(
color: Theme.of(Get.context!).primaryColor,
borderRadius: BorderRadius.circular(24.w),
),
// 使用 Align 组件将内容整体靠左居中
alignment: Alignment.centerLeft,
// 使用 Padding 来实现左侧 24px 的边距
child: Padding(
padding: EdgeInsets.only(left: 24.w),
// 1. 使用 Row 来水平排列图标和文本
child: Row(
mainAxisSize: MainAxisSize.min, // 让 Row 的宽度包裹内容
children: [
// 2. 添加加号图标
Text(
'+',
style: TextStyle(
color: Colors.black,
fontSize: 18.sp, // 调整大小以匹配之前的图标
fontWeight: FontWeight.w900, // 设置非常粗的字重
),
),
SizedBox(width: 4.w), // 3. 在图标和文本之间添加一些间距
// 4. RichText 保持不变,用于显示不同样式的文本
RichText(
text: TextSpan(
style: TextStyle(
fontSize: 10.sp,
color: Colors.black, //
),
children: <TextSpan>[
TextSpan(
text: '设备:',
style: TextStyle(
fontWeight: FontWeight.w600,
),
),
TextSpan(
text: '可添加',
style: TextStyle(
fontWeight: FontWeight.w400,
),
),
],
),
),
],
),
),
),
);
}
String _extractDeviceModel(String deviceName) {
// 2. 使用正则表达式匹配括号内的内容
final RegExp regExp = RegExp(r'\((.*?)\)');
// 3. 查找第一个匹配项
final Match? match = regExp.firstMatch(deviceName);
// 4. 如果找到匹配项,返回括号内的文本;否则返回原始名称
if (match != null && match.groupCount >= 1) {
// group(1) 返回第一个捕获组的内容,即括号内的文本
return match.group(1) ?? deviceName;
} else {
// 如果没有括号,则直接返回原始设备名称
return deviceName;
}
}
}