feat: mvp viode

This commit is contained in:
liqupan
2026-02-03 21:41:25 +08:00
parent dec5748cca
commit 8f19377517
13 changed files with 701 additions and 31 deletions

View File

@@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/core.dart';
import 'voice_mode_overlay.dart';
import 'voice_session_controller.dart';
class InteractionScreen extends ConsumerStatefulWidget {
final String characterId;
@@ -20,6 +21,7 @@ class _InteractionScreenState extends ConsumerState<InteractionScreen> {
List<ChatMessage> _messages = [];
final TextEditingController _controller = TextEditingController();
final ScrollController _scrollController = ScrollController();
VoiceSessionController? _voiceController;
bool _isVoiceMode = false;
bool _isLoading = false;
bool _isTyping = false;
@@ -31,8 +33,10 @@ class _InteractionScreenState extends ConsumerState<InteractionScreen> {
_loadCharacterAndMessages();
}
@override
@override
void dispose() {
_voiceController?.dispose();
_controller.dispose();
_scrollController.dispose();
super.dispose();
@@ -81,6 +85,38 @@ class _InteractionScreenState extends ConsumerState<InteractionScreen> {
});
}
void _enterVoiceMode() {
FocusScope.of(context).unfocus();
_voiceController = VoiceSessionController(
character: _character!,
onUserMessage: (text) {
if (!mounted) return;
final userMsg = ChatMessage.user(text);
setState(() {
_messages = [..._messages, userMsg];
});
ChatStorageService.addMessage(widget.characterId, userMsg);
_scrollToBottom();
},
onAiMessage: (msg) {
if (!mounted) return;
setState(() {
_messages = [..._messages, msg];
});
ChatStorageService.addMessage(widget.characterId, msg);
_scrollToBottom();
},
);
setState(() => _isVoiceMode = true);
}
void _exitVoiceMode() {
_voiceController?.dispose();
_voiceController = null;
setState(() => _isVoiceMode = false);
}
Future<void> _sendMessage() async {
if (_controller.text.trim().isEmpty || _character == null || _isLoading) return;
@@ -273,10 +309,11 @@ class _InteractionScreenState extends ConsumerState<InteractionScreen> {
),
if (_isVoiceMode && _character != null)
if (_isVoiceMode && _character != null && _voiceController != null)
VoiceModeOverlay(
character: _character!,
onClose: () => setState(() => _isVoiceMode = false),
controller: _voiceController!,
onClose: _exitVoiceMode,
),
],
),
@@ -405,10 +442,7 @@ class _InteractionScreenState extends ConsumerState<InteractionScreen> {
child: Row(
children: [
GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();
setState(() => _isVoiceMode = true);
},
onTap: _enterVoiceMode,
child: Container(
width: 44,
height: 44,