wolfplus 2 years ago
parent
commit
1b1cd66e1a
  1. 6
      android/app/src/main/kotlin/com/example/deep_voice/AzureAsrHelper.kt
  2. 613
      lib/modules/chat/views/chat_view.dart
  3. 8
      lib/modules/settings/views/settings_view.dart
  4. 295
      lib/modules/translation/controllers/translation_controller.dart
  5. 132
      lib/modules/translation/views/translation_detail_view.dart
  6. 111
      lib/modules/translation/views/translation_history_view.dart
  7. 193
      lib/modules/translation/views/translation_view.dart

6
android/app/src/main/kotlin/com/example/deep_voice/AzureAsrHelper.kt

@ -269,9 +269,9 @@ class AzureAsrHelper {
if (event.result.reason == ResultReason.RecognizingSpeech) { if (event.result.reason == ResultReason.RecognizingSpeech) {
val autoDetectSourceLanguageResult = AutoDetectSourceLanguageResult.fromResult(event.result) val autoDetectSourceLanguageResult = AutoDetectSourceLanguageResult.fromResult(event.result)
val detectedLanguage = autoDetectSourceLanguageResult?.language ?: "" val detectedLanguage = autoDetectSourceLanguageResult?.language ?: ""
if (isAutoDetectLanguage) { // if (isAutoDetectLanguage) {
Log.d(TAG, "正在识别: ${event.result.text}, 检测到的语言: $detectedLanguage") // Log.d(TAG, "正在识别: ${event.result.text}, 检测到的语言: $detectedLanguage")
} // }
callback.onRecognizing(event.result.text, detectedLanguage) callback.onRecognizing(event.result.text, detectedLanguage)
} }
} }

613
lib/modules/chat/views/chat_view.dart

@ -13,20 +13,26 @@ class ChatView extends GetView<ChatController> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold( return Scaffold(
backgroundColor: Colors.grey[50], // 使用浅灰背景,类似HTML模板 backgroundColor: isDarkMode ? AppColors.darkBackground : Colors.grey[50],
appBar: _buildAppBar(), appBar: _buildAppBar(isDarkMode),
body: _buildBody(), body: _buildBody(isDarkMode),
); );
} }
// 构建AppBar // 构建AppBar
PreferredSizeWidget _buildAppBar() { PreferredSizeWidget _buildAppBar(bool isDarkMode) {
return AppBar( return AppBar(
elevation: 1, elevation: 1,
backgroundColor: Colors.white, backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white,
leading: IconButton( leading: IconButton(
icon: Icon(Icons.arrow_back_ios, color: Colors.black87, size: 20.r), icon: Icon(
Icons.arrow_back_ios,
color: isDarkMode ? Colors.white : Colors.black87,
size: 20.r
),
onPressed: () => Get.back(), onPressed: () => Get.back(),
), ),
titleSpacing: 0, titleSpacing: 0,
@ -80,7 +86,7 @@ class ChatView extends GetView<ChatController> {
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.black87, color: isDarkMode ? Colors.white : Colors.black87,
), ),
), ),
Row( Row(
@ -117,7 +123,7 @@ class ChatView extends GetView<ChatController> {
: Icons.volume_off, : Icons.volume_off,
color: controller.isTtsEnabled.value color: controller.isTtsEnabled.value
? AppColors.primary ? AppColors.primary
: Colors.grey, : isDarkMode ? Colors.grey[400] : Colors.grey,
), ),
onPressed: controller.toggleTts, onPressed: controller.toggleTts,
)), )),
@ -127,21 +133,27 @@ class ChatView extends GetView<ChatController> {
height: 34.r, height: 34.r,
margin: EdgeInsets.only(right: 8.w), margin: EdgeInsets.only(right: 8.w),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey[100], color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
shape: BoxShape.circle, shape: BoxShape.circle,
boxShadow: [ boxShadow: [
BoxShadow( if (!isDarkMode)
color: Colors.black.withOpacity(0.05), BoxShadow(
blurRadius: 2, color: Colors.black.withOpacity(0.05),
spreadRadius: 0.5, blurRadius: 2,
), spreadRadius: 0.5,
),
], ],
), ),
child: PopupMenuButton( child: PopupMenuButton(
icon: Icon(Icons.more_vert, color: Colors.black87, size: 20.r), icon: Icon(
Icons.more_vert,
color: isDarkMode ? Colors.white : Colors.black87,
size: 20.r
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r), borderRadius: BorderRadius.circular(12.r),
), ),
color: isDarkMode ? AppColors.cardBackground : Colors.white,
itemBuilder: (context) => [ itemBuilder: (context) => [
PopupMenuItem( PopupMenuItem(
value: 'clear', value: 'clear',
@ -149,16 +161,23 @@ class ChatView extends GetView<ChatController> {
children: [ children: [
Icon(Icons.delete_outline, color: Colors.red), Icon(Icons.delete_outline, color: Colors.red),
SizedBox(width: 8.w), SizedBox(width: 8.w),
Text('清空对话'), Text(
'清空聊天记录',
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
),
),
], ],
), ),
onTap: () {
// 延迟执行,避免在弹出菜单关闭前执行
Future.delayed(
const Duration(milliseconds: 100),
() => controller.clearChat(),
);
},
), ),
], ],
onSelected: (value) {
if (value == 'clear') {
controller.clearChat();
}
},
), ),
), ),
], ],
@ -166,59 +185,43 @@ class ChatView extends GetView<ChatController> {
} }
// 构建主体内容 // 构建主体内容
Widget _buildBody() { Widget _buildBody(bool isDarkMode) {
return Column( return Column(
children: [ children: [
// 消息列表 // 聊天内容区域
Expanded( Expanded(
child: Container( child: Obx(() {
color: Colors.grey[50], // 使用浅灰背景 if (controller.messages.isEmpty) {
child: Obx(() => controller.messages.isEmpty return _buildEmptyState(isDarkMode);
? _buildEmptyChat() } else {
: _buildChatList()), return _buildChatList(isDarkMode);
), }
}),
), ),
// 输入区域 - 根据当前模式显示不同的输入面板 // 底部输入区域
Obx(() => controller.isVoiceRecognitionActive.value _buildInputArea(isDarkMode),
? _buildVoiceInputPanel()
: _buildTextInputPanel()),
], ],
); );
} }
// 构建空聊天界面 // 构建空状态
Widget _buildEmptyChat() { Widget _buildEmptyState(bool isDarkMode) {
return Center( return Center(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Container( Container(
width: 80.r, width: 80.w,
height: 80.r, height: 80.w,
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.primary.withOpacity(0.1), color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: ClipRRect( child: Icon(
borderRadius: BorderRadius.circular(40.r), Icons.chat_bubble_outline,
child: controller.agent.avatarUrl.isNotEmpty size: 40.w,
? Image.asset( color: isDarkMode ? Colors.white.withOpacity(0.5) : Colors.grey[400],
controller.agent.avatarUrl,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Icon(
Icons.smart_toy_rounded,
size: 40.r,
color: AppColors.primary,
);
},
)
: Icon(
Icons.smart_toy_rounded,
size: 40.r,
color: AppColors.primary,
),
), ),
), ),
SizedBox(height: 24.h), SizedBox(height: 24.h),
@ -227,7 +230,7 @@ class ChatView extends GetView<ChatController> {
style: TextStyle( style: TextStyle(
fontSize: 18.sp, fontSize: 18.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.black87, color: isDarkMode ? Colors.white : Colors.black87,
), ),
), ),
SizedBox(height: 12.h), SizedBox(height: 12.h),
@ -238,7 +241,7 @@ class ChatView extends GetView<ChatController> {
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
color: Colors.grey[600], color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
height: 1.5, height: 1.5,
), ),
), ),
@ -249,7 +252,7 @@ class ChatView extends GetView<ChatController> {
} }
// 构建聊天列表 // 构建聊天列表
Widget _buildChatList() { Widget _buildChatList(bool isDarkMode) {
return ListView.builder( return ListView.builder(
controller: controller.scrollController, controller: controller.scrollController,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h), padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h),
@ -259,14 +262,14 @@ class ChatView extends GetView<ChatController> {
cacheExtent: 1000, // 增加缓存范围,减少重建 cacheExtent: 1000, // 增加缓存范围,减少重建
itemBuilder: (context, index) { itemBuilder: (context, index) {
final message = controller.messages[index]; final message = controller.messages[index];
return _buildMessageItem(message); return _buildMessageItem(message, isDarkMode);
}, },
); );
} }
// 构建消息项 // 构建消息项
Widget _buildMessageItem(Message message) { Widget _buildMessageItem(Message message, bool isDarkMode) {
final bool isUser = message.role == 'user'; final isUser = message.role == 'user';
return Padding( return Padding(
padding: EdgeInsets.only(bottom: 16.h), padding: EdgeInsets.only(bottom: 16.h),
@ -274,164 +277,102 @@ class ChatView extends GetView<ChatController> {
mainAxisAlignment: isUser ? MainAxisAlignment.end : MainAxisAlignment.start, mainAxisAlignment: isUser ? MainAxisAlignment.end : MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// AI头像(非用户消息时显示) if (!isUser) _buildAvatar(false, isDarkMode),
if (!isUser)
Container( SizedBox(width: 8.w),
width: 36.r,
height: 36.r,
margin: EdgeInsets.only(right: 8.w, top: 2.h),
decoration: BoxDecoration(
color: AppColors.primary,
borderRadius: BorderRadius.circular(18.r),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 2,
offset: Offset(0, 1),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(18.r),
child: controller.agent.avatarUrl.isNotEmpty
? Image.asset(
controller.agent.avatarUrl,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Icon(
Icons.smart_toy_rounded,
color: Colors.white,
size: 20.r,
);
},
)
: Icon(
Icons.smart_toy_rounded,
color: Colors.white,
size: 20.r,
),
),
),
// 消息气泡
Flexible( Flexible(
child: Container( child: Column(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), crossAxisAlignment: isUser ? CrossAxisAlignment.end : CrossAxisAlignment.start,
decoration: BoxDecoration( children: [
color: isUser ? AppColors.primary : Colors.white, // 消息气泡
borderRadius: BorderRadius.only( Container(
topLeft: Radius.circular(isUser ? 16.r : 0), padding: EdgeInsets.all(12.r),
topRight: Radius.circular(isUser ? 0 : 16.r), decoration: BoxDecoration(
bottomLeft: Radius.circular(16.r), color: isUser
bottomRight: Radius.circular(16.r), ? AppColors.primary
), : isDarkMode ? AppColors.cardBackground : Colors.grey[200],
boxShadow: [ borderRadius: BorderRadius.circular(16.r),
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 3,
spreadRadius: 0.5,
offset: Offset(0, 1),
), ),
], child: Column(
), crossAxisAlignment: CrossAxisAlignment.start,
child: Column( children: [
crossAxisAlignment: CrossAxisAlignment.start, // 消息内容
children: [ if (message.content.isNotEmpty)
// 消息内容 SelectableText(
if (message.content.isNotEmpty) message.content,
SelectableText( style: TextStyle(
message.content, color: isUser
style: TextStyle( ? Colors.white
color: isUser ? Colors.white : Colors.black87, : isDarkMode ? Colors.white : Colors.black,
fontSize: 15.sp, fontSize: 16.sp,
height: 1.5, height: 1.5,
letterSpacing: 0.2,
),
textAlign: TextAlign.left,
maxLines: null,
enableInteractiveSelection: true,
textWidthBasis: TextWidthBasis.longestLine,
),
// 加载指示器
if (message.isLoading)
Padding(
padding: EdgeInsets.only(top: message.content.isNotEmpty ? 8.h : 0),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 12.w,
height: 12.h,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
isUser ? Colors.white : AppColors.primary,
),
),
), ),
SizedBox(width: 8.w), ),
Text(
'思考中...', // 加载中动画
style: TextStyle( if (message.isLoading)
color: isUser ? Colors.white : Colors.black54, Padding(
fontSize: 12.sp, padding: EdgeInsets.only(top: 8.h),
child: Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(
3,
(index) => _buildLoadingDot(index, isDarkMode),
), ),
), ),
], ),
), ],
),
),
// 时间戳
Padding(
padding: EdgeInsets.only(top: 4.h, left: 4.w, right: 4.w),
child: Text(
_formatTime(message.timestamp),
style: TextStyle(
fontSize: 10.sp,
color: isDarkMode ? Colors.grey[500] : Colors.grey[500],
), ),
],
),
),
),
// 用户头像(用户消息时显示)
if (isUser)
Container(
width: 36.r,
height: 36.r,
margin: EdgeInsets.only(left: 8.w, top: 2.h),
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(18.r),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 2,
offset: Offset(0, 1),
), ),
],
),
child: Center(
child: Icon(
Icons.person,
color: Colors.grey[600],
size: 20.r,
), ),
), ],
), ),
),
SizedBox(width: 8.w),
if (isUser) _buildAvatar(true, isDarkMode),
], ],
), ),
); );
} }
// 构建文本输入面板 // 构建输入区域
Widget _buildTextInputPanel() { Widget _buildInputArea(bool isDarkMode) {
final textController = TextEditingController(text: controller.inputText.value); final textController = TextEditingController(text: controller.inputText.value);
textController.addListener(() { // 检查是否处于语音识别模式
controller.inputText.value = textController.text; return Obx(() {
if (controller.isVoiceRecognitionActive.value) {
return _buildVoiceInputPanel(isDarkMode);
} else {
return _buildTextInputPanel(isDarkMode, textController);
}
}); });
}
// 构建文本输入面板
Widget _buildTextInputPanel(bool isDarkMode, TextEditingController textController) {
return Container( return Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h), padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: isDarkMode ? AppColors.cardBackground : Colors.white,
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.06), color: Colors.black.withOpacity(0.05),
blurRadius: 10, blurRadius: 3,
offset: Offset(0, -1), offset: Offset(0, -1),
), ),
], ],
@ -439,25 +380,18 @@ class ChatView extends GetView<ChatController> {
child: SafeArea( child: SafeArea(
child: Row( child: Row(
children: [ children: [
// 语音输入按钮 // 语音识别按钮
Container( Container(
width: 40.r, width: 40.w,
height: 40.r, height: 40.w,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey[100], color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
shape: BoxShape.circle, shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 3,
spreadRadius: 0.5,
),
],
), ),
child: IconButton( child: IconButton(
icon: Icon( icon: Icon(
Icons.mic_none, Icons.mic_none,
color: Colors.grey[600], color: isDarkMode ? Colors.white : Colors.grey[700],
size: 20.r, size: 20.r,
), ),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
@ -465,115 +399,99 @@ class ChatView extends GetView<ChatController> {
), ),
), ),
// 文本输入框 SizedBox(width: 8.w),
// 输入框
Expanded( Expanded(
child: Container( child: TextField(
margin: EdgeInsets.symmetric(horizontal: 8.w), controller: textController,
decoration: BoxDecoration( decoration: InputDecoration(
color: Colors.grey[100], hintText: '输入消息...',
borderRadius: BorderRadius.circular(20.r), hintStyle: TextStyle(
), color: isDarkMode ? Colors.grey[500] : Colors.grey[500]
child: TextField(
controller: textController,
decoration: InputDecoration(
hintText: '输入消息...',
hintStyle: TextStyle(color: Colors.grey[500]),
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 16.w,
vertical: 10.h,
),
), ),
maxLines: null, filled: true,
textInputAction: TextInputAction.send, fillColor: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
style: TextStyle( border: OutlineInputBorder(
fontSize: 15.sp, borderRadius: BorderRadius.circular(20.r),
color: Colors.black87, borderSide: BorderSide.none,
), ),
onSubmitted: (value) { contentPadding: EdgeInsets.symmetric(
if (value.trim().isNotEmpty) { horizontal: 16.w,
controller.sendMessage(value); vertical: 10.h,
textController.clear(); ),
} ),
}, maxLines: null,
textInputAction: TextInputAction.send,
style: TextStyle(
fontSize: 15.sp,
color: isDarkMode ? Colors.white : Colors.black87,
), ),
onSubmitted: (value) {
if (value.trim().isNotEmpty) {
controller.sendMessage(value);
textController.clear();
}
},
), ),
), ),
SizedBox(width: 8.w),
// 发送按钮 // 发送按钮
Obx(() => Container( Obx(() {
width: 40.r, final hasText = controller.inputText.value.isNotEmpty;
height: 40.r, return Container(
decoration: BoxDecoration( width: 40.w,
color: controller.inputText.value.trim().isEmpty height: 40.w,
? Colors.grey[100] decoration: BoxDecoration(
: AppColors.primary, color: hasText
shape: BoxShape.circle, ? AppColors.primary
boxShadow: controller.inputText.value.trim().isEmpty : isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[200],
? [ shape: BoxShape.circle,
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 3,
spreadRadius: 0.5,
),
]
: [
BoxShadow(
color: AppColors.primary.withOpacity(0.3),
blurRadius: 5,
spreadRadius: 0.5,
),
],
),
child: IconButton(
icon: Icon(
Icons.send,
size: 18.r,
color: controller.inputText.value.trim().isEmpty
? Colors.grey[500]
: Colors.white,
), ),
padding: EdgeInsets.zero, child: IconButton(
onPressed: controller.inputText.value.trim().isEmpty icon: Icon(
? null Icons.send,
: () { color: hasText
controller.sendMessage(controller.inputText.value); ? Colors.white
textController.clear(); : isDarkMode ? Colors.white.withOpacity(0.5) : Colors.grey[400],
}, size: 18.r,
), ),
)), padding: EdgeInsets.zero,
onPressed: hasText
? () {
controller.sendMessage(controller.inputText.value);
textController.clear();
}
: null,
),
);
}),
], ],
), ),
), ),
); );
} }
// 构建语音输入面板 // 构建语音输入面板
Widget _buildVoiceInputPanel() { Widget _buildVoiceInputPanel(bool isDarkMode) {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( color: isDarkMode ? AppColors.cardBackground : Colors.white,
begin: Alignment.topCenter, boxShadow: [
end: Alignment.bottomCenter, BoxShadow(
colors: [ color: Colors.black.withOpacity(0.05),
Colors.white, blurRadius: 3,
Color(0xFFE8D5F5), offset: Offset(0, -1),
], ),
stops: [0.3, 1.0], ],
),
// boxShadow: [
// BoxShadow(
// color: Colors.black.withOpacity(0.06),
// blurRadius: 10,
// offset: Offset(0, -1),
// ),
// ],
), ),
child: SafeArea( child: SafeArea(
child: SizedBox( child: SizedBox(
height: 80.h, height: 120.h,
child: Padding( child: Padding(
padding: EdgeInsets.symmetric(horizontal: 30.w), padding: EdgeInsets.symmetric(horizontal: 20.w),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
@ -583,7 +501,7 @@ class ChatView extends GetView<ChatController> {
width: 60.r, width: 60.r,
height: 60.r, height: 60.r,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.white,
shape: BoxShape.circle, shape: BoxShape.circle,
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
@ -639,7 +557,7 @@ class ChatView extends GetView<ChatController> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSpeaking color: isSpeaking
? AppColors.primary ? AppColors.primary
: Colors.grey.shade400, : isDarkMode ? Colors.grey.shade600 : Colors.grey.shade400,
borderRadius: BorderRadius.circular(2.r), borderRadius: BorderRadius.circular(2.r),
), ),
), ),
@ -659,7 +577,7 @@ class ChatView extends GetView<ChatController> {
controller.isSpeechDetected.value ? '正在聆听...' : '你可以开始说话', controller.isSpeechDetected.value ? '正在聆听...' : '你可以开始说话',
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
color: Colors.grey.shade700, color: isDarkMode ? Colors.grey[400] : Colors.grey[700],
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
)), )),
@ -672,7 +590,7 @@ class ChatView extends GetView<ChatController> {
width: 60.r, width: 60.r,
height: 60.r, height: 60.r,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.white,
shape: BoxShape.circle, shape: BoxShape.circle,
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
@ -686,7 +604,7 @@ class ChatView extends GetView<ChatController> {
child: IconButton( child: IconButton(
icon: Icon( icon: Icon(
Icons.close, Icons.close,
color: Colors.grey.shade600, color: isDarkMode ? Colors.white : Colors.grey.shade600,
size: 28.r, size: 28.r,
), ),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
@ -701,4 +619,87 @@ class ChatView extends GetView<ChatController> {
), ),
); );
} }
// 构建头像
Widget _buildAvatar(bool isUser, bool isDarkMode) {
return Container(
width: 36.r,
height: 36.r,
decoration: BoxDecoration(
color: isUser
? (isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[200])
: AppColors.primary,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 2,
offset: Offset(0, 1),
),
],
),
child: isUser
? Center(
child: Icon(
Icons.person,
color: isDarkMode ? Colors.white : Colors.grey[700],
size: 20.r,
),
)
: ClipRRect(
borderRadius: BorderRadius.circular(18.r),
child: controller.agent.avatarUrl.isNotEmpty
? Image.asset(
controller.agent.avatarUrl,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Icon(
Icons.smart_toy_rounded,
color: Colors.white,
size: 20.r,
);
},
)
: Icon(
Icons.smart_toy_rounded,
color: Colors.white,
size: 20.r,
),
),
);
}
// 构建加载动画点
Widget _buildLoadingDot(int index, bool isDarkMode) {
return AnimatedBuilder(
animation: AlwaysStoppedAnimation(0),
builder: (context, child) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 2.w),
width: 8.r,
height: 8.r,
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.5) : Colors.grey[400],
shape: BoxShape.circle,
),
);
},
);
}
// 格式化时间
String _formatTime(DateTime time) {
final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day);
final yesterday = today.subtract(const Duration(days: 1));
final messageDate = DateTime(time.year, time.month, time.day);
if (messageDate == today) {
return '今天 ${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}';
} else if (messageDate == yesterday) {
return '昨天 ${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}';
} else {
return '${time.month}月${time.day}日 ${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}';
}
}
} }

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

@ -27,13 +27,7 @@ class SettingsView extends GetView<SettingsController> {
), ),
), ),
centerTitle: true, centerTitle: true,
leading: IconButton(
icon: Icon(
Icons.arrow_back_ios,
color: isDarkMode ? Colors.white70 : Colors.black54
),
onPressed: () => Get.back(),
),
), ),
body: ListView( body: ListView(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),

295
lib/modules/translation/controllers/translation_controller.dart

@ -8,7 +8,7 @@ import '../../../data/services/azure_asr_service.dart';
import '../../../data/services/azure_tts_service.dart'; import '../../../data/services/azure_tts_service.dart';
import '../../../data/services/language_manager.dart'; import '../../../data/services/language_manager.dart';
import '../../../core/utils/logger.dart'; import '../../../core/utils/logger.dart';
import '../../../data/services/voice_interaction_service.dart';
class TranslationController extends GetxController { class TranslationController extends GetxController {
// 服务 // 服务
final AzureAsrService _asrService = Get.find<AzureAsrService>(); final AzureAsrService _asrService = Get.find<AzureAsrService>();
@ -16,9 +16,12 @@ class TranslationController extends GetxController {
final AzureTtsService _ttsService = Get.find<AzureTtsService>(); final AzureTtsService _ttsService = Get.find<AzureTtsService>();
final LanguageManager _languageManager = Get.find<LanguageManager>(); final LanguageManager _languageManager = Get.find<LanguageManager>();
final GetStorage _storage = GetStorage(); final GetStorage _storage = GetStorage();
final VoiceInteractionService _voiceInteractionService = Get.find<VoiceInteractionService>();
// 存储相关 // 存储相关
static const String _historyKey = 'translation_history'; static const String _historyKey = 'translation_history';
static const String _sourceLanguageKey = 'translation_source_language';
static const String _targetLanguageKey = 'translation_target_language';
static const int _maxHistoryItems = 100; static const int _maxHistoryItems = 100;
// 状态变量 // 状态变量
@ -43,9 +46,8 @@ class TranslationController extends GetxController {
// 其他 // 其他
StreamSubscription? _recognitionSubscription; StreamSubscription? _recognitionSubscription;
StreamSubscription? _voiceInteractionSubscription;
Timer? _translationDebounceTimer; Timer? _translationDebounceTimer;
Timer? _scrollDebounceTimer; // 滚动防抖定时器
bool _isNearBottom = true; // 标记是否在底部附近
// 获取支持的语言列表 // 获取支持的语言列表
Map<String, String> get supportedLanguages => _languageManager.getChineseNameToAsrCodeMap(); Map<String, String> get supportedLanguages => _languageManager.getChineseNameToAsrCodeMap();
@ -53,75 +55,73 @@ class TranslationController extends GetxController {
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
_initController();
} // 加载上次保存的语言设置
_loadSavedLanguages();
// 初始化语言代码
final sourceAsrCode = _languageManager.getAsrCodeByChineseName(sourceLanguage.value);
if (sourceAsrCode != null) {
sourceLanguageCode.value = sourceAsrCode;
}
final targetAsrCode = _languageManager.getAsrCodeByChineseName(targetLanguage.value);
if (targetAsrCode != null) {
targetLanguageCode.value = targetAsrCode;
}
// 订阅VoiceInteractionService事件
_subscribeToVoiceInteractionEvents();
// 初始化控制器 // 加载历史记录
void _initController() {
_initLanguageCodes();
loadTranslationHistory(); loadTranslationHistory();
// 初始化服务
_initServices(); _initServices();
_setupListeners();
// 初始化完成后滚动到底部 // 使用微任务在布局完成后自动滚动到底部
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
// 延迟 300ms 再次尝试滚动,确保列表已完全渲染 _scrollToBottom();
Future.delayed(const Duration(milliseconds: 300), () {
scrollToBottom(animate: false);
});
}); });
} }
// 重新初始化控制器(公共方法) /// 订阅VoiceInteractionService事件
void reinitialize() { void _subscribeToVoiceInteractionEvents() {
_setupListeners(); if (_voiceInteractionService.eventStream != null) {
_voiceInteractionSubscription = _voiceInteractionService.eventStream!.listen((event) {
if (event is RecognitionStartedEvent) {
// 收到recognitionStarted事件时,停止当前的AI交互、录音和TTS
stopAll();
}
});
}
}
// 加载保存的语言设置
void _loadSavedLanguages() {
final savedSourceLanguage = _storage.read(_sourceLanguageKey);
final savedTargetLanguage = _storage.read(_targetLanguageKey);
// 初始化完成后滚动到底部 if (savedSourceLanguage != null && supportedLanguages.containsKey(savedSourceLanguage)) {
WidgetsBinding.instance.addPostFrameCallback((_) { sourceLanguage.value = savedSourceLanguage;
scrollToBottom(animate: false); }
});
if (savedTargetLanguage != null && supportedLanguages.containsKey(savedTargetLanguage)) {
targetLanguage.value = savedTargetLanguage;
}
} }
// 仅设置滚动监听器(不启动翻译) // 保存当前语言设置
void setupScrollListener() { void _saveLanguageSettings() {
// 移除之前的监听器,避免重复添加 _storage.write(_sourceLanguageKey, sourceLanguage.value);
scrollController.removeListener(_onScroll); _storage.write(_targetLanguageKey, targetLanguage.value);
// 重新添加滚动监听器
scrollController.addListener(_onScroll);
} }
// 初始化服务 // 初始化服务
Future<void> _initServices() async { Future<void> _initServices() async {
await _initTranslationService();
await _initAzureAsrService();
}
// 初始化语言代码
void _initLanguageCodes() {
final sourceAsrCode = _languageManager.getAsrCodeByChineseName(sourceLanguage.value);
if (sourceAsrCode != null) {
sourceLanguageCode.value = sourceAsrCode;
}
final targetAsrCode = _languageManager.getAsrCodeByChineseName(targetLanguage.value);
if (targetAsrCode != null) {
targetLanguageCode.value = targetAsrCode;
}
}
// 初始化翻译服务
Future<void> _initTranslationService() async {
try { try {
await _translationService.initialize(); await _translationService.initialize();
} catch (e) {
Logger.error('初始化翻译服务失败: $e');
}
}
// 初始化语音识别服务
Future<void> _initAzureAsrService() async {
try {
final List<String> asrSupportedLanguages = [sourceLanguageCode.value, targetLanguageCode.value]; final List<String> asrSupportedLanguages = [sourceLanguageCode.value, targetLanguageCode.value];
await _ttsService.initialize(); await _ttsService.initialize();
await _asrService.initialize( await _asrService.initialize(
@ -129,61 +129,19 @@ class TranslationController extends GetxController {
supportedLanguages: asrSupportedLanguages, supportedLanguages: asrSupportedLanguages,
); );
} catch (e) { } catch (e) {
Logger.error('初始化语音识别服务失败: $e'); Logger.error('初始化服务失败: $e');
} }
} }
// 设置监听器
void _setupListeners() {
// 监听历史记录变化,当有新项添加时滚动到底部
ever(translationHistory, (_) {
if (_isNearBottom) {
scrollToBottom();
}
});
// 监听当前项变化,当有新项添加时滚动到底部
ever(currentItem, (item) {
if (item != null && _isNearBottom) {
scrollToBottom();
}
});
// 监听滚动位置,判断是否在底部附近
scrollController.addListener(_onScroll);
}
// 滚动监听器
void _onScroll() {
if (!scrollController.hasClients) return;
// 判断是否在底部附近(距离底部20像素内)
final maxScroll = scrollController.position.maxScrollExtent;
final currentScroll = scrollController.position.pixels;
_isNearBottom = (maxScroll - currentScroll) < 20;
}
@override @override
void onClose() { void onClose() {
stopRecognition(); stopRecognition();
_recognitionSubscription?.cancel(); _recognitionSubscription?.cancel();
_translationDebounceTimer?.cancel(); _translationDebounceTimer?.cancel();
_scrollDebounceTimer?.cancel();
scrollController.removeListener(_onScroll);
scrollController.dispose(); scrollController.dispose();
super.onClose(); super.onClose();
} }
// 更新ASR支持的语言
Future<void> _updateAsrSupportedLanguages() async {
try {
final List<String> asrSupportedLanguages = [sourceLanguageCode.value, targetLanguageCode.value];
await _asrService.setSupportedLanguages(asrSupportedLanguages);
} catch (e) {
Logger.error('更新ASR支持的语言失败: $e');
}
}
// 切换TTS功能 // 切换TTS功能
void toggleTts() => isTtsEnabled.toggle(); void toggleTts() => isTtsEnabled.toggle();
@ -191,8 +149,13 @@ class TranslationController extends GetxController {
Future<void> startRecognition() async { Future<void> startRecognition() async {
if (isRecognizing.value) return; if (isRecognizing.value) return;
_voiceInteractionService.pauseVoiceInteraction();
try { try {
await _updateAsrSupportedLanguages(); // 更新ASR支持的语言
final List<String> asrSupportedLanguages = [sourceLanguageCode.value, targetLanguageCode.value];
await _asrService.setSupportedLanguages(asrSupportedLanguages);
currentSessionId = DateTime.now().millisecondsSinceEpoch.toString(); currentSessionId = DateTime.now().millisecondsSinceEpoch.toString();
// 初始化新的翻译项 // 初始化新的翻译项
@ -283,48 +246,48 @@ class TranslationController extends GetxController {
timestamp: DateTime.now(), timestamp: DateTime.now(),
sessionId: currentSessionId!, sessionId: currentSessionId!,
isFirstInSession: translationHistory.isEmpty || translationHistory.last.sessionId != currentSessionId, isFirstInSession: translationHistory.isEmpty || translationHistory.last.sessionId != currentSessionId,
isIntermediate: true, // 保留标记,用于内部逻辑 isIntermediate: true,
); );
// 添加新项后滚动到底部
_scrollToBottom();
} else { } else {
// 更新当前项 // 更新当前项
currentItem.value!.sourceText = text; currentItem.value!.sourceText = text;
currentItem.value!.isIntermediate = true; // 保留标记,用于内部逻辑 currentItem.value!.isIntermediate = true;
currentItem.refresh(); currentItem.refresh();
// 更新后滚动到底部
_scrollToBottom();
} }
// 防抖处理翻译请求 // 防抖处理翻译请求
_translationDebounceTimer?.cancel(); _translationDebounceTimer?.cancel();
_translationDebounceTimer = Timer(const Duration(milliseconds: 300), () { _translationDebounceTimer = Timer(const Duration(milliseconds: 300), () {
translateCurrentItem(isIntermediate: true); if (currentItem.value != null && currentItem.value!.sourceText.isNotEmpty) {
translateText(currentItem.value!.sourceText);
}
}); });
} }
// 翻译当前项
Future<void> translateCurrentItem({bool isIntermediate = false}) async {
if (currentItem.value == null || currentItem.value!.sourceText.isEmpty) return;
await translateText(currentItem.value!.sourceText);
}
// 处理最终识别结果 // 处理最终识别结果
Future<void> handleFinalResult(String text) async { Future<void> handleFinalResult(String text) async {
if (text.isEmpty || currentItem.value == null) return; if (text.isEmpty || currentItem.value == null) return;
// 更新当前项,但不立即添加到历史记录 // 更新当前项,但不立即添加到历史记录
currentItem.value!.sourceText = text; currentItem.value!.sourceText = text;
currentItem.value!.isIntermediate = false; // 标记为最终结果 currentItem.value!.isIntermediate = false;
currentItem.refresh(); currentItem.refresh();
// 更新后滚动到底部
_scrollToBottom();
// 先翻译,保持UI稳定 // 先翻译,保持UI稳定
final translationResult = await translateText(text); final translationResult = await translateText(text);
// 给UI一点时间更新
await Future.delayed(const Duration(milliseconds: 100));
// 翻译完成后再添加到历史记录 // 翻译完成后再添加到历史记录
if (currentItem.value != null) { if (currentItem.value != null) {
// 确保添加到历史记录的是最终结果
translationHistory.add(currentItem.value!); translationHistory.add(currentItem.value!);
saveTranslationHistory(); saveTranslationHistory();
// 添加到历史记录后滚动到底部
_scrollToBottom();
// 播放TTS // 播放TTS
if (translationResult != null && isTtsEnabled.value) { if (translationResult != null && isTtsEnabled.value) {
@ -357,7 +320,28 @@ class TranslationController extends GetxController {
); );
if (translationResult != null) { if (translationResult != null) {
_updateTranslationResult(sourceText, translationResult, detectedSourceLanguageCode, detectedTargetLanguageCode); // 更新翻译结果
if (currentItem.value != null && currentItem.value!.sourceText.contains(sourceText)) {
// 更新当前项
currentItem.value!.translatedText = translationResult;
currentItem.value!.sourceLanguageCode = detectedSourceLanguageCode;
currentItem.value!.targetLanguageCode = detectedTargetLanguageCode;
currentItem.refresh();
// 更新后滚动到底部
_scrollToBottom();
} else {
// 更新历史记录
final index = translationHistory.indexWhere((item) => item.sourceText == sourceText);
if (index != -1) {
translationHistory[index].translatedText = translationResult;
translationHistory[index].sourceLanguageCode = detectedSourceLanguageCode;
translationHistory[index].targetLanguageCode = detectedTargetLanguageCode;
translationHistory.refresh();
saveTranslationHistory();
// 更新后滚动到底部
_scrollToBottom();
}
}
} }
} catch (e) { } catch (e) {
Logger.error('翻译失败: $e'); Logger.error('翻译失败: $e');
@ -368,27 +352,6 @@ class TranslationController extends GetxController {
return translationResult; return translationResult;
} }
// 更新翻译结果
void _updateTranslationResult(String sourceText, String result, String sourceCode, String targetCode) {
if (currentItem.value != null && currentItem.value!.sourceText.contains(sourceText)) {
// 更新当前项
currentItem.value!.translatedText = result;
currentItem.value!.sourceLanguageCode = sourceCode;
currentItem.value!.targetLanguageCode = targetCode;
currentItem.refresh();
} else {
// 更新历史记录
final index = translationHistory.indexWhere((item) => item.sourceText == sourceText);
if (index != -1) {
translationHistory[index].translatedText = result;
translationHistory[index].sourceLanguageCode = sourceCode;
translationHistory[index].targetLanguageCode = targetCode;
translationHistory.refresh();
saveTranslationHistory();
}
}
}
// 播放翻译文本 // 播放翻译文本
Future<void> playTranslatedText(String text) async { Future<void> playTranslatedText(String text) async {
if (text.isEmpty) return; if (text.isEmpty) return;
@ -412,10 +375,7 @@ class TranslationController extends GetxController {
await stopRecognition(); await stopRecognition();
} }
// 取消所有定时器
_translationDebounceTimer?.cancel(); _translationDebounceTimer?.cancel();
_scrollDebounceTimer?.cancel();
await _ttsService.stop(); await _ttsService.stop();
} catch (e) { } catch (e) {
Logger.error('停止操作失败: $e'); Logger.error('停止操作失败: $e');
@ -451,30 +411,26 @@ class TranslationController extends GetxController {
} }
} }
// 简洁高效的滚动到底部方法 // 滚动到底部
void scrollToBottom({bool animate = true}) { void _scrollToBottom() {
if (!scrollController.hasClients) return; if (scrollController.hasClients && (translationHistory.isNotEmpty || currentItem.value != null)) {
try {
// 取消之前的定时器
_scrollDebounceTimer?.cancel();
// 创建新的定时器,防抖处理
_scrollDebounceTimer = Timer(const Duration(milliseconds: 100), () {
if (!scrollController.hasClients) return;
if (animate) {
scrollController.animateTo( scrollController.animateTo(
scrollController.position.maxScrollExtent, scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 300),
curve: Curves.easeOut, curve: Curves.easeOut,
); );
} else { } catch (e) {
scrollController.jumpTo(scrollController.position.maxScrollExtent); Logger.error('滚动到底部失败: $e');
} }
}); }
} }
// 设置滚动监听器(简化版,不再需要实际监听)
void setupScrollListener() {
// 简化后的空方法,保留接口兼容性
}
// 底部控制栏高度 // 底部控制栏高度
double _bottomBarHeight = 0.0; double _bottomBarHeight = 0.0;
@ -488,7 +444,6 @@ class TranslationController extends GetxController {
// 切换源语言和目标语言 // 切换源语言和目标语言
void swapLanguages() { void swapLanguages() {
// 先停止当前的翻译操作
stopAll(); stopAll();
final tempName = sourceLanguage.value; final tempName = sourceLanguage.value;
@ -498,12 +453,14 @@ class TranslationController extends GetxController {
final tempCode = sourceLanguageCode.value; final tempCode = sourceLanguageCode.value;
sourceLanguageCode.value = targetLanguageCode.value; sourceLanguageCode.value = targetLanguageCode.value;
targetLanguageCode.value = tempCode; targetLanguageCode.value = tempCode;
// 保存语言设置
_saveLanguageSettings();
} }
// 设置源语言 // 设置源语言
void setSourceLanguage(String language) { void setSourceLanguage(String language) {
if (sourceLanguage.value != language) { if (sourceLanguage.value != language) {
// 先停止当前的翻译操作
stopAll(); stopAll();
sourceLanguage.value = language; sourceLanguage.value = language;
@ -512,20 +469,15 @@ class TranslationController extends GetxController {
if (asrCode != null) { if (asrCode != null) {
sourceLanguageCode.value = asrCode; sourceLanguageCode.value = asrCode;
} }
_updateAsrSupportedLanguages(); // 保存语言设置
_saveLanguageSettings();
if (isRecognizing.value) {
stopRecognition();
startRecognition();
}
} }
} }
// 设置目标语言 // 设置目标语言
void setTargetLanguage(String language) { void setTargetLanguage(String language) {
if (targetLanguage.value != language) { if (targetLanguage.value != language) {
// 先停止当前的翻译操作
stopAll(); stopAll();
targetLanguage.value = language; targetLanguage.value = language;
@ -535,7 +487,8 @@ class TranslationController extends GetxController {
targetLanguageCode.value = asrCode; targetLanguageCode.value = asrCode;
} }
_updateAsrSupportedLanguages(); // 保存语言设置
_saveLanguageSettings();
} }
} }
@ -546,6 +499,11 @@ class TranslationController extends GetxController {
try { try {
final List<dynamic> historyList = jsonDecode(historyJson); final List<dynamic> historyList = jsonDecode(historyJson);
translationHistory.value = historyList.map((item) => TranslationItem.fromJson(item)).toList(); translationHistory.value = historyList.map((item) => TranslationItem.fromJson(item)).toList();
// 使用微任务确保在视图完全渲染后滚动到底部
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToBottom();
});
} catch (e) { } catch (e) {
_storage.remove(_historyKey); _storage.remove(_historyKey);
Logger.error('加载翻译历史失败: $e'); Logger.error('加载翻译历史失败: $e');
@ -572,6 +530,11 @@ class TranslationController extends GetxController {
translationHistory.clear(); translationHistory.clear();
_storage.remove(_historyKey); _storage.remove(_historyKey);
} }
// 滚动到底部 (公共方法,用于向后兼容)
void scrollToBottom({bool animate = true}) {
_scrollToBottom();
}
} }
// 翻译项模型 // 翻译项模型

132
lib/modules/translation/views/translation_detail_view.dart

@ -7,6 +7,7 @@ import '../controllers/translation_detail_controller.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import '../../../core/theme/app_colors.dart';
class TranslationDetailView extends GetView<TranslationDetailController> { class TranslationDetailView extends GetView<TranslationDetailController> {
TranslationDetailView({Key? key}) : super(key: key); TranslationDetailView({Key? key}) : super(key: key);
@ -33,19 +34,25 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold( return Scaffold(
backgroundColor: Color(0xFFF5F5F8), backgroundColor: isDarkMode ? AppColors.darkBackground : Color(0xFFF5F5F8),
appBar: _buildAppBar(), appBar: _buildAppBar(isDarkMode),
body: Obx(() { body: Obx(() {
// 显示加载状态 // 显示加载状态
if (controller.isLoading.value) { if (controller.isLoading.value) {
return const Center(child: CircularProgressIndicator()); return Center(
child: CircularProgressIndicator(
color: isDarkMode ? AppColors.primary : Colors.blue,
),
);
} }
return Column( return Column(
children: [ children: [
_buildTabButtons(), _buildTabButtons(isDarkMode),
_buildContentArea(), _buildContentArea(isDarkMode),
], ],
); );
}), }),
@ -53,14 +60,14 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
} }
// 构建应用栏 // 构建应用栏
PreferredSizeWidget _buildAppBar() { PreferredSizeWidget _buildAppBar(bool isDarkMode) {
return AppBar( return AppBar(
backgroundColor: Color(0xFFF5F5F8), backgroundColor: isDarkMode ? AppColors.cardBackground : Color(0xFFF5F5F8),
elevation: 0, elevation: 0,
leading: IconButton( leading: IconButton(
icon: Icon( icon: Icon(
Icons.arrow_back_ios, Icons.arrow_back_ios,
color: Colors.black, color: isDarkMode ? Colors.white : Colors.black,
size: 20.sp, size: 20.sp,
), ),
onPressed: () => Get.back(), onPressed: () => Get.back(),
@ -68,7 +75,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
title: Text( title: Text(
'Detail', 'Detail',
style: TextStyle( style: TextStyle(
color: Colors.black, color: isDarkMode ? Colors.white : Colors.black,
fontSize: 24.sp, fontSize: 24.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
@ -78,7 +85,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
IconButton( IconButton(
icon: Icon( icon: Icon(
Icons.share, Icons.share,
color: Colors.black, color: isDarkMode ? Colors.white : Colors.black,
size: 22.sp, size: 22.sp,
), ),
onPressed: _shareContent, onPressed: _shareContent,
@ -88,7 +95,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
} }
// 构建选项卡按钮 // 构建选项卡按钮
Widget _buildTabButtons() { Widget _buildTabButtons(bool isDarkMode) {
return Padding( return Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
child: Row( child: Row(
@ -98,6 +105,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
title: '原始记录', title: '原始记录',
isSelected: controller.selectedTabIndex.value == 0, isSelected: controller.selectedTabIndex.value == 0,
onTap: () => controller.switchTab(0), onTap: () => controller.switchTab(0),
isDarkMode: isDarkMode,
)), )),
), ),
SizedBox(width: 12.w), SizedBox(width: 12.w),
@ -106,6 +114,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
title: '记录摘要', title: '记录摘要',
isSelected: controller.selectedTabIndex.value == 1, isSelected: controller.selectedTabIndex.value == 1,
onTap: () => controller.switchTab(1), onTap: () => controller.switchTab(1),
isDarkMode: isDarkMode,
)), )),
), ),
], ],
@ -114,7 +123,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
} }
// 构建内容区域 // 构建内容区域
Widget _buildContentArea() { Widget _buildContentArea(bool isDarkMode) {
return Expanded( return Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 16.w), padding: EdgeInsets.symmetric(horizontal: 16.w),
@ -122,14 +131,14 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildTimestamp(), _buildTimestamp(isDarkMode),
SizedBox(height: 8.h), SizedBox(height: 8.h),
// 根据选中的选项卡显示不同内容 // 根据选中的选项卡显示不同内容
if (controller.selectedTabIndex.value == 0) if (controller.selectedTabIndex.value == 0)
..._buildOriginalRecords() ..._buildOriginalRecords(isDarkMode)
else else
_buildSummaryWidget(), _buildSummaryWidget(isDarkMode),
SizedBox(height: 24.h), SizedBox(height: 24.h),
], ],
@ -140,7 +149,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
} }
// 构建时间戳 // 构建时间戳
Widget _buildTimestamp() { Widget _buildTimestamp(bool isDarkMode) {
return Padding( return Padding(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 8.h), padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 8.h),
child: Text( child: Text(
@ -148,7 +157,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Colors.grey[600], color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
letterSpacing: 1.0, letterSpacing: 1.0,
), ),
), ),
@ -156,8 +165,8 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
} }
// 构建原始记录列表 // 构建原始记录列表
List<Widget> _buildOriginalRecords() { List<Widget> _buildOriginalRecords(bool isDarkMode) {
return controller.sessionItems.map((sessionItem) => _buildSessionItemWidget(sessionItem)).toList(); return controller.sessionItems.map((sessionItem) => _buildSessionItemWidget(sessionItem, isDarkMode)).toList();
} }
// 构建选项卡按钮 // 构建选项卡按钮
@ -165,17 +174,20 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
required String title, required String title,
required bool isSelected, required bool isSelected,
required VoidCallback onTap, required VoidCallback onTap,
required bool isDarkMode,
}) { }) {
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: onTap,
child: Container( child: Container(
height: 40.h, height: 40.h,
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected ? Colors.blue : Colors.white, color: isSelected
? (isDarkMode ? AppColors.primary : Colors.blue)
: (isDarkMode ? AppColors.cardBackground : Colors.white),
borderRadius: BorderRadius.circular(20.r), borderRadius: BorderRadius.circular(20.r),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withAlpha(8), color: Colors.black.withAlpha(isDarkMode ? 30 : 8),
blurRadius: 4, blurRadius: 4,
offset: Offset(0, 1), offset: Offset(0, 1),
), ),
@ -187,7 +199,9 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
style: TextStyle( style: TextStyle(
fontSize: 15.sp, fontSize: 15.sp,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
color: isSelected ? Colors.white : Colors.black87, color: isSelected
? Colors.white
: (isDarkMode ? Colors.white : Colors.black87),
), ),
), ),
), ),
@ -195,16 +209,16 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
} }
// 构建会话详情中的翻译项 // 构建会话详情中的翻译项
Widget _buildSessionItemWidget(TranslationItem item) { Widget _buildSessionItemWidget(TranslationItem item, bool isDarkMode) {
return Container( return Container(
width: double.infinity, width: double.infinity,
margin: EdgeInsets.only(bottom: 16.h), margin: EdgeInsets.only(bottom: 16.h),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: isDarkMode ? AppColors.cardBackground : Colors.white,
borderRadius: BorderRadius.circular(16.r), borderRadius: BorderRadius.circular(16.r),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withAlpha(8), color: Colors.black.withAlpha(isDarkMode ? 30 : 8),
blurRadius: 8, blurRadius: 8,
offset: Offset(0, 2), offset: Offset(0, 2),
), ),
@ -220,7 +234,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
item.sourceText, item.sourceText,
style: TextStyle( style: TextStyle(
fontSize: 15.sp, fontSize: 15.sp,
color: Colors.black, color: isDarkMode ? Colors.white : Colors.black,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
@ -236,13 +250,13 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
item.translatedText, item.translatedText,
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
color: Colors.grey[700], color: isDarkMode ? Colors.grey[400] : Colors.grey[700],
fontWeight: FontWeight.normal, fontWeight: FontWeight.normal,
), ),
), ),
), ),
SizedBox(width: 12.w), SizedBox(width: 12.w),
_buildPlayButton(() => controller.playTranslationItem(item)), _buildPlayButton(() => controller.playTranslationItem(item), isDarkMode),
], ],
), ),
), ),
@ -252,23 +266,23 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
} }
// 构建播放按钮 // 构建播放按钮
Widget _buildPlayButton(VoidCallback onTap) { Widget _buildPlayButton(VoidCallback onTap, bool isDarkMode) {
return Material( return Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
onTap: onTap, onTap: onTap,
borderRadius: BorderRadius.circular(20.r), borderRadius: BorderRadius.circular(20.r),
splashColor: Colors.blue.withOpacity(0.2), splashColor: isDarkMode ? AppColors.primary.withOpacity(0.2) : Colors.blue.withOpacity(0.2),
child: Container( child: Container(
width: 28.w, width: 28.w,
height: 28.h, height: 28.h,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.blue.withAlpha(10), color: isDarkMode ? AppColors.primary.withAlpha(10) : Colors.blue.withAlpha(10),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: Icon( child: Icon(
Icons.volume_up, Icons.volume_up,
color: Colors.blue, color: Colors.white,
size: 16.sp, size: 16.sp,
), ),
), ),
@ -277,34 +291,34 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
} }
// 构建总结视图 // 构建总结视图
Widget _buildSummaryWidget() { Widget _buildSummaryWidget(bool isDarkMode) {
final String combinedSourceText = _getCombinedSourceText(); final String combinedSourceText = _getCombinedSourceText();
return Column( return Column(
children: [ children: [
// 摘要内容容器 // 摘要内容容器
_buildSummaryContainer(), _buildSummaryContainer(isDarkMode),
// 提取摘要按钮 // 提取摘要按钮
Obx(() => _buildExtractSummaryButton(combinedSourceText)), Obx(() => _buildExtractSummaryButton(combinedSourceText, isDarkMode)),
// 重新生成按钮 // 重新生成按钮
Obx(() => _buildRegenerateSummaryButton(combinedSourceText)), Obx(() => _buildRegenerateSummaryButton(combinedSourceText, isDarkMode)),
], ],
); );
} }
// 构建摘要容器 // 构建摘要容器
Widget _buildSummaryContainer() { Widget _buildSummaryContainer(bool isDarkMode) {
return Container( return Container(
width: double.infinity, width: double.infinity,
margin: EdgeInsets.only(bottom: 16.h), margin: EdgeInsets.only(bottom: 16.h),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: isDarkMode ? AppColors.cardBackground : Colors.white,
borderRadius: BorderRadius.circular(16.r), borderRadius: BorderRadius.circular(16.r),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withAlpha(8), color: Colors.black.withAlpha(isDarkMode ? 30 : 8),
blurRadius: 8, blurRadius: 8,
offset: Offset(0, 2), offset: Offset(0, 2),
), ),
@ -312,16 +326,16 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
), ),
child: Obx(() { child: Obx(() {
if (controller.summaryResult.value.isEmpty) { if (controller.summaryResult.value.isEmpty) {
return _buildEmptySummaryPrompt(); return _buildEmptySummaryPrompt(isDarkMode);
} else { } else {
return _buildSummaryContent(); return _buildSummaryContent(isDarkMode);
} }
}), }),
); );
} }
// 构建空摘要提示 // 构建空摘要提示
Widget _buildEmptySummaryPrompt() { Widget _buildEmptySummaryPrompt(bool isDarkMode) {
return Padding( return Padding(
padding: EdgeInsets.all(24.r), padding: EdgeInsets.all(24.r),
child: Center( child: Center(
@ -329,7 +343,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
'点击下方按钮生成会话摘要', '点击下方按钮生成会话摘要',
style: TextStyle( style: TextStyle(
fontSize: 15.sp, fontSize: 15.sp,
color: Colors.grey[600], color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -339,7 +353,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
} }
// 构建摘要内容 // 构建摘要内容
Widget _buildSummaryContent() { Widget _buildSummaryContent(bool isDarkMode) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -353,7 +367,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.black, color: isDarkMode ? Colors.white : Colors.black,
), ),
), ),
SizedBox(height: 12.h), SizedBox(height: 12.h),
@ -361,7 +375,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
controller.summaryResult.value, controller.summaryResult.value,
style: TextStyle( style: TextStyle(
fontSize: 15.sp, fontSize: 15.sp,
color: Colors.black87, color: isDarkMode ? Colors.grey[400] : Colors.grey[800],
height: 1.5, height: 1.5,
), ),
), ),
@ -371,7 +385,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
Divider( Divider(
height: 1, height: 1,
thickness: 0.5, thickness: 0.5,
color: Colors.grey[200], color: isDarkMode ? Colors.grey[700] : Colors.grey[200],
), ),
Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: 12.r, vertical: 8.r), padding: EdgeInsets.symmetric(horizontal: 12.r, vertical: 8.r),
@ -384,12 +398,12 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
child: InkWell( child: InkWell(
onTap: () => controller.playText(controller.summaryResult.value), onTap: () => controller.playText(controller.summaryResult.value),
borderRadius: BorderRadius.circular(20.r), borderRadius: BorderRadius.circular(20.r),
splashColor: Colors.blue.withOpacity(0.2), splashColor: isDarkMode ? AppColors.primary.withOpacity(0.2) : Colors.blue.withOpacity(0.2),
child: Padding( child: Padding(
padding: EdgeInsets.all(8.r), padding: EdgeInsets.all(8.r),
child: Icon( child: Icon(
Icons.volume_up, Icons.volume_up,
color: Colors.blue, color: isDarkMode ? Colors.white : Colors.blue,
size: 18.sp, size: 18.sp,
), ),
), ),
@ -402,12 +416,12 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
child: InkWell( child: InkWell(
onTap: _copySummaryToClipboard, onTap: _copySummaryToClipboard,
borderRadius: BorderRadius.circular(20.r), borderRadius: BorderRadius.circular(20.r),
splashColor: Colors.blue.withOpacity(0.2), splashColor: isDarkMode ? AppColors.primary.withOpacity(0.2) : Colors.blue.withOpacity(0.2),
child: Padding( child: Padding(
padding: EdgeInsets.all(8.r), padding: EdgeInsets.all(8.r),
child: Icon( child: Icon(
Icons.copy, Icons.copy,
color: Colors.blue, color: isDarkMode ? Colors.white : Colors.blue,
size: 18.sp, size: 18.sp,
), ),
), ),
@ -432,7 +446,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
} }
// 构建提取摘要按钮 // 构建提取摘要按钮
Widget _buildExtractSummaryButton(String combinedSourceText) { Widget _buildExtractSummaryButton(String combinedSourceText, bool isDarkMode) {
final bool isDisabled = controller.isGeneratingSummary.value || controller.summaryResult.value.isNotEmpty; final bool isDisabled = controller.isGeneratingSummary.value || controller.summaryResult.value.isNotEmpty;
return Container( return Container(
@ -441,7 +455,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
child: ElevatedButton( child: ElevatedButton(
onPressed: isDisabled ? null : () => _extractSummary(combinedSourceText), onPressed: isDisabled ? null : () => _extractSummary(combinedSourceText),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue, backgroundColor: isDarkMode ? AppColors.primary : Colors.blue,
foregroundColor: Colors.white, foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 12.h), padding: EdgeInsets.symmetric(vertical: 12.h),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@ -449,11 +463,11 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
), ),
elevation: 0, elevation: 0,
disabledBackgroundColor: controller.isGeneratingSummary.value disabledBackgroundColor: controller.isGeneratingSummary.value
? Colors.blue.withAlpha(128) ? isDarkMode ? AppColors.primary.withAlpha(128) : Colors.blue.withAlpha(128)
: Colors.grey.withAlpha(128), : Colors.grey.withAlpha(128),
), ),
child: controller.isGeneratingSummary.value child: controller.isGeneratingSummary.value
? _buildGeneratingIndicator() ? _buildGeneratingIndicator(isDarkMode)
: Text( : Text(
controller.summaryResult.value.isEmpty ? '提取摘要' : '已生成摘要', controller.summaryResult.value.isEmpty ? '提取摘要' : '已生成摘要',
style: TextStyle( style: TextStyle(
@ -466,7 +480,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
} }
// 构建生成中指示器 // 构建生成中指示器
Widget _buildGeneratingIndicator() { Widget _buildGeneratingIndicator(bool isDarkMode) {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
@ -491,7 +505,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
} }
// 构建重新生成按钮 // 构建重新生成按钮
Widget _buildRegenerateSummaryButton(String combinedSourceText) { Widget _buildRegenerateSummaryButton(String combinedSourceText, bool isDarkMode) {
if (controller.summaryResult.value.isEmpty || controller.isGeneratingSummary.value) { if (controller.summaryResult.value.isEmpty || controller.isGeneratingSummary.value) {
return SizedBox.shrink(); return SizedBox.shrink();
} }
@ -502,8 +516,8 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
child: OutlinedButton( child: OutlinedButton(
onPressed: () => _extractSummary(combinedSourceText), onPressed: () => _extractSummary(combinedSourceText),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: Colors.blue, foregroundColor: isDarkMode ? AppColors.primary : Colors.blue,
side: BorderSide(color: Colors.blue), side: BorderSide(color: isDarkMode ? AppColors.primary : Colors.blue),
padding: EdgeInsets.symmetric(vertical: 12.h), padding: EdgeInsets.symmetric(vertical: 12.h),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.r), borderRadius: BorderRadius.circular(16.r),

111
lib/modules/translation/views/translation_history_view.dart

@ -5,47 +5,18 @@ import '../controllers/translation_controller.dart';
import '../controllers/translation_history_controller.dart'; import '../controllers/translation_history_controller.dart';
import '../bindings/translation_detail_binding.dart'; import '../bindings/translation_detail_binding.dart';
import 'translation_detail_view.dart'; import 'translation_detail_view.dart';
import '../../../core/theme/app_colors.dart';
class TranslationHistoryView extends GetView<TranslationHistoryController> { class TranslationHistoryView extends GetView<TranslationHistoryController> {
const TranslationHistoryView({Key? key}) : super(key: key); const TranslationHistoryView({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold( return Scaffold(
backgroundColor: Color(0xFFF5F5F8), backgroundColor: isDarkMode ? AppColors.darkBackground : Color(0xFFF5F5F8),
appBar: AppBar( appBar: _buildAppBar(isDarkMode),
backgroundColor: Color(0xFFF5F5F8),
elevation: 0,
leading: IconButton(
icon: Icon(
Icons.arrow_back_ios,
color: Colors.black,
size: 20.sp,
),
onPressed: () => Get.back(),
),
title: Text(
'History',
style: TextStyle(
color: Colors.black,
fontSize: 24.sp,
fontWeight: FontWeight.bold,
),
),
centerTitle: true,
actions: [
IconButton(
icon: Icon(
Icons.delete_outline,
color: Colors.black,
size: 24.sp,
),
onPressed: () {
_showClearHistoryConfirmDialog();
},
),
],
),
body: Obx(() { body: Obx(() {
if (!controller.hasHistory) { if (!controller.hasHistory) {
return Center( return Center(
@ -53,7 +24,7 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
'暂无历史记录', '暂无历史记录',
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
color: Colors.grey, color: isDarkMode ? Colors.grey[400] : Colors.grey,
), ),
), ),
); );
@ -61,7 +32,9 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
if (controller.isLoading.value) { if (controller.isLoading.value) {
return Center( return Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(
color: isDarkMode ? AppColors.primary : Colors.blue,
),
); );
} }
@ -82,12 +55,12 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Colors.grey[600], color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
letterSpacing: 1.0, letterSpacing: 1.0,
), ),
), ),
), ),
_buildDateGroupContainer(items), _buildDateGroupContainer(items, isDarkMode),
], ],
); );
}, },
@ -97,15 +70,15 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
} }
// 构建日期组容器 // 构建日期组容器
Widget _buildDateGroupContainer(List<TranslationItem> items) { Widget _buildDateGroupContainer(List<TranslationItem> items, bool isDarkMode) {
return Container( return Container(
margin: EdgeInsets.only(bottom: 16.h), margin: EdgeInsets.only(bottom: 16.h),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: isDarkMode ? AppColors.cardBackground : Colors.white,
borderRadius: BorderRadius.circular(16.r), borderRadius: BorderRadius.circular(16.r),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withAlpha(8), color: Colors.black.withAlpha(isDarkMode ? 30 : 8),
blurRadius: 8, blurRadius: 8,
offset: Offset(0, 2), offset: Offset(0, 2),
), ),
@ -119,19 +92,19 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
separatorBuilder: (context, index) => Divider( separatorBuilder: (context, index) => Divider(
height: 1, height: 1,
thickness: 0.5, thickness: 0.5,
color: Colors.grey[200], color: isDarkMode ? Colors.grey[800] : Colors.grey[200],
indent: 0, indent: 0,
endIndent: 0, endIndent: 0,
), ),
itemBuilder: (context, index) { itemBuilder: (context, index) {
return _buildHistoryItemInGroup(items[index]); return _buildHistoryItemInGroup(items[index], isDarkMode);
}, },
), ),
); );
} }
// 构建组内的历史记录项 // 构建组内的历史记录项
Widget _buildHistoryItemInGroup(TranslationItem item) { Widget _buildHistoryItemInGroup(TranslationItem item, bool isDarkMode) {
return InkWell( return InkWell(
onTap: () { onTap: () {
// 点击查看详情 // 点击查看详情
@ -155,7 +128,7 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
style: TextStyle( style: TextStyle(
fontSize: 15.sp, fontSize: 15.sp,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Colors.black, color: isDarkMode ? Colors.white : Colors.black,
), ),
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@ -166,7 +139,7 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
item.translatedText, item.translatedText,
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
color: Colors.grey[700], color: isDarkMode ? Colors.grey[400] : Colors.grey[700],
), ),
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@ -176,7 +149,7 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
), ),
Icon( Icon(
Icons.chevron_right, Icons.chevron_right,
color: Colors.grey[350], color: isDarkMode ? Colors.grey[600] : Colors.grey[350],
size: 20.sp, size: 20.sp,
), ),
], ],
@ -201,19 +174,24 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
} }
void _showClearHistoryConfirmDialog() { void _showClearHistoryConfirmDialog() {
final isDarkMode = Get.isDarkMode;
Get.dialog( Get.dialog(
AlertDialog( AlertDialog(
backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white,
title: Text( title: Text(
'清除历史记录', '清除历史记录',
style: TextStyle( style: TextStyle(
fontSize: 18.sp, fontSize: 18.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: isDarkMode ? Colors.white : Colors.black,
), ),
), ),
content: Text( content: Text(
'确定要清除所有翻译历史记录吗?此操作无法撤销。', '确定要清除所有翻译历史记录吗?此操作无法撤销。',
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
color: isDarkMode ? Colors.grey[300] : Colors.black87,
), ),
), ),
actions: [ actions: [
@ -223,7 +201,7 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
'取消', '取消',
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
color: Colors.grey[700], color: isDarkMode ? Colors.grey[400] : Colors.grey[700],
), ),
), ),
), ),
@ -249,4 +227,41 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
), ),
); );
} }
// 构建应用栏
PreferredSizeWidget _buildAppBar(bool isDarkMode) {
return AppBar(
backgroundColor: isDarkMode ? AppColors.cardBackground : Color(0xFFF5F5F8),
elevation: 0,
leading: IconButton(
icon: Icon(
Icons.arrow_back_ios,
color: isDarkMode ? Colors.white : Colors.black,
size: 20.sp,
),
onPressed: () => Get.back(),
),
title: Text(
'History',
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black,
fontSize: 24.sp,
fontWeight: FontWeight.bold,
),
),
centerTitle: true,
actions: [
IconButton(
icon: Icon(
Icons.delete_outline,
color: isDarkMode ? Colors.white : Colors.black,
size: 24.sp,
),
onPressed: () {
_showClearHistoryConfirmDialog();
},
),
],
);
}
} }

193
lib/modules/translation/views/translation_view.dart

@ -4,6 +4,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../controllers/translation_controller.dart'; import '../controllers/translation_controller.dart';
import '../views/translation_history_view.dart'; import '../views/translation_history_view.dart';
import '../bindings/translation_history_binding.dart'; import '../bindings/translation_history_binding.dart';
import '../../../core/theme/app_colors.dart';
// import 'package:intl/intl.dart'; // import 'package:intl/intl.dart';
class TranslationView extends GetView<TranslationController> { class TranslationView extends GetView<TranslationController> {
@ -11,20 +12,23 @@ class TranslationView extends GetView<TranslationController> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold( return Scaffold(
appBar: _buildAppBar(), backgroundColor: isDarkMode ? AppColors.darkBackground : Colors.grey[50],
body: _buildBody(), appBar: _buildAppBar(isDarkMode),
body: _buildBody(isDarkMode),
); );
} }
PreferredSizeWidget _buildAppBar() { PreferredSizeWidget _buildAppBar(bool isDarkMode) {
return AppBar( return AppBar(
backgroundColor: Colors.white, backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white,
elevation: 0, elevation: 0,
leading: IconButton( leading: IconButton(
icon: Icon( icon: Icon(
Icons.arrow_back_ios, Icons.arrow_back_ios,
color: Colors.black, color: isDarkMode ? Colors.white : Colors.black,
size: 20.sp, size: 20.sp,
), ),
onPressed: () { onPressed: () {
@ -38,11 +42,11 @@ class TranslationView extends GetView<TranslationController> {
children: [ children: [
// 源语言选择 // 源语言选择
GestureDetector( GestureDetector(
onTap: () => _showLanguageSelectionDialog(true), onTap: () => _showLanguageSelectionDialog(true, isDarkMode),
child: Container( child: Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey[100], color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
borderRadius: BorderRadius.circular(16.r), borderRadius: BorderRadius.circular(16.r),
), ),
child: Obx(() => Text( child: Obx(() => Text(
@ -50,7 +54,7 @@ class TranslationView extends GetView<TranslationController> {
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Colors.black, color: isDarkMode ? Colors.white : Colors.black,
), ),
)), )),
), ),
@ -63,12 +67,12 @@ class TranslationView extends GetView<TranslationController> {
margin: EdgeInsets.symmetric(horizontal: 8.w), margin: EdgeInsets.symmetric(horizontal: 8.w),
padding: EdgeInsets.all(4.r), padding: EdgeInsets.all(4.r),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.blue[50], color: isDarkMode ? AppColors.primary.withOpacity(0.2) : Colors.blue[50],
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: Icon( child: Icon(
Icons.swap_horiz, Icons.swap_horiz,
color: Colors.blue, color: isDarkMode ? AppColors.primary : Colors.blue,
size: 20.sp, size: 20.sp,
), ),
), ),
@ -76,11 +80,11 @@ class TranslationView extends GetView<TranslationController> {
// 目标语言选择 // 目标语言选择
GestureDetector( GestureDetector(
onTap: () => _showLanguageSelectionDialog(false), onTap: () => _showLanguageSelectionDialog(false, isDarkMode),
child: Container( child: Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey[100], color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
borderRadius: BorderRadius.circular(16.r), borderRadius: BorderRadius.circular(16.r),
), ),
child: Obx(() => Text( child: Obx(() => Text(
@ -88,7 +92,7 @@ class TranslationView extends GetView<TranslationController> {
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Colors.black, color: isDarkMode ? Colors.white : Colors.black,
), ),
)), )),
), ),
@ -101,7 +105,7 @@ class TranslationView extends GetView<TranslationController> {
Obx(() => IconButton( Obx(() => IconButton(
icon: Icon( icon: Icon(
controller.isTtsEnabled.value ? Icons.volume_up : Icons.volume_off, controller.isTtsEnabled.value ? Icons.volume_up : Icons.volume_off,
color: Colors.black, color: isDarkMode ? Colors.white : Colors.black,
size: 24.sp, size: 24.sp,
), ),
onPressed: controller.toggleTts, onPressed: controller.toggleTts,
@ -112,7 +116,7 @@ class TranslationView extends GetView<TranslationController> {
IconButton( IconButton(
icon: Icon( icon: Icon(
Icons.history, Icons.history,
color: Colors.black, color: isDarkMode ? Colors.white : Colors.black,
size: 24.sp, size: 24.sp,
), ),
onPressed: () async { onPressed: () async {
@ -132,18 +136,18 @@ class TranslationView extends GetView<TranslationController> {
); );
} }
Widget _buildBody() { Widget _buildBody(bool isDarkMode) {
return Column( return Column(
children: [ children: [
Expanded( Expanded(
child: _buildTranslationContent(), child: _buildTranslationContent(isDarkMode),
), ),
_buildControlBar(), _buildControlBar(isDarkMode),
], ],
); );
} }
Widget _buildTranslationContent() { Widget _buildTranslationContent(bool isDarkMode) {
return Obx(() { return Obx(() {
final itemCount = controller.translationHistory.length; final itemCount = controller.translationHistory.length;
@ -171,6 +175,7 @@ class TranslationView extends GetView<TranslationController> {
controller.currentItem.value!.translatedText, controller.currentItem.value!.translatedText,
true, true,
isIntermediate: controller.currentItem.value!.isIntermediate, isIntermediate: controller.currentItem.value!.isIntermediate,
isDarkMode: isDarkMode,
); );
} }
@ -182,6 +187,7 @@ class TranslationView extends GetView<TranslationController> {
false, false,
item: historyItem, item: historyItem,
isIntermediate: false, // 历史项总是显示为最终结果 isIntermediate: false, // 历史项总是显示为最终结果
isDarkMode: isDarkMode,
); );
}, },
); );
@ -195,6 +201,7 @@ class TranslationView extends GetView<TranslationController> {
{ {
TranslationItem? item, TranslationItem? item,
bool isIntermediate = false, bool isIntermediate = false,
required bool isDarkMode,
} }
) { ) {
// 使用AnimatedContainer实现平滑过渡,但恢复原来的视觉样式 // 使用AnimatedContainer实现平滑过渡,但恢复原来的视觉样式
@ -203,7 +210,7 @@ class TranslationView extends GetView<TranslationController> {
curve: Curves.easeInOut, curve: Curves.easeInOut,
margin: EdgeInsets.only(bottom: 24.h), margin: EdgeInsets.only(bottom: 24.h),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey[100], // 恢复原来的背景色 color: isDarkMode ? AppColors.cardBackground : Colors.grey[100],
borderRadius: BorderRadius.circular(12.r), borderRadius: BorderRadius.circular(12.r),
), ),
child: Column( child: Column(
@ -218,14 +225,16 @@ class TranslationView extends GetView<TranslationController> {
sourceText.isEmpty && isCurrent ? '等待语音输入...' : sourceText, sourceText.isEmpty && isCurrent ? '等待语音输入...' : sourceText,
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
color: sourceText.isEmpty && isCurrent ? Colors.grey : Colors.black, // 恢复原来的文字颜色 color: sourceText.isEmpty && isCurrent
fontWeight: FontWeight.normal, // 恢复原来的字重 ? (isDarkMode ? Colors.grey[400] : Colors.grey)
: (isDarkMode ? Colors.white : Colors.black),
fontWeight: FontWeight.normal,
), ),
), ),
), ),
// Divider // Divider
Divider(height: 1, thickness: 1, color: Colors.grey[200]), Divider(height: 1, thickness: 1, color: isDarkMode ? Colors.grey[800] : Colors.grey[200]),
// Translated text // Translated text
AnimatedContainer( AnimatedContainer(
@ -240,8 +249,10 @@ class TranslationView extends GetView<TranslationController> {
translatedText.isEmpty && isCurrent ? '翻译结果将显示在这里...' : translatedText, translatedText.isEmpty && isCurrent ? '翻译结果将显示在这里...' : translatedText,
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
color: translatedText.isEmpty && isCurrent ? Colors.grey : Colors.black, // 恢复原来的文字颜色 color: translatedText.isEmpty && isCurrent
fontStyle: FontStyle.normal, // 恢复原来的字体样式 ? (isDarkMode ? Colors.grey[400] : Colors.grey)
: (isDarkMode ? Colors.white : Colors.black),
fontStyle: FontStyle.normal,
), ),
), ),
), ),
@ -249,7 +260,7 @@ class TranslationView extends GetView<TranslationController> {
IconButton( IconButton(
icon: Icon( icon: Icon(
Icons.volume_up, Icons.volume_up,
color: Colors.blue, // 恢复原来的图标颜色 color: isDarkMode ? AppColors.primary : Colors.blue,
size: 20.sp, size: 20.sp,
), ),
onPressed: () { onPressed: () {
@ -268,7 +279,7 @@ class TranslationView extends GetView<TranslationController> {
); );
} }
Widget _buildControlBar() { Widget _buildControlBar(bool isDarkMode) {
// 使用GlobalKey来获取控制栏的高度 // 使用GlobalKey来获取控制栏的高度
final controlBarKey = GlobalKey(); final controlBarKey = GlobalKey();
@ -285,10 +296,10 @@ class TranslationView extends GetView<TranslationController> {
key: controlBarKey, key: controlBarKey,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: isDarkMode ? AppColors.cardBackground : Colors.white,
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withAlpha(13), // 0.05 * 255 ≈ 13 color: Colors.black.withAlpha(isDarkMode ? 30 : 13),
blurRadius: 4, blurRadius: 4,
offset: const Offset(0, -2), offset: const Offset(0, -2),
), ),
@ -314,7 +325,9 @@ class TranslationView extends GetView<TranslationController> {
width: 60.w, width: 60.w,
height: 60.w, height: 60.w,
decoration: BoxDecoration( decoration: BoxDecoration(
color: isRecognizing ? Colors.red : Colors.blue, color: isRecognizing
? (isDarkMode ? Colors.red[700] : Colors.red)
: (isDarkMode ? AppColors.primary : Colors.blue),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: Icon( child: Icon(
@ -331,14 +344,14 @@ class TranslationView extends GetView<TranslationController> {
} }
// 显示语言选择对话框 // 显示语言选择对话框
void _showLanguageSelectionDialog(bool isSource) { void _showLanguageSelectionDialog(bool isSource, bool isDarkMode) {
Get.dialog( Get.dialog(
Dialog( Dialog(
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24.r), borderRadius: BorderRadius.circular(24.r),
), ),
insetPadding: EdgeInsets.symmetric(horizontal: 20.w), insetPadding: EdgeInsets.symmetric(horizontal: 20.w),
backgroundColor: Colors.white, backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white,
child: Container( child: Container(
width: 0.9.sw, width: 0.9.sw,
padding: EdgeInsets.only(top: 32.h, bottom: 16.h), padding: EdgeInsets.only(top: 32.h, bottom: 16.h),
@ -349,31 +362,39 @@ class TranslationView extends GetView<TranslationController> {
Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: 24.w), padding: EdgeInsets.symmetric(horizontal: 24.w),
child: Text( child: Text(
'Select Language', isSource ? '选择源语言' : '选择目标语言',
style: TextStyle( style: TextStyle(
fontSize: 32.sp, fontSize: 20.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.black, color: isDarkMode ? Colors.white : Colors.black,
), ),
), ),
), ),
SizedBox(height: 24.h), SizedBox(height: 16.h),
Container( Container(
constraints: BoxConstraints( constraints: BoxConstraints(maxHeight: 0.5.sh),
maxHeight: 0.6.sh,
),
child: SingleChildScrollView( child: SingleChildScrollView(
physics: BouncingScrollPhysics(),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: controller.supportedLanguages.keys.map((language) { children: controller.supportedLanguages.keys.map((language) {
final isSelected = isSource return ListTile(
? controller.sourceLanguage.value == language title: Text(
: controller.targetLanguage.value == language; language,
style: TextStyle(
return _buildLanguageItem( color: isDarkMode ? Colors.white : Colors.black,
language: language.split(' ')[0], ),
isSelected: isSelected, ),
trailing: Obx(() {
final currentLanguage = isSource
? controller.sourceLanguage.value
: controller.targetLanguage.value;
return currentLanguage == language
? Icon(
Icons.check_circle,
color: isDarkMode ? AppColors.primary : Colors.blue,
)
: const SizedBox.shrink();
}),
onTap: () { onTap: () {
if (isSource) { if (isSource) {
controller.setSourceLanguage(language); controller.setSourceLanguage(language);
@ -387,24 +408,19 @@ class TranslationView extends GetView<TranslationController> {
), ),
), ),
), ),
SizedBox(height: 24.h), SizedBox(height: 16.h),
GestureDetector( Align(
onTap: () => Get.back(), alignment: Alignment.centerRight,
child: Container( child: Padding(
margin: EdgeInsets.symmetric(horizontal: 16.w), padding: EdgeInsets.only(right: 24.w),
width: double.infinity, child: TextButton(
padding: EdgeInsets.symmetric(vertical: 16.h), onPressed: () => Get.back(),
alignment: Alignment.center, child: Text(
decoration: BoxDecoration( '取消',
color: Colors.grey[200], style: TextStyle(
borderRadius: BorderRadius.circular(12.r), color: isDarkMode ? AppColors.primary : Colors.blue,
), fontSize: 16.sp,
child: Text( ),
'Cancel',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w500,
color: Colors.black87,
), ),
), ),
), ),
@ -413,53 +429,6 @@ class TranslationView extends GetView<TranslationController> {
), ),
), ),
), ),
barrierDismissible: true,
);
}
// 构建语言选择项
Widget _buildLanguageItem({
required String language,
required bool isSelected,
required VoidCallback onTap,
}) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 16.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
language,
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.normal,
color: Colors.black,
),
),
Container(
width: 28.w,
height: 28.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4.r),
border: Border.all(
color: isSelected ? Colors.transparent : Colors.grey[300]!,
width: 1.5,
),
color: isSelected ? Color(0xFF6C5CE7) : Colors.white,
),
child: isSelected
? Icon(
Icons.check,
color: Colors.white,
size: 20.sp,
)
: null,
),
],
),
),
); );
} }
} }
Loading…
Cancel
Save