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:
+30
-1
@@ -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',
|
||||||
|
|||||||
Reference in New Issue
Block a user