Browse Source

feat(profile): 实现用户昵称内联编辑和优化头像更新流程

- 新增昵称内联编辑功能,替换原有的对话框编辑方式
- 添加加载状态指示器和错误处理机制
- 实现乐观更新和状态回滚功能
- 优化头像上传流程,增加压缩和验证逻辑
- 改进错误提示和用户反馈体验
newdev_shunjiawei
lxm 1 year ago
parent
commit
5d6b24021c
  1. 396
      lib/modules/profile/controllers/profile_controller.dart
  2. 254
      lib/modules/profile/views/profile_view.dart
  3. 7
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleConst.kt

396
lib/modules/profile/controllers/profile_controller.dart

@ -28,6 +28,13 @@ class ProfileController extends GetxController {
final Rx<User> userInfo = User.instance.obs; // 响应式用户信息对象,使用单例模式确保全局唯一性
final RxString localAvatar = ''.obs; // 本地头像路径,便于响应式更新UI
final RxBool hasEdits = false.obs; // 是否有编辑
final RxBool isUpdating = false.obs; // 是否正在更新用户信息
final RxBool isEditingNickname = false.obs; // 是否正在编辑昵称(内联编辑状态)
// 用于乐观更新的原始状态备份
String _originalName = '';
String _originalAvatar = '';
String _originalLocalAvatar = '';
// 使用响应式变量
final currentLanguage = 'zh'.obs;
@ -35,6 +42,33 @@ class ProfileController extends GetxController {
// 账号服务
late final AccountService _accountService;
// 错误处理工具方法
String _getErrorMessage(dynamic error) {
final errorStr = error.toString().toLowerCase();
if (errorStr.contains('network') ||
errorStr.contains('socket') ||
errorStr.contains('timeout') ||
errorStr.contains('connection')) {
return '网络连接失败,请检查网络设置后重试';
} else if (errorStr.contains('server') ||
errorStr.contains('500') ||
errorStr.contains('502') ||
errorStr.contains('503')) {
return '服务器暂时无法响应,请稍后重试';
} else if (errorStr.contains('401') || errorStr.contains('unauthorized')) {
return '登录已过期,请重新登录';
} else if (errorStr.contains('403') || errorStr.contains('forbidden')) {
return '没有权限执行此操作';
} else if (errorStr.contains('404')) {
return '请求的资源不存在';
} else if (errorStr.contains('file') || errorStr.contains('image')) {
return '图片处理失败,请重新选择图片';
} else {
return '操作失败,请重试';
}
}
@override
void onInit() {
super.onInit();
@ -158,6 +192,8 @@ class ProfileController extends GetxController {
// 更新用户信息到后端
Future<void> _syncWithBackend() async {
try {
Logger().d('开始同步用户信息到后端');
// 创建FormData对象用于上传
dio.FormData formData = dio.FormData();
@ -182,10 +218,12 @@ class ProfileController extends GetxController {
);
Logger().d('已添加头像文件到请求');
} else {
Logger().e('头像文件不存在');
Logger().e('头像文件不存在: ${localAvatar.value}');
throw Exception('头像文件不存在');
}
} catch (e) {
Logger().e('读取头像文件失败: $e');
throw Exception('读取头像文件失败: $e');
}
}
@ -194,25 +232,53 @@ class ProfileController extends GetxController {
// 调用API,使用FormData
final response = await Api.updateUserSetting(formData);
Logger().d('--更新用户信息响应: $response');
if (response != null) {
if (response != null && response is Map<String, dynamic>) {
// 检查响应是否包含错误
if (response.containsKey('error') || response.containsKey('msg')) {
final errorMsg = response['error'] ?? response['msg'] ?? '服务器返回错误';
Logger().e('服务器返回错误: $errorMsg');
throw Exception('服务器错误: $errorMsg');
}
Logger().d('更新成功: $response');
Get.snackbar('成功', '同步到服务器成功');
// 更新用户信息为服务器返回的最新数据
userInfo.update((u) {
if (u != null) {
u.name = response['name'] ?? '';
u.avatar = response['avatar'] ?? '';
u.name = response['name'] ?? u.name;
u.avatar = response['avatar'] ?? u.avatar;
}
});
// 如果服务器返回了新的头像URL,下载并保存到本地
if (response['avatar'] != null && response['avatar'].toString().isNotEmpty) {
final serverAvatar = response['avatar'].toString();
if (serverAvatar.startsWith('http')) {
// 异步下载新头像,不阻塞当前流程
_downloadAndSaveAvatar(serverAvatar);
}
}
Logger().d('更新后的用户信息: ${userInfo.value.toJson()}');
} else {
Logger().e('更新失败: $response');
Get.snackbar('错误', '同步到服务器失败,请重试');
return;
Logger().e('服务器响应为空或格式错误: $response');
throw Exception('服务器响应异常');
}
} catch (e) {
Logger().e('同步到后端失败: $e');
Get.snackbar('Error', '同步到服务器失败,请重试');
throw e;
// 根据错误类型抛出更具体的异常
if (e.toString().contains('SocketException') ||
e.toString().contains('TimeoutException') ||
e.toString().contains('network')) {
throw Exception('network: 网络连接失败,请检查网络设置');
} else if (e.toString().contains('500') ||
e.toString().contains('server')) {
throw Exception('server: 服务器内部错误,请稍后重试');
} else {
throw Exception('unknown: ${e.toString()}');
}
}
}
@ -221,12 +287,127 @@ class ProfileController extends GetxController {
if (userJson != null) {
userInfo.value = User.instance;
Logger().d('从本地加载用户信息: $userJson');
// 加载本地头像路径并验证文件有效性
final savedLocalAvatar = _storage.read('local_avatar');
if (savedLocalAvatar != null && savedLocalAvatar.toString().isNotEmpty) {
final avatarPath = savedLocalAvatar.toString();
// 验证文件是否存在
final file = File(avatarPath);
file.exists().then((exists) {
if (exists) {
localAvatar.value = avatarPath;
Logger().d('加载本地头像路径: ${localAvatar.value}');
} else {
// 文件不存在,清除无效路径
localAvatar.value = '';
_storage.remove('local_avatar');
Logger().w('本地头像文件不存在,已清除路径: $avatarPath');
}
});
} else {
// 确保本地头像路径为空
localAvatar.value = '';
Logger().d('没有本地头像路径');
}
// 保存原始状态用于回滚
_saveOriginalState();
// 如果有远程头像地址,下载并保存到本地
if (userInfo.value.avatar.isNotEmpty) {
Logger().d('远程头像地址: ${userInfo.value.avatar}');
_downloadAndSaveAvatar(userInfo.value.avatar);
}
// 确保状态一致性
_ensureStateConsistency();
}
}
// 确保本地状态与服务器状态的一致性
void _ensureStateConsistency() {
// 检查本地头像文件是否存在
if (localAvatar.value.isNotEmpty) {
final file = File(localAvatar.value);
file.exists().then((exists) {
if (!exists) {
Logger().w('本地头像文件不存在,清除本地头像路径: ${localAvatar.value}');
localAvatar.value = '';
_storage.remove('local_avatar');
// 如果有远程头像,重新下载
if (userInfo.value.avatar.isNotEmpty) {
_downloadAndSaveAvatar(userInfo.value.avatar);
}
}
});
}
}
// 保存原始状态用于乐观更新回滚
void _saveOriginalState() {
_originalName = userInfo.value.name;
_originalAvatar = userInfo.value.avatar;
_originalLocalAvatar = localAvatar.value;
Logger().d('已保存原始状态 - 名称: $_originalName, 头像: $_originalAvatar, 本地头像: $_originalLocalAvatar');
}
// 回滚到原始状态
Future<void> _rollbackToOriginalState() async {
Logger().d('开始回滚到原始状态');
// 回滚内存中的状态
userInfo.update((user) {
if (user != null) {
user.name = _originalName;
user.avatar = _originalAvatar;
}
});
localAvatar.value = _originalLocalAvatar;
// 重置编辑状态
hasEdits.value = false;
// 关键修复:回滚后立即同步到本地存储
try {
await _syncLocalStorage();
Logger().d('回滚状态已同步到本地存储');
} catch (e) {
Logger().e('回滚状态同步到本地存储失败: $e');
}
Logger().d('已回滚到原始状态 - 名称: $_originalName, 头像: $_originalAvatar, 本地头像: $_originalLocalAvatar');
}
// 开始编辑昵称(内联编辑)
void startEditingNickname() {
isEditingNickname.value = true;
Logger().d('开始内联编辑昵称');
}
// 完成编辑昵称(内联编辑)
void finishEditingNickname(String newName) {
isEditingNickname.value = false;
if (newName.trim().isNotEmpty && newName.trim() != _originalName) {
// 乐观更新
userInfo.update((user) {
if (user != null) {
user.name = newName.trim();
}
});
hasEdits.value = true;
Logger().d('昵称内联编辑完成,新名称: ${newName.trim()}');
} else {
Logger().d('昵称内联编辑取消或无变化');
}
}
// 取消编辑昵称(内联编辑)
void cancelEditingNickname() {
isEditingNickname.value = false;
Logger().d('取消内联编辑昵称');
}
// 下载并保存远程头像
@ -273,6 +454,29 @@ class ProfileController extends GetxController {
Future<void> saveUserInfo() async {
await _storage.write(userKey, userInfo.value.toJson());
Logger().d('用户信息已保存到本地存储');
}
// 同步所有相关状态到本地存储
Future<void> _syncLocalStorage() async {
try {
// 保存用户信息
await saveUserInfo();
// 同步本地头像路径
if (localAvatar.value.isNotEmpty) {
await _storage.write('local_avatar', localAvatar.value);
Logger().d('本地头像路径已保存: ${localAvatar.value}');
} else {
// 如果本地头像为空,清除存储中的路径
await _storage.remove('local_avatar');
Logger().d('本地头像路径已清除');
}
Logger().d('所有状态已同步到本地存储');
} catch (e) {
Logger().e('同步本地存储失败: $e');
}
}
// 更新用户头像
@ -285,82 +489,152 @@ class ProfileController extends GetxController {
);
if (image != null) {
// 检查文件大小
final file = File(image.path);
var fileSize = await file.length();
var quality = 85;
// 如果文件大于1MB,进行压缩
while (fileSize > 1024 * 1024 && quality > 30) {
final compressedImage = await FlutterImageCompress.compressAndGetFile(
file.absolute.path,
file.absolute.path, // 覆盖原文件
quality: quality,
minWidth: 800,
minHeight: 800,
);
try {
// 保存当前状态用于可能的回滚
_saveOriginalState();
// 检查文件大小
final file = File(image.path);
var fileSize = await file.length();
var quality = 85;
// 如果文件大于1MB,进行压缩
while (fileSize > 1024 * 1024 && quality > 30) {
final compressedImage = await FlutterImageCompress.compressAndGetFile(
file.absolute.path,
file.absolute.path, // 覆盖原文件
quality: quality,
minWidth: 800,
minHeight: 800,
);
if (compressedImage == null) {
Get.snackbar('错误', '图片压缩失败');
return;
}
if (compressedImage == null) {
Get.snackbar('错误', '图片压缩失败');
return;
//更新文件大小和质量
fileSize = await compressedImage.length();
quality -= 5;
Logger().d('压缩后文件大小:$fileSize');
}
//更新文件大小和质量
fileSize = await compressedImage.length();
quality -= 5;
Logger().d('压缩后文件大小:$fileSize');
}
userInfo.update((user) {
if (user != null) {
user.avatar = image.path;
}
});
// 乐观更新:立即更新UI
userInfo.update((user) {
if (user != null) {
user.avatar = image.path;
}
});
// 更新本地头像路径
localAvatar.value = image.path;
await _storage.write('local_avatar', image.path);
// await saveUserInfo(); //保存到本地
//await _syncWithBackend(); //同步到后端
// 通知 SettingsController 更新
// if (Get.isRegistered<SettingsController>()) {
// Get.find<SettingsController>().loadUserInfo();
// }
hasEdits.value = true; //表示有编辑
// 更新本地头像路径
localAvatar.value = image.path;
await _storage.write('local_avatar', image.path);
hasEdits.value = true; //表示有编辑
Logger().d('头像已乐观更新,等待用户保存');
} catch (e) {
Logger().e('更新头像失败: $e');
final errorMessage = _getErrorMessage(e);
Get.snackbar('头像更新失败', errorMessage,
duration: const Duration(seconds: 3),
snackPosition: SnackPosition.TOP,
);
// 发生错误时回滚状态
await _rollbackToOriginalState();
}
}
}
// 更新用户名
// 更新用户名和头像(保存操作)
Future<void> updateUserNameAndAvatar(String name) async {
if (name.trim().isEmpty) {
Logger().e('无效名称: $name');
Get.snackbar('错误', '用户名不能为空');
return;
}
// 显示加载状态
isUpdating.value = true;
try {
userInfo.update((user) {
if (user != null) {
user.name = name.trim();
Logger().d('名称更新为: ${user.name}');
}
});
Logger().d('开始保存用户信息 - 名称: ${name.trim()}, 本地头像: ${localAvatar.value}');
//await saveUserInfo(); //保存到本地
await _syncWithBackend(); //同步到后端
// 同步到后端
await _syncWithBackend();
//同步更新 SettingsController
// 同步成功后更新原始状态和本地存储
_saveOriginalState();
await _syncLocalStorage();
// 同步更新 SettingsController
if (Get.isRegistered<SettingsController>()) {
final settingsController = Get.find<SettingsController>();
settingsController.userInfo.value.name = userInfo.value.name;
settingsController.userInfo.value.avatar =
'${userInfo.value.avatar}?time=${DateTime.now().millisecondsSinceEpoch}';
//settingsController.loadUserInfo();
settingsController.userInfo.refresh();
}
// 重置编辑状态
hasEdits.value = false;
Logger().d('用户信息保存成功');
// 显示成功提示
Get.snackbar(
'保存成功',
'个人信息已更新',
duration: const Duration(seconds: 3),
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.green.withOpacity(0.1),
colorText: Colors.green.shade700,
icon: Icon(
Icons.check_circle_outline,
color: Colors.green.shade700,
),
margin: const EdgeInsets.all(16),
borderRadius: 8,
isDismissible: true,
forwardAnimationCurve: Curves.easeOutBack,
);
Logger().d('成功提示已显示');
// 备用成功提示机制 - 使用ScaffoldMessenger确保用户看到
Future.delayed(const Duration(milliseconds: 100), () {
if (Get.context != null) {
ScaffoldMessenger.of(Get.context!).showSnackBar(
const SnackBar(
content: Row(
children: [
Icon(Icons.check_circle, color: Colors.white),
SizedBox(width: 8),
Text('个人信息已更新'),
],
),
backgroundColor: Colors.green,
duration: Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
),
);
}
});
} catch (e) {
Logger().e('更新用户名和头像错误: $e');
Get.snackbar('错误', '更新用户名和头像失败');
// 同步失败时回滚到原始状态
await _rollbackToOriginalState();
// 显示错误提示
final errorMessage = _getErrorMessage(e);
Get.snackbar('保存失败', errorMessage,
duration: const Duration(seconds: 4),
snackPosition: SnackPosition.TOP,
);
} finally {
// 隐藏加载状态
isUpdating.value = false;
}
}

254
lib/modules/profile/views/profile_view.dart

@ -170,22 +170,46 @@ class EditProfileView extends GetView<ProfileController> {
actions: [
Obx(() => Visibility(
visible: controller.hasEdits.value,
child: TextButton(
onPressed: () {
// 保存用户名
controller.updateUserNameAndAvatar(nameController.text);
// 返回上一级页面
Get.back();
},
child: Text(
'save'.tr,
style: TextStyle(
fontSize: 16.sp,
color: isDarkMode ? Colors.blue[300] : Colors.blue,
fontWeight: FontWeight.w500,
),
),
),
child: controller.isUpdating.value
? Padding(
padding: EdgeInsets.all(16.w),
child: SizedBox(
width: 20.w,
height: 20.w,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
isDarkMode ? Colors.blue[300]! : Colors.blue,
),
),
),
)
: TextButton(
onPressed: () async {
// 保存用户名和头像
await controller
.updateUserNameAndAvatar(nameController.text);
// 如果保存成功(没有编辑状态),延迟返回上一级页面让用户看到成功提示
if (!controller.hasEdits.value) {
// 延迟跳转,让用户看到成功提示
Future.delayed(const Duration(milliseconds: 500),
() {
if (Get.isRegistered<ProfileController>()) {
Get.back();
}
});
}
},
child: Text(
'save'.tr,
style: TextStyle(
fontSize: 16.sp,
color:
isDarkMode ? Colors.blue[300] : Colors.blue,
fontWeight: FontWeight.w500,
),
),
),
))
],
),
@ -341,41 +365,54 @@ class EditProfileView extends GetView<ProfileController> {
);
}
// 新增:构建昵称设置项
// 新增:构建昵称设置项(双模式内联编辑)
Widget _buildNicknameSetting(BuildContext context, bool isDarkMode,
TextEditingController nameController) {
return Obx(() {
// 根据编辑状态显示不同的UI
if (controller.isEditingNickname.value) {
return _buildNicknameEditingMode(context, isDarkMode, nameController);
} else {
return _buildNicknameDisplayMode(context, isDarkMode, nameController);
}
});
}
// 构建昵称显示模式
Widget _buildNicknameDisplayMode(BuildContext context, bool isDarkMode,
TextEditingController nameController) {
return InkWell(
onTap: () => _showEditNicknameDialog(context, isDarkMode, nameController),
key: Key('nickname_edit_button'), // 添加测试Key
onTap: () {
// 开始内联编辑
nameController.text = controller.userInfo.value.name;
controller.startEditingNickname();
},
child: Padding(
padding:
EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), // 调整垂直间距
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),
child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween, // 移除
children: [
// --- 添加图标 ---
// 图标
Container(
width: 32.w,
height: 32.w,
decoration: BoxDecoration(
color: isDarkMode
? Colors.green[900]!.withOpacity(0.3) // 使用示例颜色
: Colors.green[50]!, // 使用示例颜色
borderRadius: BorderRadius.circular(16.r), // 图标背景圆角
? Colors.green[900]!.withOpacity(0.3)
: Colors.green[50]!,
borderRadius: BorderRadius.circular(16.r),
),
child: Icon(
Icons.edit_outlined, // 编辑图标
Icons.edit_outlined,
size: 18.w,
color: isDarkMode
? Colors.green[300]!
: Colors.green[600]!, // 图标颜色
color: isDarkMode ? Colors.green[300]! : Colors.green[600]!,
),
),
SizedBox(width: 12.w), // 图标和文字间距
// --- ---
SizedBox(width: 12.w),
// 标签
Expanded(
// 让文字占据剩余空间
child: Text(
'nickname'.tr, // 名字
'nickname'.tr,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w500,
@ -383,14 +420,13 @@ class EditProfileView extends GetView<ProfileController> {
),
),
),
// --- 右侧内容保持不变 ---
// 右侧内容
Row(
children: [
// 使用 Obx 监听 userInfo 的变化来显示昵称
Obx(() => Text(
controller.userInfo.value.name.isNotEmpty
? controller.userInfo.value.name
: 'notSet'.tr, // 未设置
: 'notSet'.tr,
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[600],
@ -408,87 +444,111 @@ class EditProfileView extends GetView<ProfileController> {
borderRadius: BorderRadius.circular(16.r),
),
child: Icon(
Icons.chevron_right,
Icons.edit,
size: 16.w,
color: isDarkMode ? Colors.white70 : Colors.grey[500],
),
),
],
),
// --- ---
],
),
),
);
}
// 新增:显示编辑昵称对话框的方法
void _showEditNicknameDialog(BuildContext context, bool isDarkMode,
// 构建昵称编辑模式
Widget _buildNicknameEditingMode(BuildContext context, bool isDarkMode,
TextEditingController nameController) {
// 创建一个临时的 Controller,用于对话框内的编辑,避免直接修改主 nameController
final dialogNameController =
TextEditingController(text: nameController.text);
Get.dialog(
AlertDialog(
backgroundColor: isDarkMode ? Colors.grey[900] : Colors.white,
title: Text(
'editNickname'.tr, // 编辑昵称
style: TextStyle(color: isDarkMode ? Colors.white : Colors.black87),
),
content: TextField(
controller: dialogNameController, // 使用对话框的 Controller
autofocus: true,
decoration: InputDecoration(
hintText: 'enterNickname'.tr, // 请输入昵称
hintStyle:
TextStyle(color: isDarkMode ? Colors.white54 : Colors.black38),
// 可以添加其他样式,如边框
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
borderSide: BorderSide(
color: isDarkMode ? Colors.white38 : Colors.grey[400]!),
return Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), // 与显示模式保持一致的padding
child: Row(
children: [
// 图标 - 保持与显示模式相同的绿色主题
Container(
width: 32.w,
height: 32.w,
decoration: BoxDecoration(
color: isDarkMode
? Colors.green[900]!.withOpacity(0.3)
: Colors.green[50]!,
borderRadius: BorderRadius.circular(16.r),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
borderSide: BorderSide(
color: isDarkMode ? Colors.white38 : Colors.grey[400]!),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
borderSide: BorderSide(
color: isDarkMode ? Colors.blue[300]! : Colors.blue),
child: Icon(
Icons.edit_outlined, // 保持与显示模式相同的图标
size: 18.w,
color: isDarkMode ? Colors.green[300]! : Colors.green[600]!,
),
),
style: TextStyle(color: isDarkMode ? Colors.white : Colors.black87),
),
actions: [
TextButton(
onPressed: () => Get.back(),
SizedBox(width: 12.w),
// 标签 - 保持与显示模式相同的布局
Expanded(
child: Text(
'cancel'.tr,
'nickname'.tr,
style: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.black54),
fontSize: 16.sp,
fontWeight: FontWeight.w500,
color: isDarkMode ? Colors.white : Colors.black87,
),
),
),
TextButton(
onPressed: () {
// 确认时,将对话框输入的值更新回主 nameController
nameController.text = dialogNameController.text;
controller.userInfo.update((user) {
if (user != null) {
user.name = dialogNameController.text;
}
});
controller.hasEdits.value = true; // 标记已编辑
Get.back(); // 关闭对话框
// 注意:实际的保存操作由 AppBar 中的保存按钮触发
},
child: Text(
'confirm'.tr,
style:
TextStyle(color: isDarkMode ? Colors.blue[300] : Colors.blue),
),
// 右侧内容区域 - 与显示模式布局保持一致
Row(
children: [
// 编辑输入框 - 替换原来的昵称文本显示区域
Container(
constraints: BoxConstraints(
minWidth: 80.w, // 确保有足够的编辑空间
maxWidth: 120.w, // 限制最大宽度保持布局稳定
),
child: TextField(
controller: nameController,
autofocus: true,
textAlign: TextAlign.right, // 右对齐,与原昵称显示保持一致
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.white70 : Colors.grey[600],
),
decoration: InputDecoration(
hintText: 'enterNickname'.tr,
hintStyle: TextStyle(
color: isDarkMode ? Colors.white54 : Colors.grey[500],
fontSize: 14.sp,
),
border: InputBorder.none, // 移除边框,保持简洁
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
contentPadding: EdgeInsets.zero, // 移除内边距
isDense: true,
),
onSubmitted: (value) {
// 按回车确认编辑
controller.finishEditingNickname(value);
},
onTapOutside: (event) {
// 点击外部区域确认编辑
controller.finishEditingNickname(nameController.text);
},
),
),
SizedBox(width: 8.w),
// 右侧箭头图标 - 保持与显示模式完全相同
Container(
width: 32.w,
height: 32.w,
decoration: BoxDecoration(
color: isDarkMode
? Colors.white.withOpacity(0.05)
: Colors.grey[100],
borderRadius: BorderRadius.circular(16.r),
),
child: Icon(
Icons.chevron_right,
size: 16.w,
color: isDarkMode ? Colors.white70 : Colors.grey[500],
),
),
],
),
],
),

7
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleConst.kt

@ -11,13 +11,16 @@ object BleConst {
val PRIMARY_SERVICE_UUID: UUID = UUID.fromString("0000abc0-0000-1111-2222-123456789abc")
/** 音频服务UUID - 文档中定义为00001801-0000-1000-8000-00805f9b34fb */
val AUDIO_SERVICE_UUID: UUID = UUID.fromString("0000ae00-0000-1000-8000-00805f9b34fb")
val AUDIO_SERVICE_UUID: UUID = UUID.fromString("0000ABC0-0001-1111-2222-123456789ABC")
/** 接收音频特征UUID - 文档中定义为0000ABC1-0000-1111-2222-123456789ABC */
val RECEIVE_AUDIO_CHAR_UUID: UUID = UUID.fromString("0000ae02-0000-1000-8000-00805f9b34fb")
val RECEIVE_AUDIO_CHAR_UUID: UUID = UUID.fromString("0000ABC2-0001-1111-2222-123456789ABC")
/** 写入特征UUID - 文档中定义为0000ABC1-0000-1111-2222-123456789ABC */
val WRITE_CHAR_UUID: UUID = UUID.fromString("0000abc1-0000-1111-2222-123456789abc")
/** 音频写入特征UUID - 文档中定义为0000ABC1-0000-1111-2222-123456789ABC */
val AUDIO_WRITE_CHAR_UUID: UUID = UUID.fromString("0000ABC1-0001-1111-2222-123456789ABC")
/** 通知特征UUID - 文档中定义为0000ABC2-0000-1111-2222-123456789ABC */
val NOTIFY_CHAR_UUID: UUID = UUID.fromString("0000abc2-0000-1111-2222-123456789abc")

Loading…
Cancel
Save