|
|
@ -57,6 +57,8 @@ class MobileElfController extends GetxController { |
|
|
// BLE/AI 唤醒触发的会话(非通话、非PTT模式下的唤醒) |
|
|
// BLE/AI 唤醒触发的会话(非通话、非PTT模式下的唤醒) |
|
|
bool _bleWakeupActive = false; |
|
|
bool _bleWakeupActive = false; |
|
|
final _storage = GetStorage(); |
|
|
final _storage = GetStorage(); |
|
|
|
|
|
// 滚动:layout 变化前是否已在底部 |
|
|
|
|
|
bool _atBottom = true; |
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════ |
|
|
// ═══════════════════════════════════════════════════════════════════════════ |
|
|
// 生命周期 |
|
|
// 生命周期 |
|
|
@ -172,8 +174,8 @@ class MobileElfController extends GetxController { |
|
|
|
|
|
|
|
|
case AgentServiceEventType.ttsCompleted: |
|
|
case AgentServiceEventType.ttsCompleted: |
|
|
if (!sessionActive) break; |
|
|
if (!sessionActive) break; |
|
|
_pttResultPending = false; // PTT 一轮结束 |
|
|
_pttResultPending = false; |
|
|
_bleWakeupActive = false; // BLE 唤醒一轮结束 |
|
|
_endBleWakeup(); // BLE 唤醒一轮结束,清理 native PTT 状态 |
|
|
if (_inCallMode) { |
|
|
if (_inCallMode) { |
|
|
status.value = MobileElfStatus.recording; |
|
|
status.value = MobileElfStatus.recording; |
|
|
micAlive.value = true; |
|
|
micAlive.value = true; |
|
|
@ -186,16 +188,16 @@ class MobileElfController extends GetxController { |
|
|
case AgentServiceEventType.ttsCanceled: |
|
|
case AgentServiceEventType.ttsCanceled: |
|
|
case AgentServiceEventType.ttsStopped: |
|
|
case AgentServiceEventType.ttsStopped: |
|
|
if (!sessionActive) break; |
|
|
if (!sessionActive) break; |
|
|
_pttResultPending = false; // TTS 被打断也视为结束 |
|
|
_pttResultPending = false; |
|
|
_bleWakeupActive = false; |
|
|
_endBleWakeup(); |
|
|
status.value = _inCallMode ? MobileElfStatus.recording : MobileElfStatus.idle; |
|
|
status.value = _inCallMode ? MobileElfStatus.recording : MobileElfStatus.idle; |
|
|
micAlive.value = _inCallMode; |
|
|
micAlive.value = _inCallMode; |
|
|
break; |
|
|
break; |
|
|
|
|
|
|
|
|
case AgentServiceEventType.error: |
|
|
case AgentServiceEventType.error: |
|
|
if (!sessionActive) break; |
|
|
if (!sessionActive) break; |
|
|
_pttResultPending = false; // 出错时结束等待 |
|
|
_pttResultPending = false; |
|
|
_bleWakeupActive = false; |
|
|
_endBleWakeup(); |
|
|
final msg = event.data['message']?.toString() ?? '未知错误'; |
|
|
final msg = event.data['message']?.toString() ?? '未知错误'; |
|
|
status.value = MobileElfStatus.error; |
|
|
status.value = MobileElfStatus.error; |
|
|
errorMsg.value = msg; |
|
|
errorMsg.value = msg; |
|
|
@ -305,6 +307,13 @@ class MobileElfController extends GetxController { |
|
|
if (pttRecording.value || _pttStarting) return; |
|
|
if (pttRecording.value || _pttStarting) return; |
|
|
_pttStarting = true; |
|
|
_pttStarting = true; |
|
|
_checkHeartbeat(); |
|
|
_checkHeartbeat(); |
|
|
|
|
|
// BLE 唤醒/PTT 等待结果期间用户按下 PTT,先中断当前会话(同 startCall 逻辑) |
|
|
|
|
|
if (_bleWakeupActive || _pttResultPending) { |
|
|
|
|
|
_pttResultPending = false; |
|
|
|
|
|
_bleWakeupActive = false; |
|
|
|
|
|
await AgentService.interruptCurrentResponse(); |
|
|
|
|
|
await AgentService.stopConversation(); |
|
|
|
|
|
} |
|
|
final granted = await PermissionUtil.instance.requestPermission( |
|
|
final granted = await PermissionUtil.instance.requestPermission( |
|
|
permissionType: Permission.microphone, |
|
|
permissionType: Permission.microphone, |
|
|
permissionName: '麦克风', |
|
|
permissionName: '麦克风', |
|
|
@ -386,9 +395,21 @@ class MobileElfController extends GetxController { |
|
|
_scrollToBottom(); |
|
|
_scrollToBottom(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// BLE 唤醒会话标志清除 |
|
|
|
|
|
/// Native 状态(pttRecording 等)由 StsAgent.startPtt() 的重入逻辑懒清理, |
|
|
|
|
|
/// 不在此调用 stopConversation(),避免与新会话产生竞态。 |
|
|
|
|
|
void _endBleWakeup() { |
|
|
|
|
|
_bleWakeupActive = false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
void _scrollToBottom() { |
|
|
void _scrollToBottom() { |
|
|
|
|
|
// layout 变化前捕获是否在底部,避免新内容撑开后差值变大误判为"已滚离底部" |
|
|
|
|
|
if (scrollController.hasClients) { |
|
|
|
|
|
final pos = scrollController.position; |
|
|
|
|
|
_atBottom = pos.maxScrollExtent - pos.pixels <= 80; |
|
|
|
|
|
} |
|
|
WidgetsBinding.instance.addPostFrameCallback((_) { |
|
|
WidgetsBinding.instance.addPostFrameCallback((_) { |
|
|
if (scrollController.hasClients) { |
|
|
if (scrollController.hasClients && _atBottom) { |
|
|
scrollController.animateTo( |
|
|
scrollController.animateTo( |
|
|
scrollController.position.maxScrollExtent, |
|
|
scrollController.position.maxScrollExtent, |
|
|
duration: const Duration(milliseconds: 300), |
|
|
duration: const Duration(milliseconds: 300), |
|
|
|