import 'dart:typed_data'; import 'dart:convert'; import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_document_picker/flutter_document_picker.dart'; import 'package:flutter_sound/flutter_sound.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:http/http.dart' as http; import 'package:path_provider/path_provider.dart'; import 'dart:async'; import 'package:audio_session/audio_session.dart'; import 'package:uuid/uuid.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Scriptor', theme: ThemeData( primarySwatch: Colors.blue, ), home: AudioCapturePage(), ); } } class AudioCapturePage extends StatefulWidget { @override _AudioCapturePageState createState() => _AudioCapturePageState(); } class _AudioCapturePageState extends State { FlutterSoundRecorder audioRecorder = FlutterSoundRecorder(); late String filePath; late Timer timer; final String apiUrl = 'http://192.168.1.134:3003'; int recordingDuration = 0; bool isRecording = false; String sessionName = ''; String sessionEmail = ''; DateTime sessionDate = DateTime.now(); bool bStartSession = false; bool bStopSession = false; bool bStopSessionSaveFile = false; final tecSessionName = TextEditingController(); final tecSessionDateTime = TextEditingController(); final fkSessionCreate = GlobalKey(); final fkSessionStop = GlobalKey(); final tecSessionEmail = TextEditingController(); bool sessionSummarizeWithLLM = false; late Timer totalTimer; @override void initState() { super.initState(); initializeAudioSession(); // Initialisation de l'AudioSession requestPermissions(); } /// Initialiser et configurer 'AudioSession' Future initializeAudioSession() async { final session = await AudioSession.instance; await session.configure(AudioSessionConfiguration( avAudioSessionCategory: AVAudioSessionCategory.playAndRecord, avAudioSessionCategoryOptions: AVAudioSessionCategoryOptions.allowBluetooth | AVAudioSessionCategoryOptions.defaultToSpeaker, avAudioSessionMode: AVAudioSessionMode.spokenAudio, androidAudioAttributes: const AndroidAudioAttributes( contentType: AndroidAudioContentType.speech, flags: AndroidAudioFlags.none, usage: AndroidAudioUsage.voiceCommunication, ), androidAudioFocusGainType: AndroidAudioFocusGainType.gain, androidWillPauseWhenDucked: true, )); print("AudioSession configurée avec succès !"); } // Demande des permissions nécessaires Future requestPermissions() async { await Permission.microphone.request(); await Permission.storage.request(); var microphoneStatus = await Permission.microphone.status; var storageStatus = await Permission.storage.status; if (!microphoneStatus.isGranted || !storageStatus.isGranted) { if (kDebugMode) { print("Permissions non accordées."); } return; } else { if (kDebugMode) { print("Permissions accordées."); } } } // Démarre l'enregistrement Future startRecording() async { sessionDate = DateTime.now(); sessionName = ''; tecSessionName.text = ''; //Dialog for name and date bStartSession = false; await showDialog( context: context, builder: (context) { return AlertDialog( title: const Text('Nommer la session'), content: Form( key: fkSessionCreate, child: Column( mainAxisSize: MainAxisSize.min, children: [ TextFormField( controller: tecSessionName, decoration: const InputDecoration( hintText: 'Nom de la session', ), validator: (value) { if (value == null || value.isEmpty) { return 'Veuillez entrer un nom de session'; } return null; }, ), TextFormField( controller: tecSessionDateTime, decoration: const InputDecoration( hintText: 'Date et heure', ), onTap: () async { DateTime? date = await showDatePicker( context: context, initialDate: DateTime.now(), firstDate: DateTime(2021), lastDate: DateTime(2025), ); if (date != null) { TimeOfDay? time = await showTimePicker( context: context, initialTime: TimeOfDay.now(), ); if (time != null) { setState(() { sessionDate = DateTime( date.year, date.month, date.day, time.hour, time.minute, ); tecSessionDateTime.text = sessionDate.toString().substring(0, 19); }); } } }, validator: (value) { if (value == null || value.isEmpty) { return 'Veuillez entrer une date et une heure'; } return null; }, ), ], )), actions: [ TextButton( onPressed: () { Navigator.pop(context); }, child: const Text('Annuler'), ), TextButton( onPressed: () { if (fkSessionCreate.currentState!.validate()) { sessionName = tecSessionName.text; bStartSession = true; Navigator.pop(context); } }, child: const Text('Valider'), ), ], ); }, ); if (!bStartSession) { return; } Directory tempDir = await getTemporaryDirectory(); filePath = '${tempDir.path}/audioScriptorRecord.wav'; // Enregistrement complet // Ouvre et commence l'enregistrement pour le fichier total await audioRecorder.openRecorder(); await audioRecorder.startRecorder(toFile: filePath, codec: Codec.pcm16WAV); setState(() { isRecording = true; recordingDuration = 0; }); // Démarre un timer pour la durée totale de l'enregistrement totalTimer = Timer.periodic(const Duration(seconds: 1), (timer) { setState(() { recordingDuration++; }); }); print('Enregistrement commencé'); ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text("L'enregistrement a commencé"), duration: Duration(seconds: 1), )); } // Arrêter l'enregistrement des deux flux Future stopRecording() async { String sessionEmail = ''; bStopSession = false; sessionDate = DateTime.now(); tecSessionDateTime.text = sessionDate.toString().substring(0, 19); if (kDebugMode) { tecSessionEmail.text = 'guillaume.david@icloud.com'; } await showDialog( context: context, builder: (context) { bool summarizeWithLLM = false; return AlertDialog( title: const Text('Terminer la session'), content: Form( key: fkSessionStop, child: StatefulBuilder( builder: (context, setState) { return Column( mainAxisSize: MainAxisSize.min, children: [ TextFormField( controller: tecSessionEmail, decoration: const InputDecoration( hintText: 'Email', ), validator: (value) { if (value == null || value.isEmpty) { return 'Veuillez entrer un email'; } return null; }, ), const SizedBox(height: 20), // Checkbox avec mise à jour de l'état Row( children: [ Checkbox( value: summarizeWithLLM, onChanged: (value) { setState(() { summarizeWithLLM = value!; sessionSummarizeWithLLM = summarizeWithLLM; }); }, ), const Text('Créer un résumé'), ], ), ], ); }, ), ), actions: [ TextButton( onPressed: () { Navigator.pop(context); }, child: const Text('Annuler'), ), TextButton( onPressed: () { if (fkSessionStop.currentState!.validate()) { print('Résumé activé : $summarizeWithLLM'); bStopSession = true; Navigator.pop(context); } }, child: const Text('Valider'), ), ], ); }, ); if (!bStopSession) { return; } //Dialog for save file in local storage of phone bStopSessionSaveFile = false; await showDialog( context: context, builder: (context) { return AlertDialog( title: const Text('Sauvegarder le fichier'), content: const Text('Voulez-vous sauvegarder le fichier localement ?'), actions: [ TextButton( onPressed: () { Navigator.pop(context); }, child: const Text('Non'), ), TextButton( onPressed: () { bStopSessionSaveFile = true; Navigator.pop(context); }, child: const Text('Oui'), ), ], ); }, ); if (bStopSessionSaveFile) { try { String fileName = '$sessionName - ${sessionDate.toString().substring(0, 19)}.wav'; String localPath = await getApplicationDocumentsDirectory().then( (value) => '${value.path}/$fileName', ); File sourceFile = File(filePath); await sourceFile.copy(localPath); ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text("Le fichier a été exporté avec succès dans $localPath"), duration: Duration(seconds: 2), )); } catch (e) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text("Erreur lors de l'exportation du fichier: $e"), duration: Duration(seconds: 2), )); } setState(() { bStopSessionSaveFile = false; }); } await audioRecorder.stopRecorder(); totalTimer.cancel(); setState(() { isRecording = false; }); ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text("L'enregistrement a été arrêté"), duration: Duration(seconds: 1), )); //Envoyer le fichier final await sendFinalToAPI(filePath); setState(() { sessionName = ''; sessionEmail = ''; sessionDate = DateTime.now(); sessionSummarizeWithLLM = false; tecSessionName.text = ''; tecSessionEmail.text = ''; recordingDuration = 0; }); } Future sendFinalToAPI(String filePath) async { try { File file = File(filePath); if (!await file.exists()) { print('Le fichier n\'existe pas à l\'emplacement : $filePath'); return; } //Show taille print('Taille du fichier : ${await file.length()} octets'); var uri = Uri.parse('$apiUrl/work'); var request = http.MultipartRequest('POST', uri); request.files.add(await http.MultipartFile.fromPath('file', file.path)); // Ajouter le champ sessionId request.fields['sessionName'] = sessionName; request.fields['sessionDate'] = sessionDate.toString(); request.fields['sessionEmail'] = tecSessionEmail.text; request.fields['sessionSummarizeWithLLM'] = sessionSummarizeWithLLM.toString(); await _sendRequest(request); ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text("Le fichier a été envoyé. Vous recevrez un email."), duration: Duration(seconds: 1), )); } catch (e) { print('Erreur lors de l\'envoi du fichier : $e'); } } // Fonction pour envoyer la requête sans attendre la réponse Future _sendRequest(http.MultipartRequest request) async { try { var response = await request.send(); if (response.statusCode == 200) { var responseData = await response.stream.bytesToString(); var jsonData = jsonDecode(responseData); print("Réponse de l'API reçue et traitée."); } else { print('Erreur lors de l\'envoi à l\'API : ${response.statusCode}'); } } catch (e) { print('Erreur lors de l\'envoi du fichier : $e'); } } @override void dispose() { audioRecorder.closeRecorder(); totalTimer.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text( 'Scriptor', style: TextStyle(color: Colors.black, fontSize: 32), ), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ sessionName != '' ? Text( sessionName, style: const TextStyle(fontSize: 20), ) : const Text(''), IconButton( icon: Icon( isRecording ? Icons.stop : Icons.mic, size: 100, color: Colors.red, ), onPressed: isRecording ? stopRecording : startRecording, ), const SizedBox(height: 20), Text( '${(recordingDuration ~/ 3600).toString().padLeft(2, '0')} H ${((recordingDuration % 3600) ~/ 60).toString().padLeft(2, '0')} M ${(recordingDuration % 60).toString().padLeft(2, '0')} S', style: const TextStyle(fontSize: 20), ), const SizedBox(height: 10), //Check box Container( padding: const EdgeInsets.all(10), child: const Text( "Pour annoncer un locuteur, dites 'Hey Scripto, je suis %nom% et j'ai le rôle de %rôle%.'", textAlign: TextAlign.center, ), ) /* //Bouton opour enovyer un fichier depuis le téléphone à l'API ElevatedButton( onPressed: () async { FilePickerResult? result = await FilePicker.platform.pickFiles(); if (result != null) { String filePath = result.files.single.path!; sendFinalToAPI(filePath); } }, child: const Text('Envoyer un fichier local'), ),*/ ], ), ), ); } }