wolfplus 2 years ago
parent
commit
1b1cd66e1a
  1. 6
      android/app/src/main/kotlin/com/example/deep_voice/AzureAsrHelper.kt
  2. 607
      lib/modules/chat/views/chat_view.dart
  3. 8
      lib/modules/settings/views/settings_view.dart
  4. 287
      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) {
val autoDetectSourceLanguageResult = AutoDetectSourceLanguageResult.fromResult(event.result)
val detectedLanguage = autoDetectSourceLanguageResult?.language ?: ""
if (isAutoDetectLanguage) {
Log.d(TAG, "正在识别: ${event.result.text}, 检测到的语言: $detectedLanguage")
}
// if (isAutoDetectLanguage) {
// Log.d(TAG, "正在识别: ${event.result.text}, 检测到的语言: $detectedLanguage")
// }
callback.onRecognizing(event.result.text, detectedLanguage)
}
}

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

@ -13,20 +13,26 @@ class ChatView extends GetView<ChatController> {
@override
Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
backgroundColor: Colors.grey[50], // 使用浅灰背景,类似HTML模板
appBar: _buildAppBar(),
body: _buildBody(),
backgroundColor: isDarkMode ? AppColors.darkBackground : Colors.grey[50],
appBar: _buildAppBar(isDarkMode),
body: _buildBody(isDarkMode),
);
}
// 构建AppBar
PreferredSizeWidget _buildAppBar() {
PreferredSizeWidget _buildAppBar(bool isDarkMode) {
return AppBar(
elevation: 1,
backgroundColor: Colors.white,
backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white,
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(),
),
titleSpacing: 0,
@ -80,7 +86,7 @@ class ChatView extends GetView<ChatController> {
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.bold,
color: Colors.black87,
color: isDarkMode ? Colors.white : Colors.black87,
),
),
Row(
@ -117,7 +123,7 @@ class ChatView extends GetView<ChatController> {
: Icons.volume_off,
color: controller.isTtsEnabled.value
? AppColors.primary
: Colors.grey,
: isDarkMode ? Colors.grey[400] : Colors.grey,
),
onPressed: controller.toggleTts,
)),
@ -127,21 +133,27 @@ class ChatView extends GetView<ChatController> {
height: 34.r,
margin: EdgeInsets.only(right: 8.w),
decoration: BoxDecoration(
color: Colors.grey[100],
color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 2,
spreadRadius: 0.5,
),
if (!isDarkMode)
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 2,
spreadRadius: 0.5,
),
],
),
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(
borderRadius: BorderRadius.circular(12.r),
),
color: isDarkMode ? AppColors.cardBackground : Colors.white,
itemBuilder: (context) => [
PopupMenuItem(
value: 'clear',
@ -149,16 +161,23 @@ class ChatView extends GetView<ChatController> {
children: [
Icon(Icons.delete_outline, color: Colors.red),
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(
children: [
// 消息列表
// 聊天内容区域
Expanded(
child: Container(
color: Colors.grey[50], // 使用浅灰背景
child: Obx(() => controller.messages.isEmpty
? _buildEmptyChat()
: _buildChatList()),
),
child: Obx(() {
if (controller.messages.isEmpty) {
return _buildEmptyState(isDarkMode);
} else {
return _buildChatList(isDarkMode);
}
}),
),
// 输入区域 - 根据当前模式显示不同的输入面板
Obx(() => controller.isVoiceRecognitionActive.value
? _buildVoiceInputPanel()
: _buildTextInputPanel()),
// 底部输入区域
_buildInputArea(isDarkMode),
],
);
}
// 构建空聊天界面
Widget _buildEmptyChat() {
// 构建空状态
Widget _buildEmptyState(bool isDarkMode) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 80.r,
height: 80.r,
width: 80.w,
height: 80.w,
decoration: BoxDecoration(
color: AppColors.primary.withOpacity(0.1),
color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
shape: BoxShape.circle,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(40.r),
child: controller.agent.avatarUrl.isNotEmpty
? Image.asset(
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,
),
child: Icon(
Icons.chat_bubble_outline,
size: 40.w,
color: isDarkMode ? Colors.white.withOpacity(0.5) : Colors.grey[400],
),
),
SizedBox(height: 24.h),
@ -227,7 +230,7 @@ class ChatView extends GetView<ChatController> {
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
color: Colors.black87,
color: isDarkMode ? Colors.white : Colors.black87,
),
),
SizedBox(height: 12.h),
@ -238,7 +241,7 @@ class ChatView extends GetView<ChatController> {
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14.sp,
color: Colors.grey[600],
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
height: 1.5,
),
),
@ -249,7 +252,7 @@ class ChatView extends GetView<ChatController> {
}
// 构建聊天列表
Widget _buildChatList() {
Widget _buildChatList(bool isDarkMode) {
return ListView.builder(
controller: controller.scrollController,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h),
@ -259,14 +262,14 @@ class ChatView extends GetView<ChatController> {
cacheExtent: 1000, // 增加缓存范围,减少重建
itemBuilder: (context, index) {
final message = controller.messages[index];
return _buildMessageItem(message);
return _buildMessageItem(message, isDarkMode);
},
);
}
// 构建消息项
Widget _buildMessageItem(Message message) {
final bool isUser = message.role == 'user';
Widget _buildMessageItem(Message message, bool isDarkMode) {
final isUser = message.role == 'user';
return Padding(
padding: EdgeInsets.only(bottom: 16.h),
@ -274,164 +277,102 @@ class ChatView extends GetView<ChatController> {
mainAxisAlignment: isUser ? MainAxisAlignment.end : MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// AI头像(非用户消息时显示)
if (!isUser)
Container(
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,
),
),
),
if (!isUser) _buildAvatar(false, isDarkMode),
SizedBox(width: 8.w),
// 消息气泡
Flexible(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
decoration: BoxDecoration(
color: isUser ? AppColors.primary : Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(isUser ? 16.r : 0),
topRight: Radius.circular(isUser ? 0 : 16.r),
bottomLeft: Radius.circular(16.r),
bottomRight: Radius.circular(16.r),
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 3,
spreadRadius: 0.5,
offset: Offset(0, 1),
child: Column(
crossAxisAlignment: isUser ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: [
// 消息气泡
Container(
padding: EdgeInsets.all(12.r),
decoration: BoxDecoration(
color: isUser
? AppColors.primary
: isDarkMode ? AppColors.cardBackground : Colors.grey[200],
borderRadius: BorderRadius.circular(16.r),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 消息内容
if (message.content.isNotEmpty)
SelectableText(
message.content,
style: TextStyle(
color: isUser ? Colors.white : Colors.black87,
fontSize: 15.sp,
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,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 消息内容
if (message.content.isNotEmpty)
SelectableText(
message.content,
style: TextStyle(
color: isUser
? Colors.white
: isDarkMode ? Colors.white : Colors.black,
fontSize: 16.sp,
height: 1.5,
),
SizedBox(width: 8.w),
Text(
'思考中...',
style: TextStyle(
color: isUser ? Colors.white : Colors.black54,
fontSize: 12.sp,
),
// 加载中动画
if (message.isLoading)
Padding(
padding: EdgeInsets.only(top: 8.h),
child: Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(
3,
(index) => _buildLoadingDot(index, isDarkMode),
),
),
],
),
),
],
),
),
),
),
],
),
),
// 用户头像(用户消息时显示)
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),
// 时间戳
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],
),
),
],
),
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);
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(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h),
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
decoration: BoxDecoration(
color: Colors.white,
color: isDarkMode ? AppColors.cardBackground : Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.06),
blurRadius: 10,
color: Colors.black.withOpacity(0.05),
blurRadius: 3,
offset: Offset(0, -1),
),
],
@ -439,25 +380,18 @@ class ChatView extends GetView<ChatController> {
child: SafeArea(
child: Row(
children: [
// 语音输入按钮
// 语音识别按钮
Container(
width: 40.r,
height: 40.r,
width: 40.w,
height: 40.w,
decoration: BoxDecoration(
color: Colors.grey[100],
color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 3,
spreadRadius: 0.5,
),
],
),
child: IconButton(
icon: Icon(
Icons.mic_none,
color: Colors.grey[600],
color: isDarkMode ? Colors.white : Colors.grey[700],
size: 20.r,
),
padding: EdgeInsets.zero,
@ -465,83 +399,75 @@ class ChatView extends GetView<ChatController> {
),
),
// 文本输入框
SizedBox(width: 8.w),
// 输入框
Expanded(
child: Container(
margin: EdgeInsets.symmetric(horizontal: 8.w),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(20.r),
),
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,
),
child: TextField(
controller: textController,
decoration: InputDecoration(
hintText: '输入消息...',
hintStyle: TextStyle(
color: isDarkMode ? Colors.grey[500] : Colors.grey[500]
),
maxLines: null,
textInputAction: TextInputAction.send,
style: TextStyle(
fontSize: 15.sp,
color: Colors.black87,
filled: true,
fillColor: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20.r),
borderSide: BorderSide.none,
),
onSubmitted: (value) {
if (value.trim().isNotEmpty) {
controller.sendMessage(value);
textController.clear();
}
},
contentPadding: EdgeInsets.symmetric(
horizontal: 16.w,
vertical: 10.h,
),
),
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(
width: 40.r,
height: 40.r,
decoration: BoxDecoration(
color: controller.inputText.value.trim().isEmpty
? Colors.grey[100]
: AppColors.primary,
shape: BoxShape.circle,
boxShadow: controller.inputText.value.trim().isEmpty
? [
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,
Obx(() {
final hasText = controller.inputText.value.isNotEmpty;
return Container(
width: 40.w,
height: 40.w,
decoration: BoxDecoration(
color: hasText
? AppColors.primary
: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[200],
shape: BoxShape.circle,
),
padding: EdgeInsets.zero,
onPressed: controller.inputText.value.trim().isEmpty
? null
: () {
controller.sendMessage(controller.inputText.value);
textController.clear();
},
),
)),
child: IconButton(
icon: Icon(
Icons.send,
color: hasText
? Colors.white
: 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,
),
);
}),
],
),
),
@ -549,31 +475,23 @@ class ChatView extends GetView<ChatController> {
}
// 构建语音输入面板
Widget _buildVoiceInputPanel() {
Widget _buildVoiceInputPanel(bool isDarkMode) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.white,
Color(0xFFE8D5F5),
],
stops: [0.3, 1.0],
),
// boxShadow: [
// BoxShadow(
// color: Colors.black.withOpacity(0.06),
// blurRadius: 10,
// offset: Offset(0, -1),
// ),
// ],
color: isDarkMode ? AppColors.cardBackground : Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 3,
offset: Offset(0, -1),
),
],
),
child: SafeArea(
child: SizedBox(
height: 80.h,
height: 120.h,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 30.w),
padding: EdgeInsets.symmetric(horizontal: 20.w),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
@ -583,7 +501,7 @@ class ChatView extends GetView<ChatController> {
width: 60.r,
height: 60.r,
decoration: BoxDecoration(
color: Colors.white,
color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
@ -639,7 +557,7 @@ class ChatView extends GetView<ChatController> {
decoration: BoxDecoration(
color: isSpeaking
? AppColors.primary
: Colors.grey.shade400,
: isDarkMode ? Colors.grey.shade600 : Colors.grey.shade400,
borderRadius: BorderRadius.circular(2.r),
),
),
@ -659,7 +577,7 @@ class ChatView extends GetView<ChatController> {
controller.isSpeechDetected.value ? '正在聆听...' : '你可以开始说话',
style: TextStyle(
fontSize: 14.sp,
color: Colors.grey.shade700,
color: isDarkMode ? Colors.grey[400] : Colors.grey[700],
fontWeight: FontWeight.w500,
),
)),
@ -672,7 +590,7 @@ class ChatView extends GetView<ChatController> {
width: 60.r,
height: 60.r,
decoration: BoxDecoration(
color: Colors.white,
color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
@ -686,7 +604,7 @@ class ChatView extends GetView<ChatController> {
child: IconButton(
icon: Icon(
Icons.close,
color: Colors.grey.shade600,
color: isDarkMode ? Colors.white : Colors.grey.shade600,
size: 28.r,
),
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,
leading: IconButton(
icon: Icon(
Icons.arrow_back_ios,
color: isDarkMode ? Colors.white70 : Colors.black54
),
onPressed: () => Get.back(),
),
),
body: ListView(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),

287
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/language_manager.dart';
import '../../../core/utils/logger.dart';
import '../../../data/services/voice_interaction_service.dart';
class TranslationController extends GetxController {
// 服务
final AzureAsrService _asrService = Get.find<AzureAsrService>();
@ -16,9 +16,12 @@ class TranslationController extends GetxController {
final AzureTtsService _ttsService = Get.find<AzureTtsService>();
final LanguageManager _languageManager = Get.find<LanguageManager>();
final GetStorage _storage = GetStorage();
final VoiceInteractionService _voiceInteractionService = Get.find<VoiceInteractionService>();
// 存储相关
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;
// 状态变量
@ -43,9 +46,8 @@ class TranslationController extends GetxController {
// 其他
StreamSubscription? _recognitionSubscription;
StreamSubscription? _voiceInteractionSubscription;
Timer? _translationDebounceTimer;
Timer? _scrollDebounceTimer; // 滚动防抖定时器
bool _isNearBottom = true; // 标记是否在底部附近
// 获取支持的语言列表
Map<String, String> get supportedLanguages => _languageManager.getChineseNameToAsrCodeMap();
@ -53,75 +55,73 @@ class TranslationController extends GetxController {
@override
void onInit() {
super.onInit();
_initController();
}
// 初始化控制器
void _initController() {
_initLanguageCodes();
loadTranslationHistory();
_initServices();
_setupListeners();
// 加载上次保存的语言设置
_loadSavedLanguages();
// 初始化完成后滚动到底部
WidgetsBinding.instance.addPostFrameCallback((_) {
// 延迟 300ms 再次尝试滚动,确保列表已完全渲染
Future.delayed(const Duration(milliseconds: 300), () {
scrollToBottom(animate: false);
});
// 初始化语言代码
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();
// 加载历史记录
loadTranslationHistory();
// 重新初始化控制器(公共方法)
void reinitialize() {
_setupListeners();
// 初始化服务
_initServices();
// 初始化完成后滚动到底部
// 使用微任务在布局完成后自动滚动到底部
WidgetsBinding.instance.addPostFrameCallback((_) {
scrollToBottom(animate: false);
_scrollToBottom();
});
}
// 仅设置滚动监听器(不启动翻译)
void setupScrollListener() {
// 移除之前的监听器,避免重复添加
scrollController.removeListener(_onScroll);
// 重新添加滚动监听器
scrollController.addListener(_onScroll);
/// 订阅VoiceInteractionService事件
void _subscribeToVoiceInteractionEvents() {
if (_voiceInteractionService.eventStream != null) {
_voiceInteractionSubscription = _voiceInteractionService.eventStream!.listen((event) {
if (event is RecognitionStartedEvent) {
// 收到recognitionStarted事件时,停止当前的AI交互、录音和TTS
stopAll();
}
});
}
}
// 初始化服务
Future<void> _initServices() async {
await _initTranslationService();
await _initAzureAsrService();
}
// 加载保存的语言设置
void _loadSavedLanguages() {
final savedSourceLanguage = _storage.read(_sourceLanguageKey);
final savedTargetLanguage = _storage.read(_targetLanguageKey);
// 初始化语言代码
void _initLanguageCodes() {
final sourceAsrCode = _languageManager.getAsrCodeByChineseName(sourceLanguage.value);
if (sourceAsrCode != null) {
sourceLanguageCode.value = sourceAsrCode;
if (savedSourceLanguage != null && supportedLanguages.containsKey(savedSourceLanguage)) {
sourceLanguage.value = savedSourceLanguage;
}
final targetAsrCode = _languageManager.getAsrCodeByChineseName(targetLanguage.value);
if (targetAsrCode != null) {
targetLanguageCode.value = targetAsrCode;
if (savedTargetLanguage != null && supportedLanguages.containsKey(savedTargetLanguage)) {
targetLanguage.value = savedTargetLanguage;
}
}
// 初始化翻译服务
Future<void> _initTranslationService() async {
try {
await _translationService.initialize();
} catch (e) {
Logger.error('初始化翻译服务失败: $e');
}
// 保存当前语言设置
void _saveLanguageSettings() {
_storage.write(_sourceLanguageKey, sourceLanguage.value);
_storage.write(_targetLanguageKey, targetLanguage.value);
}
// 初始化语音识别服务
Future<void> _initAzureAsrService() async {
// 初始化服务
Future<void> _initServices() async {
try {
await _translationService.initialize();
final List<String> asrSupportedLanguages = [sourceLanguageCode.value, targetLanguageCode.value];
await _ttsService.initialize();
await _asrService.initialize(
@ -129,61 +129,19 @@ class TranslationController extends GetxController {
supportedLanguages: asrSupportedLanguages,
);
} 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
void onClose() {
stopRecognition();
_recognitionSubscription?.cancel();
_translationDebounceTimer?.cancel();
_scrollDebounceTimer?.cancel();
scrollController.removeListener(_onScroll);
scrollController.dispose();
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功能
void toggleTts() => isTtsEnabled.toggle();
@ -191,8 +149,13 @@ class TranslationController extends GetxController {
Future<void> startRecognition() async {
if (isRecognizing.value) return;
_voiceInteractionService.pauseVoiceInteraction();
try {
await _updateAsrSupportedLanguages();
// 更新ASR支持的语言
final List<String> asrSupportedLanguages = [sourceLanguageCode.value, targetLanguageCode.value];
await _asrService.setSupportedLanguages(asrSupportedLanguages);
currentSessionId = DateTime.now().millisecondsSinceEpoch.toString();
// 初始化新的翻译项
@ -283,48 +246,48 @@ class TranslationController extends GetxController {
timestamp: DateTime.now(),
sessionId: currentSessionId!,
isFirstInSession: translationHistory.isEmpty || translationHistory.last.sessionId != currentSessionId,
isIntermediate: true, // 保留标记,用于内部逻辑
isIntermediate: true,
);
// 添加新项后滚动到底部
_scrollToBottom();
} else {
// 更新当前项
currentItem.value!.sourceText = text;
currentItem.value!.isIntermediate = true; // 保留标记,用于内部逻辑
currentItem.value!.isIntermediate = true;
currentItem.refresh();
// 更新后滚动到底部
_scrollToBottom();
}
// 防抖处理翻译请求
_translationDebounceTimer?.cancel();
_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 {
if (text.isEmpty || currentItem.value == null) return;
// 更新当前项,但不立即添加到历史记录
currentItem.value!.sourceText = text;
currentItem.value!.isIntermediate = false; // 标记为最终结果
currentItem.value!.isIntermediate = false;
currentItem.refresh();
// 更新后滚动到底部
_scrollToBottom();
// 先翻译,保持UI稳定
final translationResult = await translateText(text);
// 给UI一点时间更新
await Future.delayed(const Duration(milliseconds: 100));
// 翻译完成后再添加到历史记录
if (currentItem.value != null) {
// 确保添加到历史记录的是最终结果
translationHistory.add(currentItem.value!);
saveTranslationHistory();
// 添加到历史记录后滚动到底部
_scrollToBottom();
// 播放TTS
if (translationResult != null && isTtsEnabled.value) {
@ -357,7 +320,28 @@ class TranslationController extends GetxController {
);
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) {
Logger.error('翻译失败: $e');
@ -368,27 +352,6 @@ class TranslationController extends GetxController {
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 {
if (text.isEmpty) return;
@ -412,10 +375,7 @@ class TranslationController extends GetxController {
await stopRecognition();
}
// 取消所有定时器
_translationDebounceTimer?.cancel();
_scrollDebounceTimer?.cancel();
await _ttsService.stop();
} catch (e) {
Logger.error('停止操作失败: $e');
@ -451,28 +411,24 @@ class TranslationController extends GetxController {
}
}
// 简洁高效的滚动到底部方法
void scrollToBottom({bool animate = true}) {
if (!scrollController.hasClients) return;
// 取消之前的定时器
_scrollDebounceTimer?.cancel();
// 创建新的定时器,防抖处理
_scrollDebounceTimer = Timer(const Duration(milliseconds: 100), () {
if (!scrollController.hasClients) return;
if (animate) {
// 滚动到底部
void _scrollToBottom() {
if (scrollController.hasClients && (translationHistory.isNotEmpty || currentItem.value != null)) {
try {
scrollController.animateTo(
scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 200),
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
} else {
scrollController.jumpTo(scrollController.position.maxScrollExtent);
} catch (e) {
Logger.error('滚动到底部失败: $e');
}
});
}
}
// 设置滚动监听器(简化版,不再需要实际监听)
void setupScrollListener() {
// 简化后的空方法,保留接口兼容性
}
// 底部控制栏高度
@ -488,7 +444,6 @@ class TranslationController extends GetxController {
// 切换源语言和目标语言
void swapLanguages() {
// 先停止当前的翻译操作
stopAll();
final tempName = sourceLanguage.value;
@ -498,12 +453,14 @@ class TranslationController extends GetxController {
final tempCode = sourceLanguageCode.value;
sourceLanguageCode.value = targetLanguageCode.value;
targetLanguageCode.value = tempCode;
// 保存语言设置
_saveLanguageSettings();
}
// 设置源语言
void setSourceLanguage(String language) {
if (sourceLanguage.value != language) {
// 先停止当前的翻译操作
stopAll();
sourceLanguage.value = language;
@ -513,19 +470,14 @@ class TranslationController extends GetxController {
sourceLanguageCode.value = asrCode;
}
_updateAsrSupportedLanguages();
if (isRecognizing.value) {
stopRecognition();
startRecognition();
}
// 保存语言设置
_saveLanguageSettings();
}
}
// 设置目标语言
void setTargetLanguage(String language) {
if (targetLanguage.value != language) {
// 先停止当前的翻译操作
stopAll();
targetLanguage.value = language;
@ -535,7 +487,8 @@ class TranslationController extends GetxController {
targetLanguageCode.value = asrCode;
}
_updateAsrSupportedLanguages();
// 保存语言设置
_saveLanguageSettings();
}
}
@ -546,6 +499,11 @@ class TranslationController extends GetxController {
try {
final List<dynamic> historyList = jsonDecode(historyJson);
translationHistory.value = historyList.map((item) => TranslationItem.fromJson(item)).toList();
// 使用微任务确保在视图完全渲染后滚动到底部
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToBottom();
});
} catch (e) {
_storage.remove(_historyKey);
Logger.error('加载翻译历史失败: $e');
@ -572,6 +530,11 @@ class TranslationController extends GetxController {
translationHistory.clear();
_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:flutter/services.dart';
import 'package:share_plus/share_plus.dart';
import '../../../core/theme/app_colors.dart';
class TranslationDetailView extends GetView<TranslationDetailController> {
TranslationDetailView({Key? key}) : super(key: key);
@ -33,19 +34,25 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
@override
Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
backgroundColor: Color(0xFFF5F5F8),
appBar: _buildAppBar(),
backgroundColor: isDarkMode ? AppColors.darkBackground : Color(0xFFF5F5F8),
appBar: _buildAppBar(isDarkMode),
body: Obx(() {
// 显示加载状态
if (controller.isLoading.value) {
return const Center(child: CircularProgressIndicator());
return Center(
child: CircularProgressIndicator(
color: isDarkMode ? AppColors.primary : Colors.blue,
),
);
}
return Column(
children: [
_buildTabButtons(),
_buildContentArea(),
_buildTabButtons(isDarkMode),
_buildContentArea(isDarkMode),
],
);
}),
@ -53,14 +60,14 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
}
// 构建应用栏
PreferredSizeWidget _buildAppBar() {
PreferredSizeWidget _buildAppBar(bool isDarkMode) {
return AppBar(
backgroundColor: Color(0xFFF5F5F8),
backgroundColor: isDarkMode ? AppColors.cardBackground : Color(0xFFF5F5F8),
elevation: 0,
leading: IconButton(
icon: Icon(
Icons.arrow_back_ios,
color: Colors.black,
color: isDarkMode ? Colors.white : Colors.black,
size: 20.sp,
),
onPressed: () => Get.back(),
@ -68,7 +75,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
title: Text(
'Detail',
style: TextStyle(
color: Colors.black,
color: isDarkMode ? Colors.white : Colors.black,
fontSize: 24.sp,
fontWeight: FontWeight.bold,
),
@ -78,7 +85,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
IconButton(
icon: Icon(
Icons.share,
color: Colors.black,
color: isDarkMode ? Colors.white : Colors.black,
size: 22.sp,
),
onPressed: _shareContent,
@ -88,7 +95,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
}
// 构建选项卡按钮
Widget _buildTabButtons() {
Widget _buildTabButtons(bool isDarkMode) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
child: Row(
@ -98,6 +105,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
title: '原始记录',
isSelected: controller.selectedTabIndex.value == 0,
onTap: () => controller.switchTab(0),
isDarkMode: isDarkMode,
)),
),
SizedBox(width: 12.w),
@ -106,6 +114,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
title: '记录摘要',
isSelected: controller.selectedTabIndex.value == 1,
onTap: () => controller.switchTab(1),
isDarkMode: isDarkMode,
)),
),
],
@ -114,7 +123,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
}
// 构建内容区域
Widget _buildContentArea() {
Widget _buildContentArea(bool isDarkMode) {
return Expanded(
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 16.w),
@ -122,14 +131,14 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildTimestamp(),
_buildTimestamp(isDarkMode),
SizedBox(height: 8.h),
// 根据选中的选项卡显示不同内容
if (controller.selectedTabIndex.value == 0)
..._buildOriginalRecords()
..._buildOriginalRecords(isDarkMode)
else
_buildSummaryWidget(),
_buildSummaryWidget(isDarkMode),
SizedBox(height: 24.h),
],
@ -140,7 +149,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
}
// 构建时间戳
Widget _buildTimestamp() {
Widget _buildTimestamp(bool isDarkMode) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 8.h),
child: Text(
@ -148,7 +157,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: Colors.grey[600],
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
letterSpacing: 1.0,
),
),
@ -156,8 +165,8 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
}
// 构建原始记录列表
List<Widget> _buildOriginalRecords() {
return controller.sessionItems.map((sessionItem) => _buildSessionItemWidget(sessionItem)).toList();
List<Widget> _buildOriginalRecords(bool isDarkMode) {
return controller.sessionItems.map((sessionItem) => _buildSessionItemWidget(sessionItem, isDarkMode)).toList();
}
// 构建选项卡按钮
@ -165,17 +174,20 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
required String title,
required bool isSelected,
required VoidCallback onTap,
required bool isDarkMode,
}) {
return GestureDetector(
onTap: onTap,
child: Container(
height: 40.h,
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),
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(8),
color: Colors.black.withAlpha(isDarkMode ? 30 : 8),
blurRadius: 4,
offset: Offset(0, 1),
),
@ -187,7 +199,9 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
style: TextStyle(
fontSize: 15.sp,
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(
width: double.infinity,
margin: EdgeInsets.only(bottom: 16.h),
decoration: BoxDecoration(
color: Colors.white,
color: isDarkMode ? AppColors.cardBackground : Colors.white,
borderRadius: BorderRadius.circular(16.r),
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(8),
color: Colors.black.withAlpha(isDarkMode ? 30 : 8),
blurRadius: 8,
offset: Offset(0, 2),
),
@ -220,7 +234,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
item.sourceText,
style: TextStyle(
fontSize: 15.sp,
color: Colors.black,
color: isDarkMode ? Colors.white : Colors.black,
fontWeight: FontWeight.w500,
),
),
@ -236,13 +250,13 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
item.translatedText,
style: TextStyle(
fontSize: 14.sp,
color: Colors.grey[700],
color: isDarkMode ? Colors.grey[400] : Colors.grey[700],
fontWeight: FontWeight.normal,
),
),
),
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(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
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(
width: 28.w,
height: 28.h,
decoration: BoxDecoration(
color: Colors.blue.withAlpha(10),
color: isDarkMode ? AppColors.primary.withAlpha(10) : Colors.blue.withAlpha(10),
shape: BoxShape.circle,
),
child: Icon(
Icons.volume_up,
color: Colors.blue,
color: Colors.white,
size: 16.sp,
),
),
@ -277,34 +291,34 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
}
// 构建总结视图
Widget _buildSummaryWidget() {
Widget _buildSummaryWidget(bool isDarkMode) {
final String combinedSourceText = _getCombinedSourceText();
return Column(
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(
width: double.infinity,
margin: EdgeInsets.only(bottom: 16.h),
decoration: BoxDecoration(
color: Colors.white,
color: isDarkMode ? AppColors.cardBackground : Colors.white,
borderRadius: BorderRadius.circular(16.r),
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(8),
color: Colors.black.withAlpha(isDarkMode ? 30 : 8),
blurRadius: 8,
offset: Offset(0, 2),
),
@ -312,16 +326,16 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
),
child: Obx(() {
if (controller.summaryResult.value.isEmpty) {
return _buildEmptySummaryPrompt();
return _buildEmptySummaryPrompt(isDarkMode);
} else {
return _buildSummaryContent();
return _buildSummaryContent(isDarkMode);
}
}),
);
}
// 构建空摘要提示
Widget _buildEmptySummaryPrompt() {
Widget _buildEmptySummaryPrompt(bool isDarkMode) {
return Padding(
padding: EdgeInsets.all(24.r),
child: Center(
@ -329,7 +343,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
'点击下方按钮生成会话摘要',
style: TextStyle(
fontSize: 15.sp,
color: Colors.grey[600],
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
fontStyle: FontStyle.italic,
),
textAlign: TextAlign.center,
@ -339,7 +353,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
}
// 构建摘要内容
Widget _buildSummaryContent() {
Widget _buildSummaryContent(bool isDarkMode) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -353,7 +367,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.bold,
color: Colors.black,
color: isDarkMode ? Colors.white : Colors.black,
),
),
SizedBox(height: 12.h),
@ -361,7 +375,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
controller.summaryResult.value,
style: TextStyle(
fontSize: 15.sp,
color: Colors.black87,
color: isDarkMode ? Colors.grey[400] : Colors.grey[800],
height: 1.5,
),
),
@ -371,7 +385,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
Divider(
height: 1,
thickness: 0.5,
color: Colors.grey[200],
color: isDarkMode ? Colors.grey[700] : Colors.grey[200],
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 12.r, vertical: 8.r),
@ -384,12 +398,12 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
child: InkWell(
onTap: () => controller.playText(controller.summaryResult.value),
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(
padding: EdgeInsets.all(8.r),
child: Icon(
Icons.volume_up,
color: Colors.blue,
color: isDarkMode ? Colors.white : Colors.blue,
size: 18.sp,
),
),
@ -402,12 +416,12 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
child: InkWell(
onTap: _copySummaryToClipboard,
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(
padding: EdgeInsets.all(8.r),
child: Icon(
Icons.copy,
color: Colors.blue,
color: isDarkMode ? Colors.white : Colors.blue,
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;
return Container(
@ -441,7 +455,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
child: ElevatedButton(
onPressed: isDisabled ? null : () => _extractSummary(combinedSourceText),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
backgroundColor: isDarkMode ? AppColors.primary : Colors.blue,
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 12.h),
shape: RoundedRectangleBorder(
@ -449,11 +463,11 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
),
elevation: 0,
disabledBackgroundColor: controller.isGeneratingSummary.value
? Colors.blue.withAlpha(128)
? isDarkMode ? AppColors.primary.withAlpha(128) : Colors.blue.withAlpha(128)
: Colors.grey.withAlpha(128),
),
child: controller.isGeneratingSummary.value
? _buildGeneratingIndicator()
? _buildGeneratingIndicator(isDarkMode)
: Text(
controller.summaryResult.value.isEmpty ? '提取摘要' : '已生成摘要',
style: TextStyle(
@ -466,7 +480,7 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
}
// 构建生成中指示器
Widget _buildGeneratingIndicator() {
Widget _buildGeneratingIndicator(bool isDarkMode) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
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) {
return SizedBox.shrink();
}
@ -502,8 +516,8 @@ class TranslationDetailView extends GetView<TranslationDetailController> {
child: OutlinedButton(
onPressed: () => _extractSummary(combinedSourceText),
style: OutlinedButton.styleFrom(
foregroundColor: Colors.blue,
side: BorderSide(color: Colors.blue),
foregroundColor: isDarkMode ? AppColors.primary : Colors.blue,
side: BorderSide(color: isDarkMode ? AppColors.primary : Colors.blue),
padding: EdgeInsets.symmetric(vertical: 12.h),
shape: RoundedRectangleBorder(
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 '../bindings/translation_detail_binding.dart';
import 'translation_detail_view.dart';
import '../../../core/theme/app_colors.dart';
class TranslationHistoryView extends GetView<TranslationHistoryController> {
const TranslationHistoryView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
backgroundColor: Color(0xFFF5F5F8),
appBar: AppBar(
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();
},
),
],
),
backgroundColor: isDarkMode ? AppColors.darkBackground : Color(0xFFF5F5F8),
appBar: _buildAppBar(isDarkMode),
body: Obx(() {
if (!controller.hasHistory) {
return Center(
@ -53,7 +24,7 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
'暂无历史记录',
style: TextStyle(
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) {
return Center(
child: CircularProgressIndicator(),
child: CircularProgressIndicator(
color: isDarkMode ? AppColors.primary : Colors.blue,
),
);
}
@ -82,12 +55,12 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: Colors.grey[600],
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
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(
margin: EdgeInsets.only(bottom: 16.h),
decoration: BoxDecoration(
color: Colors.white,
color: isDarkMode ? AppColors.cardBackground : Colors.white,
borderRadius: BorderRadius.circular(16.r),
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(8),
color: Colors.black.withAlpha(isDarkMode ? 30 : 8),
blurRadius: 8,
offset: Offset(0, 2),
),
@ -119,19 +92,19 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
separatorBuilder: (context, index) => Divider(
height: 1,
thickness: 0.5,
color: Colors.grey[200],
color: isDarkMode ? Colors.grey[800] : Colors.grey[200],
indent: 0,
endIndent: 0,
),
itemBuilder: (context, index) {
return _buildHistoryItemInGroup(items[index]);
return _buildHistoryItemInGroup(items[index], isDarkMode);
},
),
);
}
// 构建组内的历史记录项
Widget _buildHistoryItemInGroup(TranslationItem item) {
Widget _buildHistoryItemInGroup(TranslationItem item, bool isDarkMode) {
return InkWell(
onTap: () {
// 点击查看详情
@ -155,7 +128,7 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
style: TextStyle(
fontSize: 15.sp,
fontWeight: FontWeight.w500,
color: Colors.black,
color: isDarkMode ? Colors.white : Colors.black,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
@ -166,7 +139,7 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
item.translatedText,
style: TextStyle(
fontSize: 14.sp,
color: Colors.grey[700],
color: isDarkMode ? Colors.grey[400] : Colors.grey[700],
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
@ -176,7 +149,7 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
),
Icon(
Icons.chevron_right,
color: Colors.grey[350],
color: isDarkMode ? Colors.grey[600] : Colors.grey[350],
size: 20.sp,
),
],
@ -201,19 +174,24 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
}
void _showClearHistoryConfirmDialog() {
final isDarkMode = Get.isDarkMode;
Get.dialog(
AlertDialog(
backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white,
title: Text(
'清除历史记录',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
color: isDarkMode ? Colors.white : Colors.black,
),
),
content: Text(
'确定要清除所有翻译历史记录吗?此操作无法撤销。',
style: TextStyle(
fontSize: 16.sp,
color: isDarkMode ? Colors.grey[300] : Colors.black87,
),
),
actions: [
@ -223,7 +201,7 @@ class TranslationHistoryView extends GetView<TranslationHistoryController> {
'取消',
style: TextStyle(
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 '../views/translation_history_view.dart';
import '../bindings/translation_history_binding.dart';
import '../../../core/theme/app_colors.dart';
// import 'package:intl/intl.dart';
class TranslationView extends GetView<TranslationController> {
@ -11,20 +12,23 @@ class TranslationView extends GetView<TranslationController> {
@override
Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
appBar: _buildAppBar(),
body: _buildBody(),
backgroundColor: isDarkMode ? AppColors.darkBackground : Colors.grey[50],
appBar: _buildAppBar(isDarkMode),
body: _buildBody(isDarkMode),
);
}
PreferredSizeWidget _buildAppBar() {
PreferredSizeWidget _buildAppBar(bool isDarkMode) {
return AppBar(
backgroundColor: Colors.white,
backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white,
elevation: 0,
leading: IconButton(
icon: Icon(
Icons.arrow_back_ios,
color: Colors.black,
color: isDarkMode ? Colors.white : Colors.black,
size: 20.sp,
),
onPressed: () {
@ -38,11 +42,11 @@ class TranslationView extends GetView<TranslationController> {
children: [
// 源语言选择
GestureDetector(
onTap: () => _showLanguageSelectionDialog(true),
onTap: () => _showLanguageSelectionDialog(true, isDarkMode),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: Colors.grey[100],
color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
borderRadius: BorderRadius.circular(16.r),
),
child: Obx(() => Text(
@ -50,7 +54,7 @@ class TranslationView extends GetView<TranslationController> {
style: TextStyle(
fontSize: 16.sp,
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),
padding: EdgeInsets.all(4.r),
decoration: BoxDecoration(
color: Colors.blue[50],
color: isDarkMode ? AppColors.primary.withOpacity(0.2) : Colors.blue[50],
shape: BoxShape.circle,
),
child: Icon(
Icons.swap_horiz,
color: Colors.blue,
color: isDarkMode ? AppColors.primary : Colors.blue,
size: 20.sp,
),
),
@ -76,11 +80,11 @@ class TranslationView extends GetView<TranslationController> {
// 目标语言选择
GestureDetector(
onTap: () => _showLanguageSelectionDialog(false),
onTap: () => _showLanguageSelectionDialog(false, isDarkMode),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: Colors.grey[100],
color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
borderRadius: BorderRadius.circular(16.r),
),
child: Obx(() => Text(
@ -88,7 +92,7 @@ class TranslationView extends GetView<TranslationController> {
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: Colors.black,
color: isDarkMode ? Colors.white : Colors.black,
),
)),
),
@ -101,7 +105,7 @@ class TranslationView extends GetView<TranslationController> {
Obx(() => IconButton(
icon: Icon(
controller.isTtsEnabled.value ? Icons.volume_up : Icons.volume_off,
color: Colors.black,
color: isDarkMode ? Colors.white : Colors.black,
size: 24.sp,
),
onPressed: controller.toggleTts,
@ -112,7 +116,7 @@ class TranslationView extends GetView<TranslationController> {
IconButton(
icon: Icon(
Icons.history,
color: Colors.black,
color: isDarkMode ? Colors.white : Colors.black,
size: 24.sp,
),
onPressed: () async {
@ -132,18 +136,18 @@ class TranslationView extends GetView<TranslationController> {
);
}
Widget _buildBody() {
Widget _buildBody(bool isDarkMode) {
return Column(
children: [
Expanded(
child: _buildTranslationContent(),
child: _buildTranslationContent(isDarkMode),
),
_buildControlBar(),
_buildControlBar(isDarkMode),
],
);
}
Widget _buildTranslationContent() {
Widget _buildTranslationContent(bool isDarkMode) {
return Obx(() {
final itemCount = controller.translationHistory.length;
@ -171,6 +175,7 @@ class TranslationView extends GetView<TranslationController> {
controller.currentItem.value!.translatedText,
true,
isIntermediate: controller.currentItem.value!.isIntermediate,
isDarkMode: isDarkMode,
);
}
@ -182,6 +187,7 @@ class TranslationView extends GetView<TranslationController> {
false,
item: historyItem,
isIntermediate: false, // 历史项总是显示为最终结果
isDarkMode: isDarkMode,
);
},
);
@ -195,6 +201,7 @@ class TranslationView extends GetView<TranslationController> {
{
TranslationItem? item,
bool isIntermediate = false,
required bool isDarkMode,
}
) {
// 使用AnimatedContainer实现平滑过渡,但恢复原来的视觉样式
@ -203,7 +210,7 @@ class TranslationView extends GetView<TranslationController> {
curve: Curves.easeInOut,
margin: EdgeInsets.only(bottom: 24.h),
decoration: BoxDecoration(
color: Colors.grey[100], // 恢复原来的背景色
color: isDarkMode ? AppColors.cardBackground : Colors.grey[100],
borderRadius: BorderRadius.circular(12.r),
),
child: Column(
@ -218,14 +225,16 @@ class TranslationView extends GetView<TranslationController> {
sourceText.isEmpty && isCurrent ? '等待语音输入...' : sourceText,
style: TextStyle(
fontSize: 16.sp,
color: sourceText.isEmpty && isCurrent ? Colors.grey : Colors.black, // 恢复原来的文字颜色
fontWeight: FontWeight.normal, // 恢复原来的字重
color: sourceText.isEmpty && isCurrent
? (isDarkMode ? Colors.grey[400] : Colors.grey)
: (isDarkMode ? Colors.white : Colors.black),
fontWeight: FontWeight.normal,
),
),
),
// 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
AnimatedContainer(
@ -240,8 +249,10 @@ class TranslationView extends GetView<TranslationController> {
translatedText.isEmpty && isCurrent ? '翻译结果将显示在这里...' : translatedText,
style: TextStyle(
fontSize: 16.sp,
color: translatedText.isEmpty && isCurrent ? Colors.grey : Colors.black, // 恢复原来的文字颜色
fontStyle: FontStyle.normal, // 恢复原来的字体样式
color: translatedText.isEmpty && isCurrent
? (isDarkMode ? Colors.grey[400] : Colors.grey)
: (isDarkMode ? Colors.white : Colors.black),
fontStyle: FontStyle.normal,
),
),
),
@ -249,7 +260,7 @@ class TranslationView extends GetView<TranslationController> {
IconButton(
icon: Icon(
Icons.volume_up,
color: Colors.blue, // 恢复原来的图标颜色
color: isDarkMode ? AppColors.primary : Colors.blue,
size: 20.sp,
),
onPressed: () {
@ -268,7 +279,7 @@ class TranslationView extends GetView<TranslationController> {
);
}
Widget _buildControlBar() {
Widget _buildControlBar(bool isDarkMode) {
// 使用GlobalKey来获取控制栏的高度
final controlBarKey = GlobalKey();
@ -285,10 +296,10 @@ class TranslationView extends GetView<TranslationController> {
key: controlBarKey,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),
decoration: BoxDecoration(
color: Colors.white,
color: isDarkMode ? AppColors.cardBackground : Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withAlpha(13), // 0.05 * 255 ≈ 13
color: Colors.black.withAlpha(isDarkMode ? 30 : 13),
blurRadius: 4,
offset: const Offset(0, -2),
),
@ -314,7 +325,9 @@ class TranslationView extends GetView<TranslationController> {
width: 60.w,
height: 60.w,
decoration: BoxDecoration(
color: isRecognizing ? Colors.red : Colors.blue,
color: isRecognizing
? (isDarkMode ? Colors.red[700] : Colors.red)
: (isDarkMode ? AppColors.primary : Colors.blue),
shape: BoxShape.circle,
),
child: Icon(
@ -331,14 +344,14 @@ class TranslationView extends GetView<TranslationController> {
}
// 显示语言选择对话框
void _showLanguageSelectionDialog(bool isSource) {
void _showLanguageSelectionDialog(bool isSource, bool isDarkMode) {
Get.dialog(
Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24.r),
),
insetPadding: EdgeInsets.symmetric(horizontal: 20.w),
backgroundColor: Colors.white,
backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white,
child: Container(
width: 0.9.sw,
padding: EdgeInsets.only(top: 32.h, bottom: 16.h),
@ -349,31 +362,39 @@ class TranslationView extends GetView<TranslationController> {
Padding(
padding: EdgeInsets.symmetric(horizontal: 24.w),
child: Text(
'Select Language',
isSource ? '选择源语言' : '选择目标语言',
style: TextStyle(
fontSize: 32.sp,
fontSize: 20.sp,
fontWeight: FontWeight.bold,
color: Colors.black,
color: isDarkMode ? Colors.white : Colors.black,
),
),
),
SizedBox(height: 24.h),
SizedBox(height: 16.h),
Container(
constraints: BoxConstraints(
maxHeight: 0.6.sh,
),
constraints: BoxConstraints(maxHeight: 0.5.sh),
child: SingleChildScrollView(
physics: BouncingScrollPhysics(),
child: Column(
mainAxisSize: MainAxisSize.min,
children: controller.supportedLanguages.keys.map((language) {
final isSelected = isSource
? controller.sourceLanguage.value == language
: controller.targetLanguage.value == language;
return _buildLanguageItem(
language: language.split(' ')[0],
isSelected: isSelected,
return ListTile(
title: Text(
language,
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black,
),
),
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: () {
if (isSource) {
controller.setSourceLanguage(language);
@ -387,24 +408,19 @@ class TranslationView extends GetView<TranslationController> {
),
),
),
SizedBox(height: 24.h),
GestureDetector(
onTap: () => Get.back(),
child: Container(
margin: EdgeInsets.symmetric(horizontal: 16.w),
width: double.infinity,
padding: EdgeInsets.symmetric(vertical: 16.h),
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(12.r),
),
child: Text(
'Cancel',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w500,
color: Colors.black87,
SizedBox(height: 16.h),
Align(
alignment: Alignment.centerRight,
child: Padding(
padding: EdgeInsets.only(right: 24.w),
child: TextButton(
onPressed: () => Get.back(),
child: Text(
'取消',
style: TextStyle(
color: isDarkMode ? AppColors.primary : Colors.blue,
fontSize: 16.sp,
),
),
),
),
@ -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