Browse Source

Merge branch 'new_dev' of https://github.com/deepcloud2048/deep_voice into new_dev

weicu
tanlongsheng 1 year ago
parent
commit
0fcc99bd21
  1. 2
      lib/data/services/user_portrait.dart
  2. 357
      lib/modules/agent/views/agent_view.dart
  3. 55
      lib/modules/explore/controllers/explore_controller.dart
  4. 302
      lib/modules/explore/views/explore_view.dart
  5. 41
      lib/modules/meeting/views/meeting_mine_view.dart
  6. 40
      lib/modules/profile/controllers/profile_controller.dart
  7. 127
      lib/modules/profile/views/profile_meeting_view.dart
  8. 23
      lib/modules/profile/views/profile_view.dart
  9. 137
      lib/modules/profile/views/unified_avatar.dart
  10. 61
      lib/modules/settings/views/settings_view.dart
  11. 2
      lib/routes/app_routes.dart
  12. 27
      local_plugins/user_portrait/test/user_portrait_method_channel_test.dart
  13. 29
      local_plugins/user_portrait/test/user_portrait_test.dart

2
lib/data/services/user_portrait.dart

@ -1,4 +1,4 @@
import 'package:deep_voice/core/utils/logger.dart';
import '/core/utils/logger.dart';
import 'package:get/get.dart';
import 'package:user_portrait/user_portrait.dart';

357
lib/modules/agent/views/agent_view.dart

@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/agent_controller.dart';
@ -61,40 +62,40 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
backgroundColor: backgroundColor,
leading: IconButton(
icon: Icon(Icons.arrow_back_ios,
color: isDarkMode ? Colors.white : Colors.black87, size: 20),
color: isDarkMode ? Colors.white : Colors.black87, size: 20.sp),
onPressed: () => Get.back(),
),
titleSpacing: 0,
title: Row(
children: [
Container(
width: 40,
height: 40,
width: 40.w,
height: 40.w,
decoration: BoxDecoration(
color: primaryColor,
borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(20.w),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 3,
blurRadius: 3.w,
offset: const Offset(0, 1),
),
],
),
child: const Icon(
child: Icon(
Icons.smart_toy_outlined,
color: Colors.white,
size: 22,
size: 22.sp,
),
),
const SizedBox(width: 12),
SizedBox(width: 12.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'XiaoYan'.tr,
style: TextStyle(
fontSize: 16,
fontSize: 16.sp,
fontWeight: FontWeight.bold,
color: isDarkMode ? Colors.white : Colors.black87,
),
@ -102,18 +103,18 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
Row(
children: [
Container(
width: 6,
height: 6,
width: 6.w,
height: 6.w,
decoration: const BoxDecoration(
color: Colors.green,
shape: BoxShape.circle,
),
),
const SizedBox(width: 4),
SizedBox(width: 4.w),
Text(
'online'.tr,
style: TextStyle(
fontSize: 12,
fontSize: 12.sp,
color: Colors.green,
fontWeight: FontWeight.w500,
),
@ -131,6 +132,7 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
controller.isTtsEnabled.value
? Icons.volume_up
: Icons.volume_off,
size: 24.sp,
color: controller.isTtsEnabled.value
? primaryColor
: isDarkMode
@ -142,9 +144,9 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
// 更多选项
Container(
width: 34,
height: 34,
margin: const EdgeInsets.only(right: 8),
width: 34.w,
height: 34.w,
margin: EdgeInsets.only(right: 8.w),
decoration: BoxDecoration(
color:
isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
@ -153,25 +155,28 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
if (!isDarkMode)
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 2,
spreadRadius: 0.5,
blurRadius: 2.w,
spreadRadius: 0.5.w,
),
],
),
child: PopupMenuButton(
icon: Icon(Icons.more_vert,
color: isDarkMode ? Colors.white : Colors.black87, size: 20),
color: isDarkMode ? Colors.white : Colors.black87,
size: 20.sp),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(12.w),
),
color: isDarkMode ? Colors.grey[800] : Colors.white,
itemBuilder: (context) => [
PopupMenuItem(
child: Row(
children: [
Icon(Icons.delete_outline, color: primaryColor, size: 20),
const SizedBox(width: 8),
Text('clearChatHistory'.tr, style: TextStyle(fontSize: 14)),
Icon(Icons.delete_outline,
color: primaryColor, size: 20.sp),
SizedBox(width: 8.w),
Text('clearChatHistory'.tr,
style: TextStyle(fontSize: 14.sp)),
],
),
onTap: () => Future.delayed(
@ -198,8 +203,8 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
controller: controller.scrollController,
shrinkWrap: true,
reverse: true,
padding: const EdgeInsets.symmetric(
vertical: 16, horizontal: 16),
padding: EdgeInsets.symmetric(
vertical: 16.h, horizontal: 16.w),
itemCount: controller.messages.length +
(controller.hasMoreMessages.value ? 1 : 0),
itemBuilder: (context, index) {
@ -212,7 +217,7 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
final message = controller
.messages[controller.messages.length - 1 - index];
return Padding(
padding: const EdgeInsets.only(bottom: 8),
padding: EdgeInsets.only(bottom: 8.h),
child: GestureDetector(
onLongPressStart:
(LongPressStartDetails details) {
@ -237,21 +242,20 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
// 输入区域
Container(
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
decoration: BoxDecoration(
color: backgroundColor,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.06),
blurRadius: 8,
blurRadius: 8.w,
offset: const Offset(0, -1),
),
],
border: Border(
top: BorderSide(
color: Colors.grey.withOpacity(0.15),
width: 1,
width: 1.w,
),
),
),
@ -269,29 +273,29 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(12.w),
child: Image.file(
File(controller.selectedImagePath.value!),
height: 150,
height: 150.h,
width: double.infinity,
fit: BoxFit.cover,
),
),
Positioned(
top: 8,
right: 8,
top: 8.h,
right: 8.w,
child: GestureDetector(
onTap: () => controller.clearImageInput(),
child: Container(
padding: const EdgeInsets.all(4),
padding: EdgeInsets.all(4.w),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.6),
shape: BoxShape.circle,
),
child: const Icon(
child: Icon(
Icons.close,
color: Colors.white,
size: 16,
size: 16.sp,
),
),
),
@ -300,34 +304,34 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
),
// 预设提示选项
const SizedBox(height: 12),
SizedBox(height: 12.h),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
_buildPromptChip(
context: context,
label: 'summarizeImageContent'.tr,//总结图片内容
label: 'summarizeImageContent'.tr, //总结图片内容
onTap: () => controller.summarizeImage(),
),
const SizedBox(width: 8),
SizedBox(width: 8.w),
_buildPromptChip(
context: context,
label: 'extractImageText'.tr,//抽取图像文字
label: 'extractImageText'.tr, //抽取图像文字
onTap: () =>
controller.extractTextFromImage(),
),
const SizedBox(width: 8),
SizedBox(width: 8.w),
_buildPromptChip(
context: context,
label: 'translateImageText'.tr,//翻译图像文字
label: 'translateImageText'.tr, //翻译图像文字
onTap: () =>
controller.translateImageText(),
),
],
),
),
const SizedBox(height: 12),
SizedBox(height: 12.h),
],
);
} else {
@ -353,8 +357,8 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
controller.toggleInputMode();
},
child: Container(
width: 44,
height: 44,
width: 44.w,
height: 44.w,
decoration: BoxDecoration(
color: isDarkMode
? Colors.grey[800]
@ -368,13 +372,13 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
color: isDarkMode
? Colors.grey[300]
: Colors.grey[700],
size: 22,
size: 22.sp,
),
),
);
}),
const SizedBox(width: 8),
SizedBox(width: 8.w),
// 文本输入框或语音波形图
Expanded(
@ -385,23 +389,23 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
if (controller.isTextInputMode.value) {
// 文本输入模式 - 限制最大高度并只向上扩展
return Container(
constraints: const BoxConstraints(
minHeight: 44, // 最小高度
maxHeight: 120, // 最大高度限制,防止挤压按钮
constraints: BoxConstraints(
minHeight: 44.h,
maxHeight: 120.h,
),
padding: const EdgeInsets.symmetric(
horizontal: 16),
padding: EdgeInsets.symmetric(
horizontal: 16.w),
decoration: BoxDecoration(
color: isDarkMode
? Colors.grey[800]
: Colors.grey[200],
borderRadius: BorderRadius.circular(22),
borderRadius:
BorderRadius.circular(22.w),
border: Border.all(
color: isDarkMode
? Colors.grey[
800]! // 修改: 从 Colors.grey[700] 改为 Colors.grey[800]
? Colors.grey[800]!
: Colors.grey.withOpacity(0.1),
width: 1.0,
width: 1.w,
),
),
child: Row(
@ -435,7 +439,7 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
}
},
style: TextStyle(
fontSize: 15,
fontSize: 15.sp,
color: isDarkMode
? Colors.white
: Colors.grey[800],
@ -448,7 +452,7 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
.tr, // 输入消息...
hintStyle: TextStyle(
color: Colors.grey[500],
fontSize: 15,
fontSize: 15.sp,
),
filled: false,
border: InputBorder.none,
@ -458,14 +462,15 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
color: isDarkMode
? Colors.grey[800]!
: Colors.grey[200]!,
width: 1.0,
width: 1.w,
),
),
contentPadding:
EdgeInsets.symmetric(
vertical: isImageActive
? 12
: 8), // 调整垂直内边距
vertical:
isImageActive
? 12.h
: 8.h),
),
onSubmitted: (_) =>
controller.sendMessage(),
@ -525,9 +530,9 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
controller.isListening.value;
return Container(
height: 50,
padding: const EdgeInsets.symmetric(
horizontal: 16),
height: 50.h,
padding: EdgeInsets.symmetric(
horizontal: 16.w),
decoration: BoxDecoration(
color: isPushToTalkActive
? (isDarkMode
@ -537,7 +542,7 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
? Colors.grey[800]
: Colors.grey[200]),
borderRadius:
BorderRadius.circular(22),
BorderRadius.circular(22.w),
border: Border.all(
color: isPushToTalkActive
? Colors.blue
@ -546,8 +551,8 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
: Colors.grey
.withOpacity(0.1)),
width: isPushToTalkActive
? 2.0
: 1.0,
? 2.w
: 1.w,
),
),
child: Row(
@ -572,7 +577,7 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
isPushToTalkActive
? Icons.mic
: Icons.mic_none,
size: 16,
size: 16.sp,
color: isPushToTalkActive
? Colors.blue
: Colors.grey[500],
@ -607,7 +612,7 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
}),
),
const SizedBox(width: 8),
SizedBox(width: 8.w),
// 动态按钮区域:展开按钮 + 发送按钮 或 图片选择按钮
Obx(() {
@ -627,7 +632,7 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
if (shouldShowExpand && hasText)
Padding(
padding:
const EdgeInsets.only(bottom: 40),
EdgeInsets.only(bottom: 40.h),
child: GestureDetector(
onTap: () {
// 打开底部弹出编辑模式
@ -646,21 +651,21 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
});
},
child: Container(
width: 32,
height: 32,
width: 32.w,
height: 32.w,
decoration: BoxDecoration(
color: isDarkMode
? Colors.grey[700]
: Colors.grey[300],
borderRadius:
BorderRadius.circular(16),
BorderRadius.circular(16.w),
),
child: Icon(
Icons.expand_less,
color: isDarkMode
? Colors.grey[300]
: Colors.grey[700],
size: 20,
size: 20.sp,
),
),
),
@ -669,17 +674,17 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
GestureDetector(
onTap: () => controller.sendMessage(),
child: Container(
width: 44,
height: 44,
width: 44.w,
height: 44.w,
decoration: BoxDecoration(
color: primaryColor,
borderRadius:
BorderRadius.circular(22),
BorderRadius.circular(22.w),
),
child: const Icon(
child: Icon(
Icons.send,
color: Colors.white,
size: 22,
size: 22.sp,
),
),
),
@ -695,21 +700,21 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
controller.togglePlusOptions();
},
child: Container(
width: 44,
height: 44,
width: 44.w,
height: 44.w,
decoration: BoxDecoration(
color: isDarkMode
? Colors.grey[800]
: Colors.grey[200],
borderRadius:
BorderRadius.circular(22),
BorderRadius.circular(22.w),
),
child: Icon(
isExpanded ? Icons.close : Icons.add,
color: isDarkMode
? Colors.grey[300]
: Colors.grey[700],
size: 22,
size: 22.sp,
),
),
);
@ -727,62 +732,77 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
height: isExpanded ? 120.h : 0, // 调整高度以适应按钮尺寸
height: isExpanded ? 100.h : 0, // 调整高度以适应按钮尺寸
child: isExpanded
? Container(
margin: EdgeInsets.only(top: 8.h),
padding: EdgeInsets.symmetric(
horizontal: 16.w), // 添加左右边距
child: Row(
mainAxisAlignment: MainAxisAlignment
.spaceEvenly, // 改为均匀分布
children: [
// 相机按钮
_buildPlusOptionButton(
context: context,
icon: Icons.camera_alt,
label: 'takePhoto'.tr, // 拍照
onTap: () async {
//controller.collapsePlusOptions();
await controller.takePhoto();
},
isDarkMode: isDarkMode,
),
// 相册按钮
_buildPlusOptionButton(
context: context,
icon: Icons.photo_library,
label: 'photoAlbum'.tr, // 相册
onTap: () async {
//controller.collapsePlusOptions();
await controller.pickImage();
},
isDarkMode: isDarkMode,
),
// 文件按钮(预留位置)
// _buildPlusOptionButton(
// context: context,
// icon: Icons.folder,
// label: '文件',
// onTap: () {
// //controller.collapsePlusOptions();
// // TODO: 实现文件选择功能
// },
// isDarkMode: isDarkMode,
// ),
// 通话按钮
_buildPlusOptionButton(
context: context,
icon: Icons.phone,
label: 'makeCall'.tr, // 打电话
onTap: () {
// 启动打电话模式
controller.startPhoneCall();
},
isDarkMode: isDarkMode,
? LayoutBuilder(
// 添加LayoutBuilder获取实际约束
builder: (context, constraints) {
return SingleChildScrollView(
// 确保内容可滚动
scrollDirection:
Axis.horizontal, // 水平滚动
child: Container(
margin: EdgeInsets.only(top: 16.h),
padding: EdgeInsets.symmetric(
horizontal: 16.w),
// 根据可用宽度调整容器宽度
width: max(constraints.maxWidth,
300.w), // 最小300宽
child: Wrap(
// 使用Wrap替代Row,自动换行
alignment: WrapAlignment.center,
spacing: 26.w, // 按钮水平间距
runSpacing: 8.h, // 按钮垂直间距
children: [
// 相机按钮
_buildPlusOptionButton(
context: context,
icon: Icons.camera_alt,
label: 'takePhoto'.tr, // 拍照
onTap: () async {
//controller.collapsePlusOptions();
await controller.takePhoto();
},
isDarkMode: isDarkMode,
),
// 相册按钮
_buildPlusOptionButton(
context: context,
icon: Icons.photo_library,
label: 'photoAlbum'.tr, // 相册
onTap: () async {
//controller.collapsePlusOptions();
await controller.pickImage();
},
isDarkMode: isDarkMode,
),
// 文件按钮(预留位置)
// _buildPlusOptionButton(
// context: context,
// icon: Icons.folder,
// label: '文件',
// onTap: () {
// //controller.collapsePlusOptions();
// // TODO: 实现文件选择功能
// },
// isDarkMode: isDarkMode,
// ),
// 通话按钮
_buildPlusOptionButton(
context: context,
icon: Icons.phone,
label: 'makeCall'.tr, // 打电话
onTap: () {
// 启动打电话模式
controller.startPhoneCall();
},
isDarkMode: isDarkMode,
),
],
),
),
],
),
);
},
)
: const SizedBox.shrink(),
);
@ -813,25 +833,24 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
required String label,
required VoidCallback onTap,
}) {
final theme = Theme.of(context);
final primaryColor = theme.primaryColor;
final primaryColor = Theme.of(context).primaryColor;
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
decoration: BoxDecoration(
color: primaryColor.withOpacity(0.08),
borderRadius: BorderRadius.circular(16),
borderRadius: BorderRadius.circular(16.w),
border: Border.all(
color: primaryColor.withOpacity(0.2),
width: 1,
width: 1.w,
),
),
child: Text(
label,
style: TextStyle(
fontSize: 13,
fontSize: 13.sp,
color: primaryColor,
fontWeight: FontWeight.w500,
),
@ -865,10 +884,10 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
height: 50.w, // 调整为44.w以适应布局
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[800] : Colors.grey[100],
borderRadius: BorderRadius.circular(12.r), // 调整为10.r
borderRadius: BorderRadius.circular(12.w),
border: Border.all(
color: isDarkMode ? Colors.grey[700]! : Colors.grey[300]!,
width: 1,
width: 1.w,
),
),
child: Icon(
@ -1085,8 +1104,8 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
BoxShadow(
color: (isActive ? Colors.red : Colors.green)
.withOpacity(0.3),
blurRadius: 12.r,
spreadRadius: 2.r,
blurRadius: 12.w,
spreadRadius: 2.w,
),
],
),
@ -1148,23 +1167,23 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
if (controller.isLoadingMore.value) {
// 正在加载状态
return Container(
padding: const EdgeInsets.symmetric(vertical: 16),
padding: EdgeInsets.symmetric(vertical: 16.h),
child: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(
width: 16,
height: 16,
SizedBox(
width: 16.w,
height: 16.h,
child: CircularProgressIndicator(
strokeWidth: 2,
strokeWidth: 2.w,
),
),
const SizedBox(width: 8),
SizedBox(width: 8.w),
Text(
'loadMoreMessages'.tr, // 加载更多消息...
style: const TextStyle(
fontSize: 14,
'loadMoreMessages'.tr,
style: TextStyle(
fontSize: 14.sp,
color: Colors.grey,
),
),
@ -1175,23 +1194,23 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
} else if (controller.isLoadingMoreError.value) {
// 加载失败状态
return Container(
padding: const EdgeInsets.symmetric(vertical: 16),
padding: EdgeInsets.symmetric(vertical: 16.h),
child: Center(
child: GestureDetector(
onTap: () => controller.loadMoreMessages(),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icon(
Icons.refresh,
size: 16,
size: 16.sp,
color: Colors.grey,
),
const SizedBox(width: 8),
SizedBox(width: 8.w),
Text(
'loadFailedClickRetry'.tr, // 加载失败,点击重试
style: const TextStyle(
fontSize: 14,
'loadFailedClickRetry'.tr,
style: TextStyle(
fontSize: 14.sp,
color: Colors.grey,
),
),
@ -1203,12 +1222,12 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
} else {
// 默认状态(有更多消息但未加载)
return Container(
padding: const EdgeInsets.symmetric(vertical: 8),
padding: EdgeInsets.symmetric(vertical: 8.h),
child: Center(
child: Text(
'swipeUpForMore'.tr, // 向上滑动查看更多
style: const TextStyle(
fontSize: 12,
'swipeUpForMore'.tr,
style: TextStyle(
fontSize: 12.sp,
color: Colors.grey,
),
),

55
lib/modules/explore/controllers/explore_controller.dart

@ -76,6 +76,17 @@ class ExploreController extends GetxController {
targetRoute: Routes.translation,
),
// AIFeature(
// title: 'conferenceAssistant'.tr, // 商务会议助手
// description: 'conferenceAssistantInfo'.tr, // 实时记录会议纪要,多语言翻译
// icon: Icons.people,
// startColor: const Color(0xFFab47bc),
// endColor: const Color(0xFFec407a),
// iconBgColor: const Color(0xFFd81b60).withOpacity(0.2),
// iconColor: const Color(0xFFd81b60),
// agentId: 'meeting_assistant',
// targetRoute: Routes.meeting,
// ),
// AIFeature(
// title: 'aiGirlfriend'.tr, // 亲子陪伴
// description: 'aiGirlfriendInfo'.tr, // 智能陪伴,随时聊天
// icon: Icons.favorite,
@ -99,6 +110,20 @@ class ExploreController extends GetxController {
// ),
];
final List<AIFeature> aiFeaturesWide = [
AIFeature(
title: 'conferenceAssistant'.tr, // 商务会议助手
description: 'conferenceAssistantInfo'.tr, // 实时记录会议纪要,多语言翻译
icon: Icons.people,
startColor: const Color(0xFFab47bc),
endColor: const Color(0xFFec407a),
iconBgColor: const Color(0xFFd81b60).withOpacity(0.2),
iconColor: const Color(0xFFd81b60),
agentId: 'meeting_assistant',
targetRoute: Routes.meeting,
),
];
// 热门功能场景列表
final List<FunctionScene> functionScenes = [
FunctionScene(
@ -177,6 +202,36 @@ class ExploreController extends GetxController {
Get.toNamed(feature.targetRoute);
} else if (feature.targetRoute == Routes.agent) {
Get.toNamed(feature.targetRoute);
} else if (feature.targetRoute == Routes.meeting) {
// 跳转到会议页面
Get.toNamed(feature.targetRoute);
}
}
// 点击AI功能(宽版本)
void onAIFeatureWideTap(int index) {
if (index < 0 || index >= aiFeaturesWide.length) return;
final feature = aiFeaturesWide[index];
// 直接根据目标路由进行跳转
if (feature.targetRoute == Routes.meeting) {
Get.toNamed(feature.targetRoute);
} else if (feature.targetRoute == Routes.chat) {
final agent = AgentProvider.getAgentById(feature.agentId);
if (agent != null) {
Get.toNamed(feature.targetRoute, arguments: {
'agentId': agent.id,
'agentName': agent.name,
'agentVoice': agent.voice,
'agentAvatar': agent.avatarUrl,
'agentSubtitle': agent.description,
'systemPrompt': agent.systemPrompt,
'welcomeMessage': agent.welcomeMessage,
});
}
} else if (feature.targetRoute.isNotEmpty) {
Get.toNamed(feature.targetRoute);
}
}

302
lib/modules/explore/views/explore_view.dart

@ -65,11 +65,11 @@ class ExploreView extends GetView<ExploreController> {
// AI功能列表
SizedBox(height: 24.h),
_buildAIFeaturesSection(isDarkMode),
_buildCustomAILayout(isDarkMode),
// 热门功能场景
SizedBox(height: 24.h),
_buildFunctionScenesSection(isDarkMode),
//_buildFunctionScenesSection(isDarkMode),
// 新功能推荐
SizedBox(height: 24.h),
@ -250,8 +250,23 @@ class ExploreView extends GetView<ExploreController> {
);
}
// AI功能部分
Widget _buildAIFeaturesSection(bool isDarkMode) {
// 自定义AI功能布局:2x2网格,商务会议助手占据下方两个网格位置
Widget _buildCustomAILayout(bool isDarkMode) {
// 找到AI全能助手
final aiAssistantIndex = controller.aiFeatures.indexWhere(
(feature) => feature.agentId == 'personal_assistant',
);
// 找到同声传译
final translationIndex = controller.aiFeatures.indexWhere(
(feature) => feature.agentId == 'simultaneous_interpreter',
);
// 找到商务会议助手(在aiFeaturesWide中)
final meetingFeature = controller.aiFeaturesWide.isNotEmpty
? controller.aiFeaturesWide.first
: null;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -264,20 +279,56 @@ class ExploreView extends GetView<ExploreController> {
),
),
SizedBox(height: 16.h),
GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 16.w,
mainAxisSpacing: 16.h,
childAspectRatio: 1.0,
),
itemCount: controller.aiFeatures.length,
itemBuilder: (context, index) {
final feature = controller.aiFeatures[index];
return _buildAIFeatureItem(feature, index, isDarkMode);
},
// 2x2网格布局
Column(
children: [
// 上层:AI全能助手(左)和同声传译(右)
Row(
children: [
// 左上角:AI全能助手
if (aiAssistantIndex != -1)
Expanded(
child: AspectRatio(
aspectRatio: 1.0, // 正方形
child: _buildAIFeatureItem(
controller.aiFeatures[aiAssistantIndex],
aiAssistantIndex,
isDarkMode,
),
),
),
SizedBox(width: 16.w),
// 右上角:同声传译
if (translationIndex != -1)
Expanded(
child: AspectRatio(
aspectRatio: 1.0, // 正方形
child: _buildAIFeatureItem(
controller.aiFeatures[translationIndex],
translationIndex,
isDarkMode,
),
),
),
],
),
SizedBox(height: 16.h),
// 下层:商务会议助手(横跨整行)
if (meetingFeature != null)
AspectRatio(
aspectRatio: 2.0, // 宽高比2:1,横跨两个网格位置
child: _buildAIFeatureItemWideForMeeting(
meetingFeature,
0, // 使用固定索引,因为这是aiFeaturesWide中的项目
isDarkMode,
),
),
],
),
],
);
@ -366,20 +417,211 @@ class ExploreView extends GetView<ExploreController> {
);
}
//整行布局
Widget _buildAIFeaturesWideSection(bool isDarkMode) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Text(
// 'aiFunction'.tr, // AI功能
// style: TextStyle(
// fontSize: 18.sp,
// fontWeight: FontWeight.w600,
// color: isDarkMode ? Colors.white : Colors.grey[800],
// ),
// ),
// SizedBox(height: 16.h),
// 直接使用 Column 包裹宽版卡片
Column(
children: controller.aiFeaturesWide.map((feature) {
return Padding(
padding: EdgeInsets.only(bottom: 16.h), // 卡片间距
child: _buildAIFeatureItemWide(
feature,
controller.aiFeatures.indexOf(feature),
isDarkMode,
),
);
}).toList(),
),
],
);
}
// AI功能项(宽版本,用于横跨整行的AI全能助手)
Widget _buildAIFeatureItemWide(AIFeature feature, int index, bool isDarkMode) {
return GestureDetector(
onTap: () => controller.onAIFeatureTap(index),
child: Container(
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
feature.startColor.withOpacity(isDarkMode ? 0.3 : 0.3),
feature.endColor.withOpacity(isDarkMode ? 0.3 : 0.3),
],
),
borderRadius: BorderRadius.circular(20.r),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.grey.withOpacity(0.1),
blurRadius: 8.w,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
// 图标圆圈
Container(
width: 48.w,
height: 48.w,
decoration: BoxDecoration(
color: feature.iconColor.withValues(alpha: 0.2),
shape: BoxShape.circle,
),
child: Icon(
feature.icon,
size: 24.sp,
color: feature.iconColor,
),
),
SizedBox(width: 16.w),
// 文本内容
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 标题
Text(
feature.title,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.grey[800],
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 4.h),
// 描述
Text(
feature.description,
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
),
);
}
// AI功能项(宽版本,专门用于商务会议助手)
Widget _buildAIFeatureItemWideForMeeting(AIFeature feature, int index, bool isDarkMode) {
return GestureDetector(
onTap: () => controller.onAIFeatureWideTap(index),
child: Container(
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
feature.startColor.withValues(alpha: isDarkMode ? 0.3 : 0.3),
feature.endColor.withValues(alpha: isDarkMode ? 0.3 : 0.3),
],
),
borderRadius: BorderRadius.circular(20.r),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withValues(alpha: 0.2)
: Colors.grey.withValues(alpha: 0.1),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
// 图标圆圈
Container(
width: 48.w,
height: 48.w,
decoration: BoxDecoration(
color: feature.iconColor.withValues(alpha: 0.2),
shape: BoxShape.circle,
),
child: Icon(
feature.icon,
size: 24.sp,
color: feature.iconColor,
),
),
SizedBox(width: 16.w),
// 文本内容
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 标题
Text(
feature.title,
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.grey[800],
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 4.h),
// 描述
Text(
feature.description,
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
),
);
}
// 热门功能场景部分
Widget _buildFunctionScenesSection(bool isDarkMode) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'popularFunction'.tr, // 热门功能场景
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.grey[800],
),
),
SizedBox(height: 16.h),
// Text(
// 'popularFunction'.tr, // 热门功能场景
// style: TextStyle(
// fontSize: 18.sp,
// fontWeight: FontWeight.w600,
// color: isDarkMode ? Colors.white : Colors.grey[800],
// ),
// ),
// SizedBox(height: 16.h),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
@ -404,13 +646,13 @@ class ExploreView extends GetView<ExploreController> {
child: Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.grey[100],
color: isDarkMode ? Colors.white.withValues(alpha: 0.05) : Colors.grey[100],
borderRadius: BorderRadius.circular(16.r),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.grey.withOpacity(0.1),
? Colors.black.withValues(alpha: 0.2)
: Colors.grey.withValues(alpha: 0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),

41
lib/modules/meeting/views/meeting_mine_view.dart

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'dart:io';
import '../../../routes/app_routes.dart';
import '../../profile/views/unified_avatar.dart';
import '../controllers/meeting_mine_controller.dart';
import 'bottomSheet/statistics_bottom_sheet.dart';
@ -113,36 +113,15 @@ class _MeetingMineViewState extends State<MeetingMineView>
onTap: () => Get.toNamed(Routes.profileMeeting), // 跳转到个人信息页面
child: Row(
children: [
// 统一头像显示逻辑:本地头像 > 网络头像 > 默认图标
Obx(
() => Container(
width: 50.w,
height: 50.w,
margin: EdgeInsets.only(right: 10.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50.w),
),
child: CircleAvatar(
radius: 25.w,
backgroundImage:
_controller.profileCtrl.localAvatar.value.isNotEmpty
? FileImage(
File(_controller.profileCtrl.localAvatar.value))
: (_controller
.profileCtrl.userInfo.value.avatar.isNotEmpty
? NetworkImage(
_controller.profileCtrl.userInfo.value.avatar)
: null) as ImageProvider<Object>?,
backgroundColor: Colors.grey[200],
child: (_controller.profileCtrl.localAvatar.value.isEmpty &&
_controller.profileCtrl.userInfo.value.avatar.isEmpty)
? Icon(
Icons.person,
size: 25.r,
color: Colors.grey[600],
)
: null,
),
// 使用统一头像组件,避免闪烁问题
Container(
margin: EdgeInsets.only(right: 10.w),
child: UnifiedAvatar(
radius: 25.w,
backgroundColor: Colors.grey[200],
iconColor: Colors.grey[600],
iconSize: 25.r,
isDarkMode: false,
),
),
Expanded(

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

@ -31,6 +31,7 @@ class ProfileController extends GetxController {
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>();
// 用于乐观更新的原始状态备份
@ -289,19 +290,23 @@ class ProfileController extends GetxController {
}
}
void loadUserInfo() {
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);
file.exists().then((exists) {
try {
final exists = await file.exists();
if (exists) {
localAvatar.value = avatarPath;
Logger().d('加载本地头像路径: ${localAvatar.value}');
@ -311,18 +316,26 @@ class ProfileController extends GetxController {
_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) {
// 如果有远程头像地址且本地头像不存在,才下载并保存到本地
if (userInfo.value.avatar.isNotEmpty && localAvatar.value.isEmpty) {
Logger().d('远程头像地址: ${userInfo.value.avatar}');
_downloadAndSaveAvatar(userInfo.value.avatar);
}
@ -333,11 +346,12 @@ class ProfileController extends GetxController {
}
// 确保本地状态与服务器状态的一致性
void _ensureStateConsistency() {
// 检查本地头像文件是否存在
void _ensureStateConsistency() async {
// 同步检查本地头像文件是否存在 - 修复闪烁问题
if (localAvatar.value.isNotEmpty) {
final file = File(localAvatar.value);
file.exists().then((exists) {
try {
final exists = await file.exists();
if (!exists) {
Logger().w('本地头像文件不存在,清除本地头像路径: ${localAvatar.value}');
localAvatar.value = '';
@ -348,7 +362,11 @@ class ProfileController extends GetxController {
_downloadAndSaveAvatar(userInfo.value.avatar);
}
}
});
} catch (e) {
Logger().e('检查本地头像文件一致性时发生异常: $e');
localAvatar.value = '';
_storage.remove('local_avatar');
}
}
}

127
lib/modules/profile/views/profile_meeting_view.dart

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../controllers/profile_controller.dart';
import 'unified_avatar.dart';
class ProfileMeetingView extends GetView<ProfileController> {
ProfileMeetingView({super.key});
@ -100,15 +101,15 @@ class ProfileMeetingView extends GetView<ProfileController> {
SizedBox(height: 16.h),
_buildNicknameItem(isDarkMode),
Divider(
height: 1.h,
height: 0.5.h,
color: isDarkMode ? Colors.grey[800] : Colors.grey[200]),
_buildLoginMethodItem(isDarkMode),
Divider(
height: 1.h,
height: 0.5.h,
color: isDarkMode ? Colors.grey[800] : Colors.grey[200]),
_buildManageLoginItem(isDarkMode),
Divider(
height: 1.h,
height: 0.5.h,
color: isDarkMode ? Colors.grey[800] : Colors.grey[200]),
SizedBox(height: 32.h),
_buildLogoutButton(isDarkMode),
@ -126,55 +127,43 @@ class ProfileMeetingView extends GetView<ProfileController> {
return Center(
child: Column(
children: [
Obx(() => GestureDetector(
onTap: () => controller.updateUserAvatar(),
child: Stack(
children: [
CircleAvatar(
radius: 40.r,
backgroundImage: controller.localAvatar.value.isNotEmpty
? FileImage(File(controller.localAvatar.value))
: (controller.userInfo.value.avatar.isNotEmpty
? NetworkImage(controller.userInfo.value.avatar)
: null) as ImageProvider<Object>?,
backgroundColor:
isDarkMode ? Colors.grey[800] : Colors.grey[200],
child: (controller.localAvatar.value.isEmpty &&
controller.userInfo.value.avatar.isEmpty)
? Icon(
Icons.person,
size: 30.r,
color: isDarkMode
? Colors.grey[400]
: Colors.grey[600],
)
: null,
),
Positioned(
bottom: 0,
right: 0,
child: Container(
width: 22.w,
height: 22.w,
decoration: BoxDecoration(
color:
isDarkMode ? Colors.grey[600] : Colors.grey[350],
shape: BoxShape.circle,
border: Border.all(
color: isDarkMode ? Colors.black : Colors.white,
width: 0.w,
),
),
child: Icon(
Icons.camera_alt,
size: 14.w,
color: Colors.black,
),
// 使用统一头像组件,避免闪烁问题
GestureDetector(
onTap: () => controller.updateUserAvatar(),
child: Stack(
children: [
UnifiedAvatar(
radius: 40.r,
backgroundColor:
isDarkMode ? Colors.grey[800] : Colors.grey[200],
iconColor: isDarkMode ? Colors.grey[400] : Colors.grey[600],
iconSize: 30.r,
isDarkMode: isDarkMode,
),
Positioned(
bottom: 0,
right: 0,
child: Container(
width: 22.w,
height: 22.w,
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[600] : Colors.grey[350],
shape: BoxShape.circle,
border: Border.all(
color: isDarkMode ? Colors.black : Colors.white,
width: 0.w,
),
),
],
child: Icon(
Icons.camera_alt,
size: 14.w,
color: Colors.black,
),
),
),
)),
],
),
),
],
),
);
@ -336,9 +325,8 @@ class ProfileMeetingView extends GetView<ProfileController> {
child: OutlinedButton(
onPressed: () => _showLogoutDialog(isDarkMode),
style: OutlinedButton.styleFrom(
backgroundColor: isDarkMode ? Colors.grey[900] : Colors.grey[50],
side: BorderSide(
color: isDarkMode ? Colors.grey[700]! : Colors.grey[300]!),
backgroundColor: isDarkMode ? Colors.grey[700] : Colors.grey[200],
side: BorderSide.none,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.r),
),
@ -358,10 +346,11 @@ class ProfileMeetingView extends GetView<ProfileController> {
return SizedBox(
width: double.infinity,
height: 48.h,
child: ElevatedButton(
child: OutlinedButton(
onPressed: () => _showDeleteAccountDialog(isDarkMode),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
style: OutlinedButton.styleFrom(
side: BorderSide.none,
backgroundColor: isDarkMode ? Colors.grey[700] : Colors.grey[200],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.r),
),
@ -370,7 +359,7 @@ class ProfileMeetingView extends GetView<ProfileController> {
'删除账号',
style: TextStyle(
fontSize: 16.sp,
color: Colors.white,
color: Colors.red[600],
),
),
),
@ -378,11 +367,25 @@ class ProfileMeetingView extends GetView<ProfileController> {
}
Widget _buildWarningText() {
return Text(
'警告:您的账号信息和所有文件将被永久删除。',
style: TextStyle(
fontSize: 12.sp,
color: Colors.red,
return Text.rich(
TextSpan(
children: [
TextSpan(
text: '警告:',
style: TextStyle(
fontSize: 12.sp, // 稍微大一点的字号
color: Colors.red[800], // 更深的红色
//fontStyle: FontStyle.italic, // 斜体
),
),
TextSpan(
text: '您的账号信息和所有文件将被永久删除。',
style: TextStyle(
fontSize: 11.sp,
color: Colors.grey[600],
),
),
],
),
textAlign: TextAlign.center,
);
@ -464,7 +467,7 @@ class ProfileMeetingView extends GetView<ProfileController> {
controller.deleteAccount();
},
child: Text(
'comfirm'.tr,
'confirm'.tr,
style: const TextStyle(
color: Colors.red,
),

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

@ -7,6 +7,7 @@ import '../../../routes/app_routes.dart';
import 'dart:io';
import 'dart:ui';
import '../../common/webview_page.dart';
import 'unified_avatar.dart';
// ProfileView: 用户设置页面
// class ProfileView extends GetView<ProfileController> {
@ -326,20 +327,14 @@ class EditProfileView extends GetView<ProfileController> {
// --- 右侧内容保持不变 ---
Row(
children: [
Obx(() => CircleAvatar(
radius: 26.r, // 调整头像大小以适应行高
backgroundImage: controller.localAvatar.value.isNotEmpty
? FileImage(File(controller.localAvatar.value))
: (controller.userInfo.value.avatar.isNotEmpty
? NetworkImage(controller.userInfo.value.avatar)
: null) as ImageProvider<Object>?,
backgroundColor: Colors.grey[200],
child: (controller.localAvatar.value.isEmpty &&
controller.userInfo.value.avatar.isEmpty)
? Icon(Icons.person,
size: 20.r, color: Colors.grey) // 默认图标
: null, // 添加背景色防止透明图片问题
)),
// 使用统一头像组件,避免闪烁问题
UnifiedAvatar(
radius: 26.r,
backgroundColor: Colors.grey[200],
iconColor: Colors.grey,
iconSize: 20.r,
isDarkMode: isDarkMode,
),
SizedBox(width: 8.w),
Container(
width: 32.w,

137
lib/modules/profile/views/unified_avatar.dart

@ -0,0 +1,137 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../controllers/profile_controller.dart';
/// 统一头像显示组件
/// 确保所有页面的头像显示逻辑一致,避免闪烁问题
class UnifiedAvatar extends StatelessWidget {
final double radius;
final Color? backgroundColor;
final Color? iconColor;
final double? iconSize;
final bool isDarkMode;
final VoidCallback? onTap;
const UnifiedAvatar({
super.key,
required this.radius,
this.backgroundColor,
this.iconColor,
this.iconSize,
this.isDarkMode = false,
this.onTap,
});
@override
Widget build(BuildContext context) {
final ProfileController profileCtrl = Get.find<ProfileController>();
return GestureDetector(
onTap: onTap,
child: Obx(() {
// 等待头像数据准备完成,避免闪烁
if (!profileCtrl.isAvatarReady.value) {
return _buildLoadingAvatar();
}
return _buildAvatar(profileCtrl);
}),
);
}
/// 构建加载中的头像占位符
Widget _buildLoadingAvatar() {
return CircleAvatar(
radius: radius,
backgroundColor:
backgroundColor ?? (isDarkMode ? Colors.grey[800] : Colors.grey[200]),
child: Icon(
Icons.person,
size: iconSize ?? (radius * 0.6),
color: iconColor ?? (isDarkMode ? Colors.grey[400] : Colors.grey[600]),
),
);
}
/// 构建实际头像
Widget _buildAvatar(ProfileController profileCtrl) {
ImageProvider<Object>? imageProvider;
// 统一头像显示优先级:本地头像 > 网络头像 > 默认图标
if (profileCtrl.localAvatar.value.isNotEmpty) {
imageProvider = FileImage(File(profileCtrl.localAvatar.value));
} else if (profileCtrl.userInfo.value.avatar.isNotEmpty) {
imageProvider = NetworkImage(profileCtrl.userInfo.value.avatar);
}
return CircleAvatar(
radius: radius,
backgroundImage: imageProvider,
backgroundColor:
backgroundColor ?? (isDarkMode ? Colors.grey[800] : Colors.grey[200]),
child: (profileCtrl.localAvatar.value.isEmpty &&
profileCtrl.userInfo.value.avatar.isEmpty)
? Icon(
Icons.person,
size: iconSize ?? (radius * 0.6),
color: iconColor ??
(isDarkMode ? Colors.grey[400] : Colors.grey[600]),
)
: null,
);
}
}
/// 带容器装饰的统一头像组件
class UnifiedAvatarWithContainer extends StatelessWidget {
final double width;
final double height;
final double radius;
final Color? backgroundColor;
final Color? iconColor;
final double? iconSize;
final bool isDarkMode;
final VoidCallback? onTap;
final EdgeInsetsGeometry? margin;
final List<BoxShadow>? boxShadow;
const UnifiedAvatarWithContainer({
super.key,
required this.width,
required this.height,
required this.radius,
this.backgroundColor,
this.iconColor,
this.iconSize,
this.isDarkMode = false,
this.onTap,
this.margin,
this.boxShadow,
});
@override
Widget build(BuildContext context) {
return Container(
width: width,
height: height,
margin: margin,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(radius),
boxShadow: boxShadow,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(radius),
child: UnifiedAvatar(
radius: radius / 2,
backgroundColor: backgroundColor,
iconColor: iconColor,
iconSize: iconSize,
isDarkMode: isDarkMode,
onTap: onTap,
),
),
);
}
}

61
lib/modules/settings/views/settings_view.dart

@ -3,14 +3,15 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'dart:ui';
import 'dart:io';
import '../../../core/theme/app_colors.dart';
import '../controllers/settings_controller.dart';
import '../../../core/widgets/common_bottom_nav.dart';
import '../../../routes/app_routes.dart';
//import '../../../data/models/user_Info.dart';
import 'dart:io';
import '../../common/webview_page.dart';
import '../../../modules/profile/controllers/profile_controller.dart';
import '../../profile/views/unified_avatar.dart';
class SettingsView extends GetView<SettingsController> {
const SettingsView({Key? key}) : super(key: key);
@ -98,46 +99,24 @@ class SettingsView extends GetView<SettingsController> {
: ImageFilter.blur(sigmaX: 0, sigmaY: 0),
child: Row(
children: [
Obx(() => GestureDetector(
//onTap: ()=>controller.updateUserAvatar(),
child: Container(
//用户头像部分
width: 60.w,
height: 60.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30.r),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: ClipRRect(
// 裁剪圆形头像
borderRadius: BorderRadius.circular(30.r),
child: CircleAvatar(
radius: 30.r,
// 统一头像显示优先级:本地头像 > 网络头像 > 默认图标
backgroundImage: profileCtrl.localAvatar.value.isNotEmpty
? FileImage(File(profileCtrl.localAvatar.value))
: (profileCtrl.userInfo.value.avatar.isNotEmpty
? NetworkImage(profileCtrl.userInfo.value.avatar)
: null) as ImageProvider<Object>?,
backgroundColor: isDarkMode ? Colors.grey[800] : Colors.grey[200],
child: (profileCtrl.localAvatar.value.isEmpty &&
profileCtrl.userInfo.value.avatar.isEmpty)
? Icon(
Icons.person,
size: 30.r,
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
)
: null,
),
),
),
)),
// 使用统一头像组件,避免闪烁问题
UnifiedAvatarWithContainer(
width: 60.w,
height: 60.w,
radius: 30.r,
backgroundColor:
isDarkMode ? Colors.grey[800] : Colors.grey[200],
iconColor: isDarkMode ? Colors.grey[400] : Colors.grey[600],
iconSize: 30.r,
isDarkMode: isDarkMode,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
SizedBox(width: 16.w),
Expanded(
// 占据剩余空间

2
lib/routes/app_routes.dart

@ -1,6 +1,6 @@
abstract class Routes {
// 认证相关
static const splash = '/';
static const splash = '/splash';
static const login = '/login';
static const pairing = '/pairing';

27
local_plugins/user_portrait/test/user_portrait_method_channel_test.dart

@ -1,27 +0,0 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:user_portrait/user_portrait_method_channel.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
MethodChannelUserPortrait platform = MethodChannelUserPortrait();
const MethodChannel channel = MethodChannel('user_portrait');
setUp(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
channel,
(MethodCall methodCall) async {
return '42';
},
);
});
tearDown(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null);
});
test('getPlatformVersion', () async {
expect(await platform.getPlatformVersion(), '42');
});
}

29
local_plugins/user_portrait/test/user_portrait_test.dart

@ -1,29 +0,0 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:user_portrait/user_portrait.dart';
import 'package:user_portrait/user_portrait_platform_interface.dart';
import 'package:user_portrait/user_portrait_method_channel.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
class MockUserPortraitPlatform
with MockPlatformInterfaceMixin
implements UserPortraitPlatform {
@override
Future<String?> getPlatformVersion() => Future.value('42');
}
void main() {
final UserPortraitPlatform initialPlatform = UserPortraitPlatform.instance;
test('$MethodChannelUserPortrait is the default instance', () {
expect(initialPlatform, isInstanceOf<MethodChannelUserPortrait>());
});
test('getPlatformVersion', () async {
UserPortrait userPortraitPlugin = UserPortrait();
MockUserPortraitPlatform fakePlatform = MockUserPortraitPlatform();
UserPortraitPlatform.instance = fakePlatform;
expect(await userPortraitPlugin.getPlatformVersion(), '42');
});
}
Loading…
Cancel
Save