OTMovilGasfiter/lib/services/speech_dictation_service.dart
2026-08-02 15:50:25 -04:00

110 lines
3.3 KiB
Dart

import 'package:flutter/foundation.dart';
import 'package:speech_to_text/speech_recognition_error.dart';
import 'package:speech_to_text/speech_to_text.dart' as stt;
typedef DictationResultCallback =
void Function(String words, bool isFinalResult);
typedef DictationStatusCallback = void Function(bool isListening);
typedef DictationErrorCallback = void Function(String errorCode);
class SpeechDictationService {
SpeechDictationService._();
static final SpeechDictationService instance = SpeechDictationService._();
final stt.SpeechToText _speech = stt.SpeechToText();
DictationResultCallback? _onResult;
DictationStatusCallback? _onStatusChanged;
DictationErrorCallback? _onError;
bool get isListening => _speech.isListening;
Future<bool> initialize({
required DictationStatusCallback onStatusChanged,
required DictationErrorCallback onError,
}) async {
_onStatusChanged = onStatusChanged;
_onError = onError;
if (_speech.isAvailable) return true;
return _speech.initialize(
onStatus: _handleStatus,
onError: _handleError,
debugLogging: kDebugMode,
options: [stt.SpeechToText.androidNoBluetooth],
);
}
Future<void> listen({required DictationResultCallback onResult}) async {
_onResult = onResult;
final localeId = await _preferredSpanishLocaleId();
await _speech.listen(
onResult: (result) =>
_onResult?.call(result.recognizedWords, result.finalResult),
listenOptions: stt.SpeechListenOptions(
cancelOnError: true,
partialResults: true,
listenMode: stt.ListenMode.dictation,
autoPunctuation: true,
pauseFor: const Duration(seconds: 8),
listenFor: const Duration(minutes: 2),
localeId: localeId,
),
);
}
Future<void> stop() async {
await _speech.stop();
// Android puede entregar el resultado final poco después de informar que
// dejó de escuchar. Esta breve espera permite conservar esas últimas
// palabras antes de cerrar el receptor.
await Future<void>.delayed(const Duration(milliseconds: 300));
_onResult = null;
_onStatusChanged?.call(false);
}
Future<void> endSession() async {
_onResult = null;
_onStatusChanged = null;
_onError = null;
if (_speech.isAvailable) {
await _speech.cancel();
}
}
void _handleStatus(String status) {
if (status == stt.SpeechToText.listeningStatus) {
_onStatusChanged?.call(true);
} else if (status == stt.SpeechToText.doneStatus ||
status == stt.SpeechToText.notListeningStatus) {
_onStatusChanged?.call(false);
}
}
void _handleError(SpeechRecognitionError error) {
_onResult = null;
_onError?.call(error.errorMsg);
_onStatusChanged?.call(false);
}
Future<String?> _preferredSpanishLocaleId() async {
try {
final locales = await _speech.locales();
for (final locale in locales) {
final normalized = locale.localeId.toLowerCase().replaceAll('_', '-');
if (normalized == 'es-cl') return locale.localeId;
}
for (final locale in locales) {
if (locale.localeId.toLowerCase().startsWith('es')) {
return locale.localeId;
}
}
} catch (_) {
// El reconocedor usará el idioma predeterminado del dispositivo.
}
return null;
}
}