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(); //设置媒体会话回调 _setupMediaSessionCallbacks(); //设置媒体会话回调
_setupTextInputListener(); //设置文本输入监听器 _setupTextInputListener(); //设置文本输入监听器
startVoiceInput(); //开启语音输入 startVoiceInput(); //开启语音输入
} }
@ -1013,8 +1011,6 @@ class AgentController extends GetxController {
} }
} }
// 开始语音输入 // 开始语音输入
Future<void> startVoiceInput() async { Future<void> startVoiceInput() async {
if (isListening.value) return; if (isListening.value) return;
@ -1309,29 +1305,71 @@ class AgentController extends GetxController {
return; return;
} }
// 切换到语音模式并开始语音输入 // 打电话模式
isTextInputMode.value = false; isTextInputMode.value = false;
// 打电话模式不使用按住说话模式,而是持续语音输入 Logger.i(TAG, '打电话模式已启动,开始语音输入');
//isPushToTalkMode.value = false;
// 开始语音识别
Logger.i(TAG, '开始打电话模式,开始语音输入');
await AgentService.startConversation(); await AgentService.startConversation();
//}
} }
// 结束打电话模式 // 结束打电话模式
void endPhoneCall() async { void endPhoneCall() async {
Logger.i(TAG, '🔥 结束打电话模式'); Logger.i(TAG, '🔥 结束打电话模式');
// 退出打电话模式 try {
isPhoneCallMode.value = false; // 如果正在录音,先停止录音
if (isListening.value) {
final result = await AgentService.stopConversation();
// 停止语音输入 if (result) {
//await stopVoiceInput(); isListening.value = false;
// 开始语音识别 }
await AgentService.stopConversation(); }
// 恢复之前的输入模式,而不是强制切换到文字模式
_restorePreviousInputMode(); // 退出打电话模式
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: SafeArea(
child: Column( child: Column(
children: [ children: [
// 顶部标题栏 - 保持背景 // 中间空白区域 - 完全透明
// Container(
// color: isDarkMode
// ? Colors.black.withOpacity(0.8)
// : Colors.white.withOpacity(0.8),
// child: _buildPhoneCallHeader(isDarkMode),
// ),
// 中间音频可视化区域 - 完全透明
const Expanded( const Expanded(
child: SizedBox(), // 空的占位符,完全透明 child: SizedBox(), // 空的占位符,完全透明
), ),
// 底部控制按钮 - 保持背景 // 底部控制按钮 - 包含音频波纹
Container( Container(
color: isDarkMode ? Colors.black : Colors.white, color: isDarkMode ? Colors.black : Colors.white,
child: _buildPhoneCallControls(isDarkMode), 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) { Widget _buildPhoneCallControls(bool isDarkMode) {
return Container( return Container(
padding: EdgeInsets.symmetric(horizontal: 32.w, vertical: 24.h), padding: EdgeInsets.symmetric(horizontal: 32.w, vertical: 24.h),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center, // 确保垂直居中对齐
children: [ children: [
// 静音按钮 // 录音控制按钮(左侧) - 控制录音输入功能
Obx(() => _buildPhoneControlButton( Obx(() => _buildPhoneControlButton(
icon: controller.isTtsEnabled.value icon: controller.isListening.value
? Icons.volume_up ? Icons.mic // 录音中显示麦克风图标
: Icons.volume_off, : Icons.mic_off, // 未录音显示麦克风关闭图标
onTap: () => controller.toggleTts(), onTap: () => controller.togglePhoneCallRecording(),
isDarkMode: isDarkMode, isDarkMode: isDarkMode,
isActive: controller.isTtsEnabled.value, isActive: controller.isListening.value,
// backgroundColor: controller.isListening.value
// ? Colors.red // 录音中显示红色背景
// : Theme.of(context).primaryColor, // 未录音显示主题色
)), )),
// 主要通话按钮 // 音频波纹组件(中间) - 仅显示动态波形效果,无其他UI元素
Obx(() { Container(
final isActive = controller.isListening.value; width: 120.w, // 设置固定宽度
return GestureDetector( height: 64.h, // 与按钮高度一致
// onTap: () { alignment: Alignment.center, // 确保波纹居中
// if (isActive) { child: const AgentSoundBarsWidget(barCount: 12), // 适应宽度的波纹条数
// 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,
),
),
);
}),
// 结束通话按钮 // 关闭打电话模式按钮(右侧) - 退出打电话模式
_buildPhoneControlButton( _buildPhoneControlButton(
icon: Icons.call_end, icon: Icons.close,
onTap: () => controller.endPhoneCall(), onTap: () => controller.endPhoneCall(),
isDarkMode: isDarkMode, isDarkMode: isDarkMode,
isActive: false, 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 isDarkMode,
required bool isActive, required bool isActive,
Color? backgroundColor, Color? backgroundColor,
Color? iconColor,
}) { }) {
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: onTap,
child: Container( child: Container(
width: 56.w, width: 64.w, // 按钮尺寸
height: 56.h, height: 64.h,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: backgroundColor ?? color: backgroundColor ??
(isDarkMode ? Colors.grey[800] : Colors.grey[200]), (isDarkMode ? Colors.grey[800] : Colors.grey[250]),
border: isActive ? Border.all(color: Colors.blue, width: 2.w) : null, 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( child: Icon(
icon, icon,
color: backgroundColor != null color: iconColor ?? (isDarkMode ? Colors.white : Colors.black),
? Colors.white size: 28.sp, // 图标尺寸
: (isDarkMode ? Colors.white : Colors.black),
size: 24.sp,
), ),
), ),
); );

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

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

Loading…
Cancel
Save