feat(voice): integrate one-shot voice capture into Quick Capture bar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-05 14:46:34 -04:00
parent 2231c60bfb
commit 211bf0d658
+30 -1
View File
@@ -29,6 +29,8 @@ import 'screens/settings/settings_screen.dart';
import 'screens/setup/setup_screen.dart'; import 'screens/setup/setup_screen.dart';
import 'screens/splash/splash_screen.dart'; import 'screens/splash/splash_screen.dart';
import 'screens/tasks/task_edit_screen.dart'; import 'screens/tasks/task_edit_screen.dart';
import 'providers/voice_provider.dart';
import 'widgets/voice_mic_button.dart';
// ChangeNotifier that fires when auth or server URL changes, // ChangeNotifier that fires when auth or server URL changes,
// used as GoRouter.refreshListenable so the router re-evaluates redirects // used as GoRouter.refreshListenable so the router re-evaluates redirects
@@ -399,6 +401,7 @@ class _QuickCaptureBarState extends ConsumerState<_QuickCaptureBar> {
@override @override
void dispose() { void dispose() {
_controller.dispose(); _controller.dispose();
ref.read(voiceProvider.notifier).exitVoiceMode();
super.dispose(); super.dispose();
} }
@@ -440,6 +443,25 @@ class _QuickCaptureBarState extends ConsumerState<_QuickCaptureBar> {
} }
} }
Future<void> _toggleCaptureMic() async {
final voice = ref.read(voiceProvider);
if (voice.voiceModeActive) {
ref.read(voiceProvider.notifier).exitVoiceMode();
return;
}
await ref.read(voiceProvider.notifier).enterVoiceMode(
onTranscript: (transcript) async {
ref.read(captureWorkQueueProvider.notifier).enqueue(transcript);
},
enableTts: false,
onError: (msg) {
if (!mounted) return;
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(msg)));
},
);
}
String _hintForLocation(String location) { String _hintForLocation(String location) {
if (location.startsWith(Routes.knowledge)) return 'Capture a note…'; if (location.startsWith(Routes.knowledge)) return 'Capture a note…';
if (location.startsWith(Routes.projects)) return 'Capture a note…'; if (location.startsWith(Routes.projects)) return 'Capture a note…';
@@ -482,7 +504,9 @@ class _QuickCaptureBarState extends ConsumerState<_QuickCaptureBar> {
onSubmitted: (_) => _submit(), onSubmitted: (_) => _submit(),
onChanged: (_) => setState(() {}), onChanged: (_) => setState(() {}),
decoration: InputDecoration( decoration: InputDecoration(
hintText: _hintForLocation(location), hintText: ref.watch(voiceProvider).voiceModeActive
? 'Listening…'
: _hintForLocation(location),
isDense: true, isDense: true,
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 14, vertical: 10), horizontal: 14, vertical: 10),
@@ -512,6 +536,11 @@ class _QuickCaptureBarState extends ConsumerState<_QuickCaptureBar> {
), ),
), ),
), ),
VoiceMicButton(
mode: ref.watch(voiceProvider).mode,
voiceModeActive: ref.watch(voiceProvider).voiceModeActive,
onTap: _toggleCaptureMic,
),
IconButton( IconButton(
icon: const Icon(Icons.settings_outlined), icon: const Icon(Icons.settings_outlined),
tooltip: 'Settings', tooltip: 'Settings',