api update and flutter update
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import 'package:vibration/vibration.dart';
|
||||
|
||||
class HapticService {
|
||||
Future<bool> get _hasVibrator async => (await Vibration.hasVibrator()) ?? false;
|
||||
|
||||
Future<void> obstacleVeryClose() async {
|
||||
if (!await _hasVibrator) return;
|
||||
Vibration.vibrate(pattern: [0, 500, 100, 500, 100, 500]);
|
||||
}
|
||||
|
||||
Future<void> obstacleClose() async {
|
||||
if (!await _hasVibrator) return;
|
||||
Vibration.vibrate(pattern: [0, 300, 100, 300]);
|
||||
}
|
||||
|
||||
Future<void> obstacleMedium() async {
|
||||
if (!await _hasVibrator) return;
|
||||
Vibration.vibrate(duration: 150);
|
||||
}
|
||||
|
||||
Future<void> sosTriggered() async {
|
||||
if (!await _hasVibrator) return;
|
||||
Vibration.vibrate(pattern: [0, 1000, 200, 1000, 200, 1000]);
|
||||
}
|
||||
|
||||
Future<void> callIncoming() async {
|
||||
if (!await _hasVibrator) return;
|
||||
Vibration.vibrate(pattern: [0, 500, 500, 500, 500, 500, 500, 500]);
|
||||
}
|
||||
|
||||
Future<void> success() async {
|
||||
if (!await _hasVibrator) return;
|
||||
Vibration.vibrate(duration: 80);
|
||||
}
|
||||
|
||||
Future<void> stop() async => Vibration.cancel();
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import 'package:speech_to_text/speech_to_text.dart';
|
||||
|
||||
class SttService {
|
||||
final SpeechToText _stt = SpeechToText();
|
||||
bool _available = false;
|
||||
bool _listening = false;
|
||||
Function(String)? onResult;
|
||||
|
||||
Future<bool> init() async {
|
||||
_available = await _stt.initialize(
|
||||
onError: (e) => _onError(e),
|
||||
onStatus: (s) => _onStatus(s),
|
||||
);
|
||||
return _available;
|
||||
}
|
||||
|
||||
Future<void> startListening() async {
|
||||
if (!_available || _listening) return;
|
||||
_listening = true;
|
||||
await _stt.listen(
|
||||
onResult: (result) {
|
||||
if (result.finalResult && result.recognizedWords.isNotEmpty) {
|
||||
onResult?.call(result.recognizedWords.toLowerCase().trim());
|
||||
}
|
||||
},
|
||||
listenFor: const Duration(seconds: 10),
|
||||
pauseFor: const Duration(seconds: 3),
|
||||
localeId: 'id_ID',
|
||||
cancelOnError: false,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> stopListening() async {
|
||||
_listening = false;
|
||||
await _stt.stop();
|
||||
}
|
||||
|
||||
bool get isListening => _listening;
|
||||
bool get isAvailable => _available;
|
||||
|
||||
void _onError(dynamic error) {
|
||||
_listening = false;
|
||||
// Auto-restart setelah error
|
||||
Future.delayed(const Duration(seconds: 1), startListening);
|
||||
}
|
||||
|
||||
void _onStatus(String status) {
|
||||
if (status == 'done' || status == 'notListening') {
|
||||
_listening = false;
|
||||
// Auto-restart agar selalu mendengarkan
|
||||
Future.delayed(const Duration(milliseconds: 500), startListening);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import 'package:flutter_tts/flutter_tts.dart';
|
||||
|
||||
class TtsService {
|
||||
final FlutterTts _tts = FlutterTts();
|
||||
final List<String> _queue = [];
|
||||
bool _speaking = false;
|
||||
String _lastSpoken = '';
|
||||
|
||||
Future<void> init({String language = 'id-ID', double pitch = 1.0, double rate = 0.5}) async {
|
||||
await _tts.setLanguage(language);
|
||||
await _tts.setPitch(pitch);
|
||||
await _tts.setSpeechRate(rate);
|
||||
await _tts.setVolume(1.0);
|
||||
_tts.setCompletionHandler(() {
|
||||
_speaking = false;
|
||||
_processQueue();
|
||||
});
|
||||
}
|
||||
|
||||
/// Tambah ke antrian - tidak memotong yg sedang bicara
|
||||
void speak(String text) {
|
||||
if (text.isEmpty) return;
|
||||
_queue.add(text);
|
||||
if (!_speaking) _processQueue();
|
||||
}
|
||||
|
||||
/// Potong TTS sekarang dan langsung ucapkan ini - untuk obstacle alert
|
||||
Future<void> speakImmediate(String text) async {
|
||||
if (text.isEmpty) return;
|
||||
_queue.clear();
|
||||
await _tts.stop();
|
||||
_speaking = true;
|
||||
_lastSpoken = text;
|
||||
await _tts.speak(text);
|
||||
}
|
||||
|
||||
Future<void> stop() async {
|
||||
_queue.clear();
|
||||
_speaking = false;
|
||||
await _tts.stop();
|
||||
}
|
||||
|
||||
String get lastSpoken => _lastSpoken;
|
||||
bool get isSpeaking => _speaking;
|
||||
|
||||
Future<void> setLanguage(String lang) async => _tts.setLanguage(lang);
|
||||
Future<void> setPitch(double pitch) async => _tts.setPitch(pitch);
|
||||
Future<void> setRate(double rate) async => _tts.setSpeechRate(rate);
|
||||
|
||||
void repeatLast() {
|
||||
if (_lastSpoken.isNotEmpty) speak(_lastSpoken);
|
||||
}
|
||||
|
||||
void _processQueue() {
|
||||
if (_queue.isEmpty || _speaking) return;
|
||||
final text = _queue.removeAt(0);
|
||||
_speaking = true;
|
||||
_lastSpoken = text;
|
||||
_tts.speak(text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'tts_service.dart';
|
||||
import 'stt_service.dart';
|
||||
|
||||
enum VoiceCommandKey {
|
||||
openWalkguide, startWalkguide, stopWalkguide,
|
||||
callGuardian, openNotification, readAllNotif,
|
||||
openSos, sendSos, whereAmI,
|
||||
openActivity, openNavigation, openSettings,
|
||||
repeatLast, stopTts,
|
||||
}
|
||||
|
||||
class VoiceCommand {
|
||||
final VoiceCommandKey key;
|
||||
final String phrase;
|
||||
final bool enabled;
|
||||
const VoiceCommand({required this.key, required this.phrase, required this.enabled});
|
||||
}
|
||||
|
||||
/// Callback yang dipanggil saat command terdeteksi
|
||||
/// Registered oleh router/screen yang relevan
|
||||
typedef CommandCallback = void Function(VoiceCommandKey key);
|
||||
|
||||
class VoiceCommandHandler {
|
||||
final SttService _stt;
|
||||
final TtsService _tts;
|
||||
|
||||
List<VoiceCommand> _commands = [];
|
||||
CommandCallback? onCommand;
|
||||
|
||||
VoiceCommandHandler(this._stt, this._tts);
|
||||
|
||||
void loadCommands(List<VoiceCommand> commands) {
|
||||
_commands = commands;
|
||||
_stt.onResult = _processText;
|
||||
}
|
||||
|
||||
void loadDefaultCommands() {
|
||||
_commands = const [
|
||||
VoiceCommand(key: VoiceCommandKey.openWalkguide, phrase: 'open walkguide', enabled: true),
|
||||
VoiceCommand(key: VoiceCommandKey.startWalkguide, phrase: 'start walkguide', enabled: true),
|
||||
VoiceCommand(key: VoiceCommandKey.stopWalkguide, phrase: 'stop walkguide', enabled: true),
|
||||
VoiceCommand(key: VoiceCommandKey.callGuardian, phrase: 'call guardian', enabled: true),
|
||||
VoiceCommand(key: VoiceCommandKey.openNotification, phrase: 'open notifications', enabled: true),
|
||||
VoiceCommand(key: VoiceCommandKey.readAllNotif, phrase: 'read all my notifications', enabled: true),
|
||||
VoiceCommand(key: VoiceCommandKey.openSos, phrase: 'open sos', enabled: true),
|
||||
VoiceCommand(key: VoiceCommandKey.sendSos, phrase: 'send sos', enabled: true),
|
||||
VoiceCommand(key: VoiceCommandKey.whereAmI, phrase: 'where am i', enabled: true),
|
||||
VoiceCommand(key: VoiceCommandKey.openActivity, phrase: 'open activity log', enabled: true),
|
||||
VoiceCommand(key: VoiceCommandKey.openNavigation, phrase: 'open navigation', enabled: true),
|
||||
VoiceCommand(key: VoiceCommandKey.openSettings, phrase: 'open settings', enabled: true),
|
||||
VoiceCommand(key: VoiceCommandKey.repeatLast, phrase: 'repeat', enabled: true),
|
||||
VoiceCommand(key: VoiceCommandKey.stopTts, phrase: 'stop', enabled: true),
|
||||
];
|
||||
_stt.onResult = _processText;
|
||||
}
|
||||
|
||||
void _processText(String text) {
|
||||
final lower = text.toLowerCase().trim();
|
||||
for (final cmd in _commands) {
|
||||
if (!cmd.enabled) continue;
|
||||
if (lower.contains(cmd.phrase.toLowerCase())) {
|
||||
_handleCommand(cmd.key);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _handleCommand(VoiceCommandKey key) {
|
||||
onCommand?.call(key);
|
||||
// Built-in actions for TTS-only commands
|
||||
if (key == VoiceCommandKey.repeatLast) _tts.repeatLast();
|
||||
if (key == VoiceCommandKey.stopTts) _tts.stop();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user