You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

753 lines
25 KiB

import '/data/services/user_portrait.dart';
import 'package:dio/dio.dart' as dio;
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:logger/logger.dart';
import '../../../data/models/user_Info.dart';
import 'dart:io';
//import 'dart:convert';
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:path_provider/path_provider.dart';
import '../../../data/services/network/api.dart';
import 'package:dio/dio.dart';
import '../../../routes/app_routes.dart';
//import '../../../modules/login/controllers/googleLogin.dart';
//import '../../../modules/login/controllers/facebookLogin.dart';
import '../../../data/services/account_service.dart';
//import 'package:http_parser/http_parser.dart'; // 新增此导入
class ProfileController extends GetxController {
static ProfileController get to => Get.find();
//用户信息
static const String userKey = 'user_info'; //用户信息存储键
final _storage = GetStorage(); //本地存储
final ImagePicker _picker = ImagePicker(); //图片选择器
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; // 是否正在编辑昵称(内联编辑状态)
final RxBool isAvatarReady = false.obs; // 头像数据是否已准备好,用于防止闪烁
//音乐播放管理
final _userportraitManager = Get.find<UserportraitManager>();
// 用于乐观更新的原始状态备份
String _originalName = '';
String _originalAvatar = '';
String _originalLocalAvatar = '';
// 使用响应式变量
final currentLanguage = 'zh'.obs;
// 账号服务
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 'networkConnectionFailed'.tr; //网络连接失败
} else if (errorStr.contains('server') ||
errorStr.contains('500') ||
errorStr.contains('502') ||
errorStr.contains('503')) {
return 'serverTemporarilyUnavailable'.tr; //服务器暂时不可用
} else if (errorStr.contains('401') || errorStr.contains('unauthorized')) {
return 'loginExpiredPleaseRelogin'.tr; //登录过期,请重新登录
} else if (errorStr.contains('403') || errorStr.contains('forbidden')) {
return 'noPermissionForThisOperation'.tr; //没有权限执行此操作
} else if (errorStr.contains('404')) {
return 'requestedResourceNotFound'.tr; //请求的资源不存在
} else if (errorStr.contains('file') || errorStr.contains('image')) {
return 'imageProcessingFailedPleaseReselect'.tr; //图片处理失败,请重新选择图片
} else {
return 'operationFailedPleaseRetry'.tr; //操作失败,请重试
}
}
@override
void onInit() {
super.onInit();
// 获取或注册账号服务
_accountService = Get.find<AccountService>();
loadUserInfo();
loadLanguagePreference();
}
// 加载用户语言偏好设置
void loadLanguagePreference() {
try {
final String? languageCode = _storage.read('languageCode');
final String? countryCode = _storage.read('countryCode');
if (languageCode != null && countryCode != null) {
Get.updateLocale(Locale(languageCode, countryCode));
currentLanguage.value = languageCode;
}
} catch (e) {
Logger().e('Failed to load language preference: $e');
}
}
// 保存用户语言偏好设置
Future<void> saveLanguagePreference(Locale locale) async {
try {
await _storage.write('languageCode', locale.languageCode);
await _storage.write('countryCode', locale.countryCode);
} catch (e) {
Logger().e('Failed to save language preference: $e');
}
}
// 切换应用语言
void changeLanguage(String languageCode, String countryCode) {
// 使用Future.delayed来避免在构建过程中更新UI
Future.delayed(Duration.zero, () async {
final locale = Locale(languageCode, countryCode);
await saveLanguagePreference(locale);
Get.updateLocale(locale);
currentLanguage.value = languageCode;
});
}
// Future<void> updateUserData({
// String? userName,
// String? email,
// String? avatar,
// }) async {
// try {
// if (userName != null) {
// await _storage.write('userName', userName);
// _userName.value = userName;
// }
// if (email != null) {
// await _storage.write('userEmail', email);
// _email.value = email;
// }
// if (avatar != null) {
// await _storage.write('userAvatar', avatar);
// _avatar.value = avatar;
// }
// Get.snackbar('Success', 'Profile updated successfully');
// } catch (e) {
// Get.snackbar('Error', 'Failed to update profile');
// }
// }
// void toggleEditing() {
// _isEditing.value = !_isEditing.value;
// }
// 验证base64图片数据
// Future<void> _verifyBase64Image(String base64String) async {
// try {
// // 解码base64数据
// List<int> imageBytes = base64Decode(base64String);
// // 获取应用文档目录
// final appDir = await getApplicationDocumentsDirectory();
// final verifyPath = '${appDir.path}/verify_base64_${DateTime.now().millisecondsSinceEpoch}.jpg';
// // 将解码后的数据写入文件
// final file = File(verifyPath);
// await file.writeAsBytes(imageBytes);
// // 记录验证文件的路径
// Logger().d('Base64验证图片已保存到: $verifyPath');
// // 显示图片预览对话框
// Get.dialog(
// Dialog(
// child: Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// Padding(
// padding: const EdgeInsets.all(16.0),
// child: Text('Base64 图片验证', style: TextStyle(fontWeight: FontWeight.bold)),
// ),
// Image.file(File(verifyPath)),
// Padding(
// padding: const EdgeInsets.all(16.0),
// child: ElevatedButton(
// onPressed: () => Get.back(),
// child: Text('关闭'),
// ),
// ),
// ],
// ),
// ),
// );
// Get.snackbar('验证', '已将Base64转换回图片,请查看预览');
// } catch (e) {
// Logger().e('验证Base64图片失败: $e');
// Get.snackbar('错误', 'Base64验证失败: $e');
// }
// }
// 强制回滚未保存的更改(在页面退出时调用)
void forceRollbackIfUnsaved() {
if (hasEdits.value) {
Logger().d('检测到未保存的编辑,执行强制回滚');
_rollbackToOriginalState(); // 复用现有的回滚方法
}
isEditingNickname.value = false;
}
// 更新用户信息到后端
Future<void> _syncWithBackend() async {
try {
Logger().d('开始同步用户信息到后端');
// 创建FormData对象用于上传
dio.FormData formData = dio.FormData();
// 添加基本用户信息
formData.fields.add(MapEntry('language', currentLanguage.value));
formData.fields.add(MapEntry('name', userInfo.value.name));
// 如果有头像,添加到FormData
if (localAvatar.value.isNotEmpty) {
try {
File imageFile = File(localAvatar.value);
if (await imageFile.exists()) {
// 添加文件到FormData
formData.files.add(
MapEntry(
'file',
await dio.MultipartFile.fromFile(
imageFile.path,
filename: 'avatar.jpg',
),
),
);
Logger().d('已添加头像文件到请求');
} else {
Logger().e('头像文件不存在: ${localAvatar.value}');
throw Exception('头像文件不存在');
}
} catch (e) {
Logger().e('读取头像文件失败: $e');
throw Exception('读取头像文件失败: $e');
}
}
Logger().d('准备更新用户信息: ${formData.fields}');
// 调用API,使用FormData
final response = await Api.updateUserSetting(formData);
Logger().d('--更新用户信息响应: $response');
if (response != null && response is Map<String, dynamic>) {
// 检查响应是否包含错误
if (response.containsKey('error') || response.containsKey('msg')) {
final errorMsg = response['error'] ??
response['msg'] ??
'serverReturnedError'.tr; //服务器返回错误
Logger().e('服务器返回错误: $errorMsg');
throw Exception('${'serverError'.tr}: $errorMsg'); //服务器错误
}
Logger().d('更新成功: $response');
// 更新用户信息为服务器返回的最新数据
userInfo.update((u) {
if (u != null) {
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');
throw Exception('服务器响应异常');
}
} catch (e) {
Logger().e('同步到后端失败: $e');
// 根据错误类型抛出更具体的异常
if (e.toString().contains('SocketException') ||
e.toString().contains('TimeoutException') ||
e.toString().contains('network')) {
throw Exception(
'network: ${'networkConnectionFailedCheckSettings'.tr}'); //网络连接失败,请检查网络设置
} else if (e.toString().contains('500') ||
e.toString().contains('server')) {
throw Exception(
'server: ${'serverInternalErrorRetryLater'.tr}'); //服务器内部错误,请稍后重试
} else {
throw Exception('unknown: ${e.toString()}');
}
}
}
void loadUserInfo() async {
final userJson = _storage.read(userKey);
if (userJson != null) {
userInfo.value = User.instance;
Logger().d('从本地加载用户信息: $userJson');
// 重置头像准备状态
isAvatarReady.value = false;
// 同步加载本地头像路径并验证文件有效性 - 修复闪烁问题
final savedLocalAvatar = _storage.read('local_avatar');
if (savedLocalAvatar != null && savedLocalAvatar.toString().isNotEmpty) {
final avatarPath = savedLocalAvatar.toString();
// 同步验证文件是否存在,避免异步导致的闪烁
final file = File(avatarPath);
try {
final exists = await file.exists();
if (exists) {
localAvatar.value = avatarPath;
Logger().d('加载本地头像路径: ${localAvatar.value}');
} else {
// 文件不存在,清除无效路径
localAvatar.value = '';
_storage.remove('local_avatar');
Logger().w('本地头像文件不存在,已清除路径: $avatarPath');
}
} catch (e) {
// 文件访问异常,清除无效路径
localAvatar.value = '';
_storage.remove('local_avatar');
Logger().e('验证本地头像文件时发生异常: $e');
}
} else {
// 确保本地头像路径为空
localAvatar.value = '';
Logger().d('没有本地头像路径');
}
// 头像数据准备完成
isAvatarReady.value = true;
// 保存原始状态用于回滚
_saveOriginalState();
// 如果有远程头像地址且本地头像不存在,才下载并保存到本地
if (userInfo.value.avatar.isNotEmpty && localAvatar.value.isEmpty) {
Logger().d('远程头像地址: ${userInfo.value.avatar}');
_downloadAndSaveAvatar(userInfo.value.avatar);
}
// 确保状态一致性
_ensureStateConsistency();
}
}
// 确保本地状态与服务器状态的一致性
void _ensureStateConsistency() async {
// 同步检查本地头像文件是否存在 - 修复闪烁问题
if (localAvatar.value.isNotEmpty) {
final file = File(localAvatar.value);
try {
final exists = await file.exists();
if (!exists) {
Logger().w('本地头像文件不存在,清除本地头像路径: ${localAvatar.value}');
localAvatar.value = '';
_storage.remove('local_avatar');
// 如果有远程头像,重新下载
if (userInfo.value.avatar.isNotEmpty) {
_downloadAndSaveAvatar(userInfo.value.avatar);
}
}
} catch (e) {
Logger().e('检查本地头像文件一致性时发生异常: $e');
localAvatar.value = '';
_storage.remove('local_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('取消内联编辑昵称');
}
// 下载并保存远程头像
Future<void> _downloadAndSaveAvatar(String url) async {
try {
Logger().d('开始下载头像: $url');
final dio = Dio(BaseOptions(
headers: {
'accept': '*/*',
'Authorization': _storage.read<String>("logintoken"),
},
));
final response = await dio.get(
url,
options: Options(responseType: ResponseType.bytes),
);
Logger().d('下载状态: ${response.statusCode}');
if (response.statusCode == 200) {
// 保存到应用文档目录
final appDir = await getApplicationSupportDirectory();
final fileName =
'/UserAvatar/avatar_${DateTime.now().millisecondsSinceEpoch}.jpg';
final localPath = '${appDir.path}/$fileName';
// 写入文件
final file = File(localPath);
await file.parent.create(recursive: true);
await file.writeAsBytes(response.data);
// 更新本地头像路径
localAvatar.value = localPath;
await _storage.write('local_avatar', localPath);
Logger().d('头像已下载到本地: $localPath');
//Get.snackbar('头像', '头像已下载到本地');
}
} catch (e) {
Logger().e('下载头像失败: $e');
}
}
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');
}
}
// 更新用户头像
Future<void> updateUserAvatar() async {
final XFile? image = await _picker.pickImage(
source: ImageSource.gallery, // 从相册选择
maxWidth: 800,
maxHeight: 800,
imageQuality: 85,
);
if (image != null) {
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('error'.tr, 'imageCompressionFailed'.tr); //图片压缩失败
return;
}
//更新文件大小和质量
fileSize = await compressedImage.length();
quality -= 5;
Logger().d('压缩后文件大小:$fileSize');
}
// 乐观更新:立即更新UI
userInfo.update((user) {
if (user != null) {
user.avatar = image.path;
}
});
// 更新本地头像路径
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(
'avatarUpdateFailed'.tr,
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('error'.tr, 'usernameCannotBeEmpty'.tr); //用户名不能为空
return;
}
// 显示加载状态
isUpdating.value = true;
try {
Logger().d('开始保存用户信息 - 名称: ${name.trim()}, 本地头像: ${localAvatar.value}');
// 同步到后端
await _syncWithBackend();
// 同步成功后更新原始状态和本地存储
_saveOriginalState();
await _syncLocalStorage();
// 重置编辑状态
hasEdits.value = false;
Logger().d('用户信息保存成功');
// 显示成功提示
Get.snackbar(
'saveSuccess'.tr,
'personalInfoUpdated'.tr,
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(
SnackBar(
content: Row(
children: [
Icon(Icons.check_circle, color: Colors.white),
SizedBox(width: 8),
Text('personalInfoUpdated'.tr),
],
),
backgroundColor: Colors.green,
duration: Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
),
);
}
});
} catch (e) {
Logger().e('更新用户名和头像错误: $e');
// 同步失败时回滚到原始状态
await _rollbackToOriginalState();
// 显示错误提示
final errorMessage = _getErrorMessage(e);
Get.snackbar(
'saveFailed'.tr,
errorMessage,
duration: const Duration(seconds: 4),
snackPosition: SnackPosition.TOP,
); //保存失败
} finally {
// 隐藏加载状态
isUpdating.value = false;
}
}
// 清理用户缓存
Future<void> clean() async {
await _userportraitManager.clean();
}
// 退出登录
Future<void> signOut() async {
try {
// 显示加载提示
Get.dialog(
const Center(child: CircularProgressIndicator()),
barrierDismissible: false,
);
// 调用账号服务执行登出
final result = await _accountService.signOut();
// 关闭加载提示
Get.back();
if (result.success) {
// 先显示成功提示
Get.snackbar('success'.tr, result.message ?? 'loggedOut'.tr); //已退出登录
Logger().d('退出登录成功');
// 然后导航到登录页面
if (Get.currentRoute != Routes.login) {
await Get.offAllNamed(Routes.login);
}
} else {
Get.snackbar('error'.tr,
result.errorMessage ?? 'logoutFailedRetry'.tr); //退出登录失败,请重试
Logger().e('退出登录失败: ${result.errorMessage}');
}
} catch (e) {
// 确保加载提示被关闭
if (Get.isDialogOpen ?? false) {
Get.back();
}
Get.snackbar('error'.tr, 'logoutFailedRetry'.tr); //退出登录失败,请重试
Logger().e('退出登录错误: $e');
}
}
// 注销账号
Future<void> deleteAccount() async {
try {
// 显示加载提示
Get.dialog(
const Center(child: CircularProgressIndicator()),
barrierDismissible: false,
);
// 调用账号服务执行账号删除
final result = await _accountService.deleteAccount();
// 关闭加载提示
Get.back();
if (result.success) {
// 先显示成功提示
Get.snackbar('success'.tr,
result.message ?? 'accountPermanentlyDeleted'.tr); //账号已被永久删除
Logger().d('账号删除成功');
// 然后导航到登录页面
if (Get.currentRoute != Routes.login) {
await Get.offAllNamed(Routes.login);
}
} else {
Get.snackbar('error'.tr,
result.errorMessage ?? 'deleteAccountFailedRetry'.tr); //删除账号失败,请重试
Logger().e('删除账号失败: ${result.errorMessage}');
}
} catch (e) {
// 确保加载提示被关闭
if (Get.isDialogOpen ?? false) {
Get.back();
}
Get.snackbar('error'.tr, 'deleteAccountFailedRetry'.tr); //删除账号失败,请重试
Logger().e('删除账号失败: $e');
}
}
}