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
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:
@@ -0,0 +1,12 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../controllers/hi_user_info_controller.dart';
|
||||
|
||||
class HIUserInfoBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<HIUserInfoController>(
|
||||
() => HIUserInfoController(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
|
||||
import 'package:kaer_with_panels/app/services/api_service/kr_api.user.dart';
|
||||
import 'package:kaer_with_panels/app/widgets/dialogs/kr_dialog.dart';
|
||||
import 'package:kaer_with_panels/app/common/app_run_data.dart';
|
||||
import 'package:kaer_with_panels/app/services/api_service/kr_auth_api.dart';
|
||||
import 'package:kaer_with_panels/app/services/kr_site_config_service.dart';
|
||||
import 'package:kaer_with_panels/app/services/kr_device_info_service.dart';
|
||||
import 'package:kaer_with_panels/app/services/kr_subscribe_service.dart';
|
||||
import 'package:kaer_with_panels/app/model/enum/kr_request_type.dart';
|
||||
import 'package:kaer_with_panels/app/localization/app_translations.dart';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'package:kaer_with_panels/utils/snackbar_util.dart';
|
||||
import 'package:kaer_with_panels/app/routes/app_pages.dart';
|
||||
|
||||
class HIUserInfoController extends GetxController {
|
||||
/// 订阅服务
|
||||
final KRSubscribeService kr_subscribeService = KRSubscribeService();
|
||||
|
||||
// 设备列表
|
||||
final RxList<Map<String, dynamic>> devices = <Map<String, dynamic>>[].obs;
|
||||
|
||||
// 加载状态
|
||||
final RxBool isLoading = true.obs;
|
||||
|
||||
// 当前设备ID
|
||||
String? currentDeviceId;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
_initDeviceId();
|
||||
loadDeviceList();
|
||||
}
|
||||
|
||||
/// 初始化当前设备ID
|
||||
Future<void> _initDeviceId() async {
|
||||
try {
|
||||
currentDeviceId = KRDeviceInfoService().deviceId ?? 'unknown';
|
||||
KRLogUtil.kr_i('当前设备ID: $currentDeviceId', tag: 'DeviceManagement');
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('获取设备ID失败: $e', tag: 'DeviceManagement');
|
||||
}
|
||||
}
|
||||
|
||||
/// 加载设备列表
|
||||
Future<void> loadDeviceList() async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
KRLogUtil.kr_i('开始加载设备列表', tag: 'DeviceManagement');
|
||||
|
||||
// 调用API获取设备列表
|
||||
final result = await KRUserApi().kr_getUserDevices();
|
||||
|
||||
result.fold(
|
||||
(error) {
|
||||
KRLogUtil.kr_e('加载设备列表失败: ${error.msg}', tag: 'DeviceManagement');
|
||||
KRSnackBarUtil.show(AppTranslations.kr_dialog.error, error.msg);
|
||||
},
|
||||
(deviceList) {
|
||||
KRLogUtil.kr_i('获取到 ${deviceList.length} 个设备', tag: 'DeviceManagement');
|
||||
|
||||
|
||||
// 转换设备数据格式
|
||||
devices.value = deviceList.map((device) {
|
||||
final identifier = device['identifier']?.toString() ?? '';
|
||||
final isCurrent = identifier == currentDeviceId;
|
||||
|
||||
return {
|
||||
'id': device['id']?.toString() ?? '',
|
||||
'identifier': identifier,
|
||||
'device_name': device['user_agent'] ?? '未知设备',
|
||||
'ip': device['ip'] ?? '',
|
||||
'last_login': device['updated_at'] ?? device['created_at'] ?? '',
|
||||
'is_current': isCurrent,
|
||||
'enabled': device['enabled'] ?? true,
|
||||
'online': device['online'] ?? false,
|
||||
};
|
||||
}).toList()
|
||||
..sort((a, b) {
|
||||
if (a['is_current'] == true) return -1;
|
||||
if (b['is_current'] == true) return 1;
|
||||
return 0;
|
||||
});
|
||||
},
|
||||
);
|
||||
} catch (e, stackTrace) {
|
||||
KRLogUtil.kr_e('加载设备列表异常: $e', tag: 'DeviceManagement');
|
||||
KRLogUtil.kr_e('堆栈跟踪: $stackTrace', tag: 'DeviceManagement');
|
||||
KRSnackBarUtil.show(AppTranslations.kr_dialog.error, AppTranslations.kr_deviceManagement.loadDeviceListFailed);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// 删除设备
|
||||
Future<bool> deleteDevice(String id) async {
|
||||
try {
|
||||
final device = devices.firstWhere(
|
||||
(d) => d['id'] == id,
|
||||
orElse: () => {},
|
||||
);
|
||||
if (device.isEmpty) return false;
|
||||
|
||||
final isCurrent = device['is_current'] ?? false;
|
||||
|
||||
KRLogUtil.kr_i('开始解绑设备 - id: $id, isCurrent: $isCurrent', tag: 'DeviceManagement');
|
||||
|
||||
final result = await KRUserApi().kr_unbindUserDevice(id);
|
||||
|
||||
bool success = false;
|
||||
result.fold(
|
||||
(error) {
|
||||
KRLogUtil.kr_e('删除设备失败: ${error.msg}', tag: 'DeviceManagement');
|
||||
KRSnackBarUtil.show(AppTranslations.kr_dialog.error, AppTranslations.kr_deviceManagement.deleteFailed(error.msg));
|
||||
},
|
||||
(_) async {
|
||||
KRLogUtil.kr_i('设备删除成功', tag: 'DeviceManagement');
|
||||
success = true;
|
||||
|
||||
if (isCurrent) {
|
||||
// 如果删除的是本机设备,重新进行设备登录
|
||||
KRLogUtil.kr_i('本机设备已删除,准备重新登录', tag: 'DeviceManagement');
|
||||
// 执行重新登录
|
||||
await _reloginWithDevice();
|
||||
} else {
|
||||
devices.removeWhere((device) => device['id'] == id);
|
||||
KRSnackBarUtil.show(AppTranslations.kr_dialog.success, AppTranslations.kr_deviceManagement.deleteSuccess);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return success;
|
||||
} catch (e, stackTrace) {
|
||||
KRLogUtil.kr_e('删除设备异常: $e', tag: 'DeviceManagement');
|
||||
KRLogUtil.kr_e('堆栈跟踪: $stackTrace', tag: 'DeviceManagement');
|
||||
KRSnackBarUtil.show(AppTranslations.kr_dialog.error, AppTranslations.kr_deviceManagement.deleteFailed(e.toString()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// 重新使用设备登录
|
||||
Future<void> _reloginWithDevice() async {
|
||||
try {
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'DeviceManagement');
|
||||
KRLogUtil.kr_i('开始重新进行设备登录', tag: 'DeviceManagement');
|
||||
KRLogUtil.kr_i('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', tag: 'DeviceManagement');
|
||||
|
||||
// 先清除当前的用户信息(但不调用 kr_loginOut,避免显示登录界面)
|
||||
final appRunData = KRAppRunData.getInstance();
|
||||
appRunData.kr_isLogin.value = false;
|
||||
appRunData.kr_token = null;
|
||||
appRunData.kr_account.value = null;
|
||||
appRunData.kr_userId.value = null;
|
||||
|
||||
KRLogUtil.kr_i('设备登录已启用,开始调用设备登录接口', tag: 'DeviceManagement');
|
||||
|
||||
// 初始化设备信息服务(如果还没初始化)
|
||||
await KRDeviceInfoService().initialize();
|
||||
|
||||
// 调用设备登录接口
|
||||
final authApi = KRAuthApi();
|
||||
final result = await authApi.kr_deviceLogin();
|
||||
|
||||
result.fold(
|
||||
(error) {
|
||||
// 设备登录失败
|
||||
KRLogUtil.kr_e('设备登录失败: ${error.msg}', tag: 'DeviceManagement');
|
||||
KRSnackBarUtil.show(AppTranslations.kr_dialog.error, AppTranslations.kr_deviceManagement.reloginFailed(error.msg));
|
||||
|
||||
// 执行完整退出登录,显示登录界面
|
||||
appRunData.kr_loginOut();
|
||||
},
|
||||
(token) async {
|
||||
// 设备登录成功
|
||||
KRLogUtil.kr_i('✅ 设备登录成功!', tag: 'DeviceManagement');
|
||||
KRLogUtil.kr_i('🎫 Token: ${token.substring(0, min(20, token.length))}...', tag: 'DeviceManagement');
|
||||
|
||||
// 保存新的用户信息
|
||||
final deviceId = KRDeviceInfoService().deviceId ?? 'unknown';
|
||||
await appRunData.kr_saveUserInfo(
|
||||
token,
|
||||
'device_$deviceId',
|
||||
KRLoginType.kr_email,
|
||||
null,
|
||||
);
|
||||
|
||||
KRLogUtil.kr_i('✅ 设备重新登录成功,已更新用户信息', tag: 'DeviceManagement');
|
||||
|
||||
// 等待一小段时间,确保登录状态已经更新
|
||||
await Future.delayed(const Duration(milliseconds: 300));
|
||||
|
||||
// 刷新订阅信息
|
||||
KRLogUtil.kr_i('🔄 开始刷新订阅信息...', tag: 'DeviceManagement');
|
||||
try {
|
||||
await KRSubscribeService().kr_refreshAll();
|
||||
KRLogUtil.kr_i('✅ 订阅信息刷新成功', tag: 'DeviceManagement');
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('订阅信息刷新失败: $e', tag: 'DeviceManagement');
|
||||
}
|
||||
|
||||
KRSnackBarUtil.show(AppTranslations.kr_dialog.success, '退出登录成功');
|
||||
Get.offAllNamed(Routes.KR_HOME);
|
||||
},
|
||||
);
|
||||
} catch (e, stackTrace) {
|
||||
KRLogUtil.kr_e('设备重新登录异常: $e', tag: 'DeviceManagement');
|
||||
KRLogUtil.kr_e('堆栈跟踪: $stackTrace', tag: 'DeviceManagement');
|
||||
KRSnackBarUtil.show(AppTranslations.kr_dialog.error, AppTranslations.kr_deviceManagement.reloginFailedGeneric);
|
||||
|
||||
// 发生异常,执行完整退出登录
|
||||
await KRAppRunData.getInstance().kr_loginOut();
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取设备类型和图标
|
||||
Map<String, dynamic> getDeviceTypeInfo(String userAgent) {
|
||||
String deviceType = AppTranslations.kr_deviceManagement.deviceTypeUnknown;
|
||||
String iconName = 'devices';
|
||||
|
||||
if (userAgent.contains('Android') || userAgent.toLowerCase().contains('android')) {
|
||||
deviceType = AppTranslations.kr_deviceManagement.deviceTypeAndroid;
|
||||
iconName = 'phone_android';
|
||||
} else if (userAgent.contains('iOS') || userAgent.contains('iPhone') || userAgent.toLowerCase().contains('ios')) {
|
||||
deviceType = AppTranslations.kr_deviceManagement.deviceTypeIos;
|
||||
iconName = 'phone_iphone';
|
||||
} else if (userAgent.contains('iPad')) {
|
||||
deviceType = AppTranslations.kr_deviceManagement.deviceTypeIpad;
|
||||
iconName = 'tablet';
|
||||
} else if (userAgent.contains('macOS') || userAgent.contains('Mac') || userAgent.toLowerCase().contains('mac')) {
|
||||
deviceType = AppTranslations.kr_deviceManagement.deviceTypeMacos;
|
||||
iconName = 'desktop_mac';
|
||||
} else if (userAgent.contains('Windows') || userAgent.toLowerCase().contains('windows')) {
|
||||
deviceType = AppTranslations.kr_deviceManagement.deviceTypeWindows;
|
||||
iconName = 'computer';
|
||||
} else if (userAgent.contains('Linux') || userAgent.toLowerCase().contains('linux')) {
|
||||
deviceType = AppTranslations.kr_deviceManagement.deviceTypeLinux;
|
||||
iconName = 'computer';
|
||||
}
|
||||
|
||||
return {
|
||||
'type': deviceType,
|
||||
'icon': iconName,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
super.onReady();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
+593
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user