feat: Ajout de la gestion des maintenances et intégration de la synthèse vocale
This commit is contained in:
@@ -1,25 +1,36 @@
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:em2rp/utils/debug_log.dart';
|
||||
|
||||
/// Service pour émettre des feedbacks sonores lors des interactions
|
||||
class AudioFeedbackService {
|
||||
/// Jouer un son de succès (clic système)
|
||||
static final AudioPlayer _player = AudioPlayer();
|
||||
|
||||
/// Jouer un son de succès
|
||||
static Future<void> playSuccessBeep() async {
|
||||
try {
|
||||
// Jouer un son système
|
||||
await HapticFeedback.mediumImpact();
|
||||
await SystemSound.play(SystemSoundType.click);
|
||||
|
||||
// Alternative : jouer un son personnalisé si disponible
|
||||
// await _player.play(AssetSource('sounds/success.mp3'));
|
||||
} catch (e) {
|
||||
DebugLog.error('[AudioFeedbackService] Error playing success beep', e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Jouer un son d'erreur (alerte système)
|
||||
/// Jouer un son d'erreur
|
||||
static Future<void> playErrorBeep() async {
|
||||
try {
|
||||
// Note: SystemSoundType.alert n'existe pas sur toutes les plateformes
|
||||
// On utilise click pour l'instant, peut être amélioré avec audioplayers
|
||||
// Double bip pour indiquer une erreur
|
||||
await HapticFeedback.heavyImpact();
|
||||
await SystemSound.play(SystemSoundType.click);
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
await SystemSound.play(SystemSoundType.click);
|
||||
|
||||
// Alternative : jouer un son d'erreur personnalisé si disponible
|
||||
// await _player.play(AssetSource('sounds/error.mp3'));
|
||||
} catch (e) {
|
||||
DebugLog.error('[AudioFeedbackService] Error playing error beep', e);
|
||||
}
|
||||
@@ -36,11 +47,15 @@ class AudioFeedbackService {
|
||||
|
||||
/// Jouer un feedback complet (son + vibration)
|
||||
static Future<void> playFullFeedback({bool isSuccess = true}) async {
|
||||
await playHapticFeedback();
|
||||
if (isSuccess) {
|
||||
await playSuccessBeep();
|
||||
} else {
|
||||
await playErrorBeep();
|
||||
}
|
||||
}
|
||||
|
||||
/// Nettoyer les ressources
|
||||
static Future<void> dispose() async {
|
||||
await _player.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,7 +286,7 @@ class PDFService {
|
||||
final pageItems = items.skip(pageStart).take(config.itemsPerPage).toList();
|
||||
final pageQRs = qrImages.skip(pageStart).take(config.itemsPerPage).toList();
|
||||
|
||||
pdf.addPage(
|
||||
pdf.addPage(
|
||||
pw.Page(
|
||||
pageFormat: PdfPageFormat.a4,
|
||||
margin: pw.EdgeInsets.zero,
|
||||
@@ -299,10 +299,20 @@ class PDFService {
|
||||
runSpacing: 0, // 0 espace entre les lignes
|
||||
children: List.generate(pageItems.length, (i) {
|
||||
final item = pageItems[i];
|
||||
// Déterminer si c'est la première colonne (indices pairs)
|
||||
final bool isFirstColumn = (i % 2) == 0;
|
||||
// Décalage de 2mm pour la première colonne
|
||||
final double leftPadding = isFirstColumn ? 8.0 : 6.0; // 6 + 2mm
|
||||
|
||||
return pw.Container(
|
||||
width: labelWidth,
|
||||
height: labelHeight,
|
||||
padding: const pw.EdgeInsets.all(6),
|
||||
padding: pw.EdgeInsets.only(
|
||||
left: leftPadding,
|
||||
right: 6,
|
||||
top: 6,
|
||||
bottom: 6,
|
||||
),
|
||||
// Suppression de la décoration (bordure)
|
||||
child: pw.Row(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.center,
|
||||
|
||||
104
em2rp/lib/services/text_to_speech_service.dart
Normal file
104
em2rp/lib/services/text_to_speech_service.dart
Normal file
@@ -0,0 +1,104 @@
|
||||
import 'package:flutter_tts/flutter_tts.dart';
|
||||
import 'package:em2rp/utils/debug_log.dart';
|
||||
|
||||
/// Service de synthèse vocale pour lire des textes à haute voix
|
||||
class TextToSpeechService {
|
||||
static final FlutterTts _tts = FlutterTts();
|
||||
static bool _isInitialized = false;
|
||||
|
||||
/// Initialiser le service TTS
|
||||
static Future<void> initialize() async {
|
||||
if (_isInitialized) return;
|
||||
|
||||
try {
|
||||
await _tts.setLanguage('fr-FR');
|
||||
await _tts.setSpeechRate(0.7); // Vitesse normale
|
||||
await _tts.setVolume(1.0);
|
||||
await _tts.setPitch(0.7); // Pitch plus bas pour une voix masculine
|
||||
|
||||
// Tenter de sélectionner une voix masculine si disponible
|
||||
try {
|
||||
final voices = await _tts.getVoices;
|
||||
if (voices != null && voices is List) {
|
||||
// Chercher une voix française masculine
|
||||
final maleVoice = voices.firstWhere(
|
||||
(voice) {
|
||||
final voiceMap = voice as Map;
|
||||
final name = voiceMap['name']?.toString().toLowerCase() ?? '';
|
||||
final locale = voiceMap['locale']?.toString().toLowerCase() ?? '';
|
||||
|
||||
// Rechercher des voix françaises masculines
|
||||
return locale.startsWith('fr') &&
|
||||
(name.contains('male') || name.contains('homme') ||
|
||||
name.contains('thomas') || name.contains('paul'));
|
||||
},
|
||||
orElse: () => null,
|
||||
);
|
||||
|
||||
if (maleVoice != null) {
|
||||
final voiceMap = maleVoice as Map;
|
||||
await _tts.setVoice({
|
||||
'name': voiceMap['name'],
|
||||
'locale': voiceMap['locale'],
|
||||
});
|
||||
DebugLog.info('[TextToSpeechService] Voix masculine sélectionnée: ${voiceMap['name']}');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
DebugLog.info('[TextToSpeechService] Impossible de sélectionner une voix spécifique, utilisation de la voix par défaut');
|
||||
}
|
||||
|
||||
_isInitialized = true;
|
||||
DebugLog.info('[TextToSpeechService] Service initialisé avec voix masculine');
|
||||
} catch (e) {
|
||||
DebugLog.error('[TextToSpeechService] Erreur lors de l\'initialisation', e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Lire un texte à haute voix
|
||||
static Future<void> speak(String text) async {
|
||||
if (!_isInitialized) {
|
||||
await initialize();
|
||||
}
|
||||
|
||||
try {
|
||||
// Arrêter toute lecture en cours
|
||||
await _tts.stop();
|
||||
|
||||
// Lire le nouveau texte
|
||||
await _tts.speak(text);
|
||||
DebugLog.info('[TextToSpeechService] Lecture: $text');
|
||||
} catch (e) {
|
||||
DebugLog.error('[TextToSpeechService] Erreur lors de la lecture', e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Arrêter la lecture en cours
|
||||
static Future<void> stop() async {
|
||||
try {
|
||||
await _tts.stop();
|
||||
} catch (e) {
|
||||
DebugLog.error('[TextToSpeechService] Erreur lors de l\'arrêt', e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifier si le service est en train de lire
|
||||
static Future<bool> isSpeaking() async {
|
||||
try {
|
||||
// FlutterTts ne fournit pas directement cette info, on retourne false par défaut
|
||||
return false;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Nettoyer les ressources
|
||||
static Future<void> dispose() async {
|
||||
try {
|
||||
await _tts.stop();
|
||||
} catch (e) {
|
||||
DebugLog.error('[TextToSpeechService] Erreur lors du nettoyage', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user