|
|
|
@ -21,8 +21,9 @@ class MobileElfView extends GetView<MobileElfController> { |
|
|
|
static const _border = Color(0xFFEAECF0); |
|
|
|
|
|
|
|
// 渐变复用 |
|
|
|
// 中国移动品牌色:蓝→绿 |
|
|
|
static const _avatarGradient = LinearGradient( |
|
|
|
colors: [Color(0xFF667EEA), Color(0xFF764BA2)], |
|
|
|
colors: [Color(0xFF0077D9), Color(0xFF00A651)], |
|
|
|
begin: Alignment.topLeft, |
|
|
|
end: Alignment.bottomRight, |
|
|
|
); |
|
|
|
@ -77,9 +78,9 @@ class MobileElfView extends GetView<MobileElfController> { |
|
|
|
title: Row( |
|
|
|
mainAxisSize: MainAxisSize.min, |
|
|
|
children: [ |
|
|
|
_gradientCircle(32.w, '晓', 13.sp), |
|
|
|
_gradientCircle(32.w, 'mobileElfAvatar'.tr, 13.sp), |
|
|
|
SizedBox(width: 10.w), |
|
|
|
Text('移动精灵', |
|
|
|
Text('mobileElf'.tr, |
|
|
|
style: TextStyle( |
|
|
|
fontSize: 16.sp, |
|
|
|
fontWeight: FontWeight.w600, |
|
|
|
@ -101,7 +102,7 @@ class MobileElfView extends GetView<MobileElfController> { |
|
|
|
: (isDark ? Colors.grey[500] : _textSecondary), |
|
|
|
), |
|
|
|
onPressed: () => controller.ttsEnabled.toggle(), |
|
|
|
tooltip: controller.ttsEnabled.value ? '关闭播报' : '开启播报', |
|
|
|
tooltip: controller.ttsEnabled.value ? 'closeBroadcast'.tr : 'openBroadcast'.tr, |
|
|
|
)), |
|
|
|
PopupMenuButton<String>( |
|
|
|
icon: Icon(Icons.more_vert, |
|
|
|
@ -110,16 +111,16 @@ class MobileElfView extends GetView<MobileElfController> { |
|
|
|
if (value == 'clear') { |
|
|
|
Get.dialog( |
|
|
|
AlertDialog( |
|
|
|
title: Text('clearChatHistory'.tr.isNotEmpty ? 'clearChatHistory'.tr : '清空记录'), |
|
|
|
content: Text('confirmClearChat'.tr.isNotEmpty ? 'confirmClearChat'.tr : '确定清空所有聊天记录吗?'), |
|
|
|
title: Text('clearChatHistory'.tr), |
|
|
|
content: Text('confirmClearChat'.tr), |
|
|
|
actions: [ |
|
|
|
TextButton(onPressed: () => Get.back(), child: Text('cancel'.tr.isNotEmpty ? 'cancel'.tr : '取消')), |
|
|
|
TextButton(onPressed: () => Get.back(), child: Text('cancel'.tr)), |
|
|
|
TextButton( |
|
|
|
onPressed: () { |
|
|
|
controller.clearMessages(); |
|
|
|
Get.back(); |
|
|
|
}, |
|
|
|
child: Text('confirm'.tr.isNotEmpty ? 'confirm'.tr : '确定', style: const TextStyle(color: Colors.red)), |
|
|
|
child: Text('confirm'.tr, style: const TextStyle(color: Colors.red)), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
@ -132,7 +133,7 @@ class MobileElfView extends GetView<MobileElfController> { |
|
|
|
child: Row(children: [ |
|
|
|
Icon(Icons.delete_outline, size: 18.sp, color: Colors.red), |
|
|
|
SizedBox(width: 8.w), |
|
|
|
Text('清空记录', style: TextStyle(fontSize: 14.sp, color: Colors.red)), |
|
|
|
Text('clearChatHistory'.tr, style: TextStyle(fontSize: 14.sp, color: Colors.red)), |
|
|
|
]), |
|
|
|
), |
|
|
|
], |
|
|
|
@ -150,7 +151,7 @@ class MobileElfView extends GetView<MobileElfController> { |
|
|
|
child: Row(mainAxisSize: MainAxisSize.min, children: [ |
|
|
|
Icon(Icons.lock_outline_rounded, size: 11.sp, color: _red), |
|
|
|
SizedBox(width: 4.w), |
|
|
|
Text('认证失败', style: TextStyle(fontSize: 11.sp, color: _red, fontWeight: FontWeight.w500)), |
|
|
|
Text('authFailed'.tr, style: TextStyle(fontSize: 11.sp, color: _red, fontWeight: FontWeight.w500)), |
|
|
|
]), |
|
|
|
); |
|
|
|
} |
|
|
|
@ -190,19 +191,19 @@ class MobileElfView extends GetView<MobileElfController> { |
|
|
|
_PillCfg _pillConfig(MobileElfStatus s, bool inCall) { |
|
|
|
switch (s) { |
|
|
|
case MobileElfStatus.connecting: |
|
|
|
return const _PillCfg(Color(0xFFFFFAEB), Color(0xFFB54708), '连接中…'); |
|
|
|
return _PillCfg(const Color(0xFFFFFAEB), const Color(0xFFB54708), 'statusConnecting'.tr); |
|
|
|
case MobileElfStatus.recording: |
|
|
|
return const _PillCfg(Color(0xFFECFDF3), _greenDark, '聆听中'); |
|
|
|
return _PillCfg(const Color(0xFFECFDF3), _greenDark, 'statusListening'.tr); |
|
|
|
case MobileElfStatus.thinking: |
|
|
|
return const _PillCfg(Color(0xFFEFF8FF), Color(0xFF175CD3), '处理中'); |
|
|
|
return _PillCfg(const Color(0xFFEFF8FF), const Color(0xFF175CD3), 'statusProcessing'.tr); |
|
|
|
case MobileElfStatus.speaking: |
|
|
|
return const _PillCfg(Color(0xFFECFDF3), _greenDark, '朗读中'); |
|
|
|
return _PillCfg(const Color(0xFFECFDF3), _greenDark, 'statusSpeaking'.tr); |
|
|
|
case MobileElfStatus.error: |
|
|
|
return const _PillCfg(Color(0xFFFEF2F2), _red, '错误'); |
|
|
|
return _PillCfg(const Color(0xFFFEF2F2), _red, 'statusError'.tr); |
|
|
|
case MobileElfStatus.idle: |
|
|
|
return inCall |
|
|
|
? const _PillCfg(Color(0xFFECFDF3), _greenDark, '通话中') |
|
|
|
: const _PillCfg(Color(0xFFF2F4F7), _textSecondary, '空闲'); |
|
|
|
? _PillCfg(const Color(0xFFECFDF3), _greenDark, 'statusInCall'.tr) |
|
|
|
: _PillCfg(const Color(0xFFF2F4F7), _textSecondary, 'statusIdle'.tr); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -226,7 +227,7 @@ class MobileElfView extends GetView<MobileElfController> { |
|
|
|
ElevatedButton.icon( |
|
|
|
onPressed: controller.retryCredentials, |
|
|
|
icon: const Icon(Icons.refresh), |
|
|
|
label: const Text('重试'), |
|
|
|
label: Text('retry'.tr), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
@ -260,15 +261,15 @@ class MobileElfView extends GetView<MobileElfController> { |
|
|
|
child: Column( |
|
|
|
mainAxisSize: MainAxisSize.min, |
|
|
|
children: [ |
|
|
|
_gradientCircle(80.w, '晓', 32.sp), |
|
|
|
_gradientCircle(80.w, 'mobileElfAvatar'.tr, 32.sp), |
|
|
|
SizedBox(height: 20.h), |
|
|
|
Text('移动精灵', |
|
|
|
Text('mobileElf'.tr, |
|
|
|
style: TextStyle( |
|
|
|
fontSize: 20.sp, |
|
|
|
fontWeight: FontWeight.bold, |
|
|
|
color: isDark ? Colors.white : _textPrimary)), |
|
|
|
SizedBox(height: 8.h), |
|
|
|
Text('点击右下角按钮,即可与晓晓对话', |
|
|
|
Text('tapToChat'.tr, |
|
|
|
style: TextStyle( |
|
|
|
fontSize: 13.sp, |
|
|
|
color: isDark ? Colors.grey[500] : _textSecondary)), |
|
|
|
@ -290,9 +291,9 @@ class MobileElfView extends GetView<MobileElfController> { |
|
|
|
// AI 头像 + 名字 |
|
|
|
if (!isUser) ...[ |
|
|
|
Row(children: [ |
|
|
|
_gradientCircle(28.w, '晓', 11.sp), |
|
|
|
_gradientCircle(28.w, 'mobileElfAvatar'.tr, 11.sp), |
|
|
|
SizedBox(width: 6.w), |
|
|
|
Text('晓晓', |
|
|
|
Text('mobileElfName'.tr, |
|
|
|
style: TextStyle(fontSize: 11.sp, color: _textSecondary)), |
|
|
|
]), |
|
|
|
SizedBox(height: 4.h), |
|
|
|
@ -352,7 +353,7 @@ class MobileElfView extends GetView<MobileElfController> { |
|
|
|
fontSize: 10.sp, color: isDark ? Colors.grey[500] : const Color(0xFFD0D5DD))), |
|
|
|
if (!isUser) ...[ |
|
|
|
SizedBox(width: 8.w), |
|
|
|
Text('generatedByAi'.tr.isNotEmpty ? 'generatedByAi'.tr : '由AI生成', |
|
|
|
Text('generatedByAi'.tr, |
|
|
|
style: TextStyle( |
|
|
|
fontSize: 10.sp, |
|
|
|
color: Colors.blue[600], |
|
|
|
@ -385,14 +386,14 @@ class MobileElfView extends GetView<MobileElfController> { |
|
|
|
PopupMenuItem( |
|
|
|
onTap: () { |
|
|
|
Clipboard.setData(ClipboardData(text: msg.text)); |
|
|
|
Get.snackbar('success'.tr.isNotEmpty ? 'success'.tr : '成功', |
|
|
|
'copySuccess'.tr.isNotEmpty ? 'copySuccess'.tr : '已复制', |
|
|
|
Get.snackbar('success'.tr, |
|
|
|
'copySuccess'.tr, |
|
|
|
backgroundColor: Colors.blue.withValues(alpha: 0.8), colorText: Colors.white); |
|
|
|
}, |
|
|
|
child: Row(children: [ |
|
|
|
Icon(Icons.copy, size: 18.sp, color: isDark ? Colors.white : Colors.black87), |
|
|
|
SizedBox(width: 8.w), |
|
|
|
Text('copy'.tr.isNotEmpty ? 'copy'.tr : '复制', |
|
|
|
Text('copy'.tr, |
|
|
|
style: TextStyle(fontSize: 14.sp, color: isDark ? Colors.white : Colors.black87)), |
|
|
|
]), |
|
|
|
), |
|
|
|
@ -407,15 +408,14 @@ class MobileElfView extends GetView<MobileElfController> { |
|
|
|
final today = DateTime(now.year, now.month, now.day); |
|
|
|
final msgDate = DateTime(t.year, t.month, t.day); |
|
|
|
|
|
|
|
if (diff.inMinutes < 1) return 'justNow'.tr.isNotEmpty ? 'justNow'.tr : '刚刚'; |
|
|
|
if (diff.inMinutes < 1) return 'justNow'.tr; |
|
|
|
if (diff.inHours < 1) { |
|
|
|
final m = diff.inMinutes.toString(); |
|
|
|
final text = 'minutesAgo'.trParams({'minutes': m}); |
|
|
|
return text.isNotEmpty && text != 'minutesAgo' ? text : '$m分钟前'; |
|
|
|
return 'minutesAgo'.trParams({'minutes': m}); |
|
|
|
} |
|
|
|
if (msgDate == today) return DateFormat('HH:mm').format(t); |
|
|
|
if (msgDate == today.subtract(const Duration(days: 1))) { |
|
|
|
return '${('yesterday'.tr.isNotEmpty ? 'yesterday'.tr : '昨天')} ${DateFormat('HH:mm').format(t)}'; |
|
|
|
return '${'yesterday'.tr} ${DateFormat('HH:mm').format(t)}'; |
|
|
|
} |
|
|
|
if (diff.inDays < 7) { |
|
|
|
return '${_weekdayName(t.weekday)} ${DateFormat('HH:mm').format(t)}'; |
|
|
|
@ -425,7 +425,7 @@ class MobileElfView extends GetView<MobileElfController> { |
|
|
|
} |
|
|
|
|
|
|
|
String _weekdayName(int w) { |
|
|
|
const names = ['', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日']; |
|
|
|
final names = ['', 'weekdayMon'.tr, 'weekdayTue'.tr, 'weekdayWed'.tr, 'weekdayThu'.tr, 'weekdayFri'.tr, 'weekdaySat'.tr, 'weekdaySun'.tr]; |
|
|
|
return names[w]; |
|
|
|
} |
|
|
|
|
|
|
|
@ -512,14 +512,14 @@ class MobileElfView extends GetView<MobileElfController> { |
|
|
|
children: [ |
|
|
|
_MiniWave(color: _greenDark), |
|
|
|
SizedBox(width: 8.w), |
|
|
|
Expanded(child: Text('晓晓正在朗读...', style: TextStyle(fontSize: 13.sp, color: _greenDark, fontWeight: FontWeight.w500))), |
|
|
|
Expanded(child: Text('aiSpeaking'.tr, style: TextStyle(fontSize: 13.sp, color: _greenDark, fontWeight: FontWeight.w500))), |
|
|
|
Container( |
|
|
|
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 4.h), |
|
|
|
decoration: BoxDecoration( |
|
|
|
border: Border.all(color: _red), |
|
|
|
borderRadius: BorderRadius.circular(16.r), |
|
|
|
), |
|
|
|
child: Text('× 打断', style: TextStyle(fontSize: 12.sp, color: _red, fontWeight: FontWeight.w500)), |
|
|
|
child: Text('interrupt'.tr, style: TextStyle(fontSize: 12.sp, color: _red, fontWeight: FontWeight.w500)), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
@ -536,7 +536,7 @@ class MobileElfView extends GetView<MobileElfController> { |
|
|
|
SizedBox(width: 8.w), |
|
|
|
Expanded(child: Obx(() { |
|
|
|
final rt = controller.realtimeText.value; |
|
|
|
return Text(rt.isNotEmpty ? rt : '聆听中...', style: TextStyle(fontSize: 13.sp, color: _greenDark), maxLines: 1, overflow: TextOverflow.ellipsis); |
|
|
|
return Text(rt.isNotEmpty ? rt : 'listeningHint'.tr, style: TextStyle(fontSize: 13.sp, color: _greenDark), maxLines: 1, overflow: TextOverflow.ellipsis); |
|
|
|
})), |
|
|
|
], |
|
|
|
), |
|
|
|
@ -550,7 +550,7 @@ class MobileElfView extends GetView<MobileElfController> { |
|
|
|
children: [ |
|
|
|
SizedBox(width: 14.w, height: 14.w, child: CircularProgressIndicator(strokeWidth: 2, color: _orange)), |
|
|
|
SizedBox(width: 8.w), |
|
|
|
Text('正在连接...', style: TextStyle(fontSize: 13.sp, color: const Color(0xFFB54708))), |
|
|
|
Text('connectingHint'.tr, style: TextStyle(fontSize: 13.sp, color: const Color(0xFFB54708))), |
|
|
|
], |
|
|
|
), |
|
|
|
); |
|
|
|
@ -563,7 +563,7 @@ class MobileElfView extends GetView<MobileElfController> { |
|
|
|
children: [ |
|
|
|
SizedBox(width: 14.w, height: 14.w, child: CircularProgressIndicator(strokeWidth: 2, color: const Color(0xFF175CD3))), |
|
|
|
SizedBox(width: 8.w), |
|
|
|
Text('AI 思考中...', style: TextStyle(fontSize: 13.sp, color: const Color(0xFF175CD3))), |
|
|
|
Text('elfAiThinking'.tr, style: TextStyle(fontSize: 13.sp, color: const Color(0xFF175CD3))), |
|
|
|
], |
|
|
|
), |
|
|
|
); |
|
|
|
@ -593,7 +593,7 @@ class MobileElfView extends GetView<MobileElfController> { |
|
|
|
decoration: BoxDecoration(shape: BoxShape.circle, color: idle ? _green : _orange), |
|
|
|
), |
|
|
|
SizedBox(width: 6.w), |
|
|
|
Text('通话中', style: TextStyle(fontSize: 13.sp, color: isDark ? Colors.white70 : _textSecondary)), |
|
|
|
Text('statusInCall'.tr, style: TextStyle(fontSize: 13.sp, color: isDark ? Colors.white70 : _textSecondary)), |
|
|
|
SizedBox(width: 4.w), |
|
|
|
Obx(() => Text(_fmtDuration(controller.callSeconds.value), |
|
|
|
style: TextStyle(fontSize: 13.sp, color: isDark ? Colors.white70 : _textSecondary))), |
|
|
|
@ -726,7 +726,7 @@ class MobileElfView extends GetView<MobileElfController> { |
|
|
|
border: Border.all(color: isDark ? const Color(0xFF444444) : const Color(0xFFD0D5DD), width: 1), |
|
|
|
), |
|
|
|
child: Center( |
|
|
|
child: Text('按住 说话', |
|
|
|
child: Text('holdToTalk'.tr, |
|
|
|
style: TextStyle( |
|
|
|
fontSize: 15.sp, |
|
|
|
color: isDark ? Colors.white70 : _textPrimary, |
|
|
|
@ -761,7 +761,7 @@ class MobileElfView extends GetView<MobileElfController> { |
|
|
|
SizedBox(width: 14.w, height: 14.w, |
|
|
|
child: CircularProgressIndicator(strokeWidth: 2, color: isDark ? Colors.white70 : _textSecondary)), |
|
|
|
SizedBox(width: 8.w), |
|
|
|
Text('连接中...', style: TextStyle(fontSize: 14.sp, color: isDark ? Colors.white70 : _textSecondary, fontWeight: FontWeight.w500)), |
|
|
|
Text('connectingHint'.tr, style: TextStyle(fontSize: 14.sp, color: isDark ? Colors.white70 : _textSecondary, fontWeight: FontWeight.w500)), |
|
|
|
], |
|
|
|
), |
|
|
|
); |
|
|
|
@ -783,7 +783,7 @@ class MobileElfView extends GetView<MobileElfController> { |
|
|
|
children: [ |
|
|
|
Obx(() => _RmsBars(rms: controller.micRms.value, color: Colors.white)), |
|
|
|
SizedBox(width: 8.w), |
|
|
|
Obx(() => Text('松开发送 · ${controller.pttSeconds.value}s', |
|
|
|
Obx(() => Text('${'releaseToSend'.tr} · ${controller.pttSeconds.value}s', |
|
|
|
style: TextStyle(fontSize: 14.sp, color: Colors.white, fontWeight: FontWeight.w500))), |
|
|
|
], |
|
|
|
), |
|
|
|
|