Browse Source

Merge branch 'new_dev' of https://github.com/deepcloud2048/deep_voice into new_dev

newdev_shunjiawei
tanlongsheng 1 year ago
parent
commit
6a5c70b5f0
  1. 144
      lib/modules/agent/controllers/agent_controller.dart
  2. 278
      lib/modules/agent/views/agent_view.dart
  3. 6
      lib/modules/translation/controllers/translation_controller.dart

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

@ -75,8 +75,11 @@ class AgentController extends GetxController {
final isFullScreenEditMode = false.obs; // 是否处于全屏编辑模式 final isFullScreenEditMode = false.obs; // 是否处于全屏编辑模式
final shouldShowExpandButton = false.obs; // 是否显示展开按钮 final shouldShowExpandButton = false.obs; // 是否显示展开按钮
// 图片选择选项展开状态 // "+"号选项展开状态
final isImageOptionsExpanded = false.obs; // 是否展开图片选择选项 final isPlusOptionsExpanded = false.obs; // 是否展开"+"号选项
// 打电话模式状态
final isPhoneCallMode = false.obs; // 是否处于打电话模式
// 键盘状态 // 键盘状态
final isKeyboardVisible = false.obs; // 键盘是否可见 final isKeyboardVisible = false.obs; // 键盘是否可见
@ -904,39 +907,65 @@ class AgentController extends GetxController {
isImageInputActive.value = false; isImageInputActive.value = false;
} }
// 切换图片选项展开状态 // 切换'+'选项展开状态
void toggleImageOptions() { void togglePlusOptions() {
if (isImageOptionsExpanded.value) { if (isPlusOptionsExpanded.value) {
// 如果当前已展开,则收缩 // 如果当前已展开,则收缩
collapseImageOptions(); collapsePlusOptions();
_pendingPlusOptionsExpansion = false; // 取消待展开状态
} else { } else {
// 如果当前未展开,则展开并关闭键盘 // 如果当前未展开,则展开并关闭键盘
if (isKeyboardVisible.value) { if (isKeyboardVisible.value) {
// 设置待展开标记
_pendingPlusOptionsExpansion = true;
hideKeyboard(); hideKeyboard();
// 延迟展开选项,等待键盘完全关闭 // 使用双重机制:轮询检测 + 键盘状态监听
Future.delayed(const Duration(milliseconds: 200), () { _waitForKeyboardToClose();
// 再次检查键盘状态,确保键盘已关闭
if (!isKeyboardVisible.value) {
isImageOptionsExpanded.value = true;
} else {
// 如果键盘还没关闭,再等一会儿
Future.delayed(const Duration(milliseconds: 100), () {
if (!isKeyboardVisible.value) {
isImageOptionsExpanded.value = true;
}
});
}
});
} else { } else {
// 键盘未显示,直接展开 // 键盘未显示,直接展开
isImageOptionsExpanded.value = true; isPlusOptionsExpanded.value = true;
} }
} }
} }
// 收缩图片选项 // 等待键盘关闭的轮询机制
void collapseImageOptions() { void _waitForKeyboardToClose() {
isImageOptionsExpanded.value = false; int attempts = 0;
const maxAttempts = 30; // 最多尝试30次,总计3秒
const checkInterval = Duration(milliseconds: 100); // 每100ms检查一次
Timer.periodic(checkInterval, (timer) {
attempts++;
// 检查是否还需要展开(用户可能已经取消了)
if (!_pendingPlusOptionsExpansion) {
timer.cancel();
return;
}
if (!isKeyboardVisible.value) {
// 键盘已关闭,展开选项
timer.cancel();
_pendingPlusOptionsExpansion = false;
// 添加小延迟确保UI稳定
Future.delayed(const Duration(milliseconds: 50), () {
if (!isKeyboardVisible.value) { // 再次确认键盘状态
isPlusOptionsExpanded.value = true;
}
});
} else if (attempts >= maxAttempts) {
// 超时,强制展开(可能键盘检测有问题)
timer.cancel();
_pendingPlusOptionsExpansion = false;
Logger.d('AgentController', '键盘关闭检测超时,强制展开选项');
isPlusOptionsExpanded.value = true;
}
});
}
// 收缩'+'选项
void collapsePlusOptions() {
isPlusOptionsExpanded.value = false;
} }
// 更新键盘可见状态 // 更新键盘可见状态
@ -944,13 +973,26 @@ class AgentController extends GetxController {
final wasVisible = isKeyboardVisible.value; final wasVisible = isKeyboardVisible.value;
isKeyboardVisible.value = isVisible; isKeyboardVisible.value = isVisible;
// 只有当键盘从隐藏变为显示时,才收缩图片选项 // 只有当键盘从隐藏变为显示时,才收缩'+'选项
// 避免键盘关闭时误触发收缩逻辑 // 避免键盘关闭时误触发收缩逻辑
if (!wasVisible && isVisible && isImageOptionsExpanded.value) { if (!wasVisible && isVisible && isPlusOptionsExpanded.value) {
collapseImageOptions(); collapsePlusOptions();
}
// 如果键盘关闭了,并且有待展开的选项,则展开
if (wasVisible && !isVisible && _pendingPlusOptionsExpansion) {
_pendingPlusOptionsExpansion = false;
Future.delayed(const Duration(milliseconds: 100), () {
if (!isKeyboardVisible.value) {
isPlusOptionsExpanded.value = true;
}
});
} }
} }
// 标记是否有待展开的'+'选项
bool _pendingPlusOptionsExpansion = false;
// 隐藏键盘 // 隐藏键盘
void hideKeyboard() { void hideKeyboard() {
FocusManager.instance.primaryFocus?.unfocus(); FocusManager.instance.primaryFocus?.unfocus();
@ -985,6 +1027,54 @@ class AgentController extends GetxController {
useImagePrompt('请翻译这张图片中的文字'); useImagePrompt('请翻译这张图片中的文字');
} }
// 开始打电话模式
void startPhoneCall() async {
Logger.i(TAG, '🔥 开始打电话模式');
// 收缩"+"选项
collapsePlusOptions();
// 进入打电话模式
isPhoneCallMode.value = true;
// 如果当前是文本模式,切换到语音模式
if (isTextInputMode.value) {
// 检查麦克风权限
final hasPermission = await PermissionUtil.instance.requestPermission(
permissionType: Permission.microphone,
permissionName: '麦克风',
explanationText: '打电话功能需要访问麦克风进行语音通话。',
permanentDenialText: '您已永久拒绝麦克风权限,请前往系统设置开启权限。',
settingsButtonText: '去设置',
cancelButtonText: '取消',
);
if (!hasPermission) {
// 权限未获取,退出打电话模式
isPhoneCallMode.value = false;
return;
}
// 切换到语音模式并开始语音输入
isTextInputMode.value = false;
await startVoiceInput();
}
}
// 结束打电话模式
void endPhoneCall() async {
Logger.i(TAG, '🔥 结束打电话模式');
// 退出打电话模式
isPhoneCallMode.value = false;
// 停止语音输入
await stopVoiceInput();
// 切换回文本模式
isTextInputMode.value = true;
}
// 获取音乐播放状态 // 获取音乐播放状态
Future<void> musicStatus() async { Future<void> musicStatus() async {
try { try {

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

@ -183,10 +183,13 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
), ),
], ],
), ),
body: Column( body: Stack(
children: [ children: [
// 聊天内容 // 主要聊天界面
Expanded( Column(
children: [
// 聊天内容
Expanded(
child: Obx( child: Obx(
() => Align( () => Align(
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
@ -407,12 +410,12 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
textInputAction: TextInputAction textInputAction: TextInputAction
.newline, // 支持回车换行 .newline, // 支持回车换行
onTap: () { onTap: () {
// 当点击输入框时,如果图片选项已展开,则收缩 // 当点击输入框时,如果'+'选项已展开,则收缩
if (controller if (controller
.isImageOptionsExpanded .isPlusOptionsExpanded
.value) { .value) {
controller controller
.collapseImageOptions(); .collapsePlusOptions();
} }
}, },
style: TextStyle( style: TextStyle(
@ -607,10 +610,10 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
// 显示'+'选择按钮 // 显示'+'选择按钮
return Obx(() { return Obx(() {
final isExpanded = final isExpanded =
controller.isImageOptionsExpanded.value; controller.isPlusOptionsExpanded.value;
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
controller.toggleImageOptions(); controller.togglePlusOptions();
}, },
child: Container( child: Container(
width: 44, width: 44,
@ -640,11 +643,11 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
// 展开的'+'选择选项 // 展开的'+'选择选项
Obx(() { Obx(() {
final isExpanded = final isExpanded =
controller.isImageOptionsExpanded.value; controller.isPlusOptionsExpanded.value;
return AnimatedContainer( return AnimatedContainer(
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut, curve: Curves.easeInOut,
height: isExpanded ? 100.h : 0, // 调整高度以适应按钮尺寸 height: isExpanded ? 120.h : 0, // 调整高度以适应按钮尺寸
child: isExpanded child: isExpanded
? Container( ? Container(
margin: EdgeInsets.only(top: 8.h), margin: EdgeInsets.only(top: 8.h),
@ -658,7 +661,7 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
icon: Icons.camera_alt, icon: Icons.camera_alt,
label: '拍照', label: '拍照',
onTap: () async { onTap: () async {
//controller.collapseImageOptions(); //controller.collapsePlusOptions();
await controller.takePhoto(); await controller.takePhoto();
}, },
isDarkMode: isDarkMode, isDarkMode: isDarkMode,
@ -669,7 +672,7 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
icon: Icons.photo_library, icon: Icons.photo_library,
label: '相册', label: '相册',
onTap: () async { onTap: () async {
//controller.collapseImageOptions(); //controller.collapsePlusOptions();
await controller.pickImage(); await controller.pickImage();
}, },
isDarkMode: isDarkMode, isDarkMode: isDarkMode,
@ -680,19 +683,19 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
// icon: Icons.folder, // icon: Icons.folder,
// label: '文件', // label: '文件',
// onTap: () { // onTap: () {
// //controller.collapseImageOptions(); // //controller.collapsePlusOptions();
// // TODO: 实现文件选择功能 // // TODO: 实现文件选择功能
// }, // },
// isDarkMode: isDarkMode, // isDarkMode: isDarkMode,
// ), // ),
// 通话按钮(预留位置) // 通话按钮
_buildPlusOptionButton( _buildPlusOptionButton(
context: context, context: context,
icon: Icons.phone, icon: Icons.phone,
label: '打电话', label: '打电话',
onTap: () { onTap: () {
//controller.collapseImageOptions(); // 启动打电话模式
// TODO: 实现通话功能 controller.startPhoneCall();
}, },
isDarkMode: isDarkMode, isDarkMode: isDarkMode,
), ),
@ -707,6 +710,16 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
], ],
), ),
), ),
],
),
// 打电话模式覆盖层
Obx(() {
if (controller.isPhoneCallMode.value) {
return _buildPhoneCallOverlay(context, isDarkMode);
}
return const SizedBox.shrink();
}),
], ],
), ),
); );
@ -808,6 +821,239 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
), ),
); );
} }
// 构建打电话模式覆盖层
Widget _buildPhoneCallOverlay(BuildContext context, bool isDarkMode) {
return Container(
color: isDarkMode ? Colors.black : Colors.white,
child: SafeArea(
child: Column(
children: [
// 顶部标题栏
_buildPhoneCallHeader(isDarkMode),
// 中间音频可视化区域
Expanded(
child: _buildPhoneCallVisualization(isDarkMode),
),
// 底部控制按钮
_buildPhoneCallControls(isDarkMode),
],
),
),
);
}
// 构建打电话模式标题栏
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(
'AI语音通话',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
),
Obx(() {
final status = controller.isListening.value
? '正在通话中...'
: controller.isSpeaking.value
? 'AI正在回复...'
: '等待开始...';
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 = '点击开始通话';
if (controller.isListening.value) {
statusText = '正在听您说话...';
} else if (controller.isSpeaking.value) {
statusText = 'AI正在回复...';
} else if (controller.isProcessing.value) {
statusText = '正在处理...';
}
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: const EdgeInsets.symmetric(horizontal: 32, vertical: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// 静音按钮
Obx(() => _buildPhoneControlButton(
icon: controller.isTtsEnabled.value ? Icons.volume_up : Icons.volume_off,
onTap: () => controller.toggleTts(),
isDarkMode: isDarkMode,
isActive: controller.isTtsEnabled.value,
)),
// 主要通话按钮
Obx(() {
final isActive = controller.isListening.value;
return GestureDetector(
onTap: () {
if (isActive) {
controller.stopVoiceInput();
} else {
controller.startVoiceInput();
}
},
child: Container(
width: 80,
height: 80,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: isActive ? Colors.red : Colors.green,
boxShadow: [
BoxShadow(
color: (isActive ? Colors.red : Colors.green).withOpacity(0.3),
blurRadius: 12,
spreadRadius: 2,
),
],
),
child: Icon(
isActive ? Icons.call_end : Icons.call,
color: Colors.white,
size: 36,
),
),
);
}),
// 结束通话按钮
_buildPhoneControlButton(
icon: Icons.call_end,
onTap: () => controller.endPhoneCall(),
isDarkMode: isDarkMode,
isActive: false,
backgroundColor: Colors.red,
),
],
),
);
}
// 构建打电话控制按钮
Widget _buildPhoneControlButton({
required IconData icon,
required VoidCallback onTap,
required bool isDarkMode,
required bool isActive,
Color? backgroundColor,
}) {
return GestureDetector(
onTap: onTap,
child: Container(
width: 56,
height: 56,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: backgroundColor ?? (isDarkMode ? Colors.grey[800] : Colors.grey[200]),
border: isActive ? Border.all(color: Colors.blue, width: 2) : null,
),
child: Icon(
icon,
color: backgroundColor != null
? Colors.white
: (isDarkMode ? Colors.white : Colors.black),
size: 24,
),
),
);
}
} }

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

@ -376,7 +376,11 @@ class TranslationController extends GetxController {
} }
final recognitionStream = final recognitionStream =
await _asrService.startContinuousRecognition(_audioSourceType); await _asrService.startContinuousRecognition(_audioSourceType);
_asrService.setcontinue(); if (currentMode.value == 'faceToFace') {
_asrService.setPause();
} else {
_asrService.setcontinue();
}
isRecognizing.value = true; isRecognizing.value = true;
if (isRecording.value && isRecognizing.value) { if (isRecording.value && isRecognizing.value) {
startRecording(); startRecording();

Loading…
Cancel
Save