Browse Source

feat(agent): 优化电话模式UI并增加录音切换功能

- 将声音条组件居中显示
- 重构电话模式控制界面,移除冗余代码
- 增加电话模式专用的录音状态切换功能
- 改进错误处理逻辑,确保状态同步
weicu
lxm 1 year ago
parent
commit
22e97f572c
  1. 74
      lib/modules/agent/controllers/agent_controller.dart
  2. 219
      lib/modules/agent/views/agent_view.dart
  3. 2
      lib/modules/agent/widgets/sound_bar_widget.dart

74
lib/modules/agent/controllers/agent_controller.dart

@ -153,8 +153,6 @@ class AgentController extends GetxController {
_setupMediaSessionCallbacks(); //设置媒体会话回调
_setupTextInputListener(); //设置文本输入监听器
startVoiceInput(); //开启语音输入
}
@ -1013,8 +1011,6 @@ class AgentController extends GetxController {
}
}
// 开始语音输入
Future<void> startVoiceInput() async {
if (isListening.value) return;
@ -1309,29 +1305,71 @@ class AgentController extends GetxController {
return;
}
// 切换到语音模式并开始语音输入
// 打电话模式
isTextInputMode.value = false;
// 打电话模式不使用按住说话模式,而是持续语音输入
//isPushToTalkMode.value = false;
// 开始语音识别
Logger.i(TAG, '开始打电话模式,开始语音输入');
Logger.i(TAG, '打电话模式已启动,开始语音输入');
await AgentService.startConversation();
//}
}
// 结束打电话模式
void endPhoneCall() async {
Logger.i(TAG, '🔥 结束打电话模式');
// 退出打电话模式
isPhoneCallMode.value = false;
try {
// 如果正在录音,先停止录音
if (isListening.value) {
final result = await AgentService.stopConversation();
// 停止语音输入
//await stopVoiceInput();
// 开始语音识别
await AgentService.stopConversation();
// 恢复之前的输入模式,而不是强制切换到文字模式
_restorePreviousInputMode();
if (result) {
isListening.value = false;
}
}
// 退出打电话模式
isPhoneCallMode.value = false;
_restorePreviousInputMode();
} catch (e) {
Logger.e(TAG, '结束打电话模式失败: $e');
// 即使出现异常,也要确保退出打电话模式
isPhoneCallMode.value = false;
isListening.value = false;
_restorePreviousInputMode();
}
}
// 打电话模式专用:切换录音状态
Future<void> togglePhoneCallRecording() async {
Logger.i(TAG, '🎤 打电话模式切换录音状态');
// 只有在打电话模式下才能使用此方法
if (!isPhoneCallMode.value) {
Logger.w(TAG, '不在打电话模式,无法切换录音状态');
return;
}
try {
if (isListening.value) {
// 当前正在录音,停止录音
Logger.i(TAG, '停止打电话模式录音');
final result = await AgentService.stopConversation();
// 手动更新状态,确保UI同步
if (result) {
isListening.value = false;
}
} else {
// 当前未录音,开始录音
Logger.i(TAG, '开始打电话模式录音');
final result = await AgentService.startConversation();
// 手动更新状态,确保UI同步
if (result) {
isListening.value = true;
}
}
} catch (e) {
Logger.e(TAG, '打电话模式录音状态切换失败: $e');
}
}
// 耳机唤醒通话模式(专用入口)

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

@ -922,20 +922,12 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
child: SafeArea(
child: Column(
children: [
// 顶部标题栏 - 保持背景
// Container(
// color: isDarkMode
// ? Colors.black.withOpacity(0.8)
// : Colors.white.withOpacity(0.8),
// child: _buildPhoneCallHeader(isDarkMode),
// ),
// 中间音频可视化区域 - 完全透明
// 中间空白区域 - 完全透明
const Expanded(
child: SizedBox(), // 空的占位符,完全透明
),
// 底部控制按钮 - 保持背景
// 底部控制按钮 - 包含音频波纹
Container(
color: isDarkMode ? Colors.black : Colors.white,
child: _buildPhoneCallControls(isDarkMode),
@ -946,185 +938,43 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
);
}
// 构建打电话模式标题栏
Widget _buildPhoneCallHeader(bool isDarkMode) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
children: [
// 返回按钮
GestureDetector(
onTap: () => controller.endPhoneCall(),
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[800] : Colors.grey[200],
borderRadius: BorderRadius.circular(20),
),
child: Icon(
Icons.arrow_back,
color: isDarkMode ? Colors.white : Colors.black,
size: 20,
),
),
),
const SizedBox(width: 16),
// 标题
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'aiVoiceCall'.tr, // AI语音通话
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
),
Obx(() {
final status = controller.isListening.value
? 'inCall'.tr // 正在通话中...
: controller.isSpeaking.value
? 'aiReplying'.tr // AI正在回复...
: 'waitingToStart'.tr; // 等待开始...
return Text(
status,
style: TextStyle(
fontSize: 14,
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
),
);
}),
],
),
),
],
),
);
}
// 构建打电话模式音频可视化
Widget _buildPhoneCallVisualization(bool isDarkMode) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 音频可视化圆圈
Obx(() {
final isActive =
controller.isListening.value || controller.isSpeaking.value;
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
width: isActive ? 200 : 160,
height: isActive ? 200 : 160,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: isDarkMode
? Colors.blue[900]?.withOpacity(0.3)
: Colors.blue[100],
border: Border.all(
color: Colors.blue,
width: isActive ? 3 : 2,
),
),
child: Icon(
Icons.phone,
size: isActive ? 80 : 60,
color: Colors.blue,
),
);
}),
const SizedBox(height: 32),
// 状态文字
Obx(() {
String statusText = 'clickToStartCall'.tr; // 点击开始通话
if (controller.isListening.value) {
statusText = 'listeningToYou'.tr; // 正在听您说话...
} else if (controller.isSpeaking.value) {
statusText = 'aiReplying'.tr; // AI正在回复...
} else if (controller.isProcessing.value) {
statusText = 'processing'.tr; // 正在处理...
}
return Text(
statusText,
style: TextStyle(
fontSize: 16,
color: isDarkMode ? Colors.grey[300] : Colors.grey[700],
fontWeight: FontWeight.w500,
),
);
}),
],
),
);
}
// 构建打电话模式控制按钮
Widget _buildPhoneCallControls(bool isDarkMode) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 32.w, vertical: 24.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center, // 确保垂直居中对齐
children: [
// 静音按钮
// 录音控制按钮(左侧) - 控制录音输入功能
Obx(() => _buildPhoneControlButton(
icon: controller.isTtsEnabled.value
? Icons.volume_up
: Icons.volume_off,
onTap: () => controller.toggleTts(),
icon: controller.isListening.value
? Icons.mic // 录音中显示麦克风图标
: Icons.mic_off, // 未录音显示麦克风关闭图标
onTap: () => controller.togglePhoneCallRecording(),
isDarkMode: isDarkMode,
isActive: controller.isTtsEnabled.value,
isActive: controller.isListening.value,
// backgroundColor: controller.isListening.value
// ? Colors.red // 录音中显示红色背景
// : Theme.of(context).primaryColor, // 未录音显示主题色
)),
// 主要通话按钮
Obx(() {
final isActive = controller.isListening.value;
return GestureDetector(
// onTap: () {
// if (isActive) {
// controller.stopVoiceInput();
// } else {
// controller.startVoiceInput();
// }
// },
child: Container(
width: 80.w,
height: 80.h,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: isActive ? Colors.red : Colors.green,
boxShadow: [
BoxShadow(
color: (isActive ? Colors.red : Colors.green)
.withOpacity(0.3),
blurRadius: 12.w,
spreadRadius: 2.w,
),
],
),
child: Icon(
isActive ? Icons.call_end : Icons.call,
color: Colors.white,
size: 36.sp,
),
),
);
}),
// 音频波纹组件(中间) - 仅显示动态波形效果,无其他UI元素
Container(
width: 120.w, // 设置固定宽度
height: 64.h, // 与按钮高度一致
alignment: Alignment.center, // 确保波纹居中
child: const AgentSoundBarsWidget(barCount: 12), // 适应宽度的波纹条数
),
// 结束通话按钮
// 关闭打电话模式按钮(右侧) - 退出打电话模式
_buildPhoneControlButton(
icon: Icons.call_end,
icon: Icons.close,
onTap: () => controller.endPhoneCall(),
isDarkMode: isDarkMode,
isActive: false,
backgroundColor: Colors.red,
//backgroundColor: Colors.red,
iconColor: Colors.redAccent,
),
],
),
@ -1138,24 +988,31 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
required bool isDarkMode,
required bool isActive,
Color? backgroundColor,
Color? iconColor,
}) {
return GestureDetector(
onTap: onTap,
child: Container(
width: 56.w,
height: 56.h,
width: 64.w, // 按钮尺寸
height: 64.h,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: backgroundColor ??
(isDarkMode ? Colors.grey[800] : Colors.grey[200]),
border: isActive ? Border.all(color: Colors.blue, width: 2.w) : null,
(isDarkMode ? Colors.grey[800] : Colors.grey[250]),
border: Border.all(color: Colors.blue, width: 2.w),
boxShadow: [
BoxShadow(
color: (backgroundColor ?? Colors.grey).withValues(alpha: 0.3),
blurRadius: 8.w,
spreadRadius: 1.w,
offset: Offset(0, 2.h),
),
],
),
child: Icon(
icon,
color: backgroundColor != null
? Colors.white
: (isDarkMode ? Colors.white : Colors.black),
size: 24.sp,
color: iconColor ?? (isDarkMode ? Colors.white : Colors.black),
size: 28.sp, // 图标尺寸
),
),
);

2
lib/modules/agent/widgets/sound_bar_widget.dart

@ -106,7 +106,7 @@ class SoundBarsWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(barCount, (index) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 1),

Loading…
Cancel
Save