import 'package:ffmpeg_kit_flutter_audio/ffmpeg_kit.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:fluttervoice2text/app/route.dart'; import 'package:fluttervoice2text/model/edit_voice_to_text_model.dart'; import 'package:path_provider/path_provider.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:flutter_sound/flutter_sound.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:toastification/toastification.dart'; import 'dart:io'; import '../../app/constant.dart'; import '../../provider/current_user_provider.dart'; import '../../provider/voice_to_text_provider.dart'; import '../home/list_riwayat_rekaman_provider.dart'; import 'upload_rekam_provider.dart'; class RekamScreen extends HookConsumerWidget { const RekamScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { SystemChrome.setEnabledSystemUIMode( SystemUiMode.manual, overlays: [SystemUiOverlay.bottom], ); final isRekam = useState(false); final judulTombol = useState("MULAI REKAM"); final qrCodeStr = useState("Scan QRCode untuk dapat merekam"); final isSelesaiRekam = useState(false); final audioPath = useState(""); final recorder = useState(FlutterSoundRecorder()); final player = useState(FlutterSoundPlayer()); final isLoadingUpload = useState(false); final currentUser = ref.watch(currentUserProvider); final host = currentUser?.host ?? ""; final userId = currentUser?.model.userId ?? ""; final awalan = useState("Info :"); final isPermissionsGranted = useState(false); // Fungsi untuk meminta izin dan menunggu sampai diberikan int androidVersion() { return Platform.isAndroid ? int.parse(Platform.version.split('.')[0]) : 0; } Future requestPermissions() async { final microphoneStatus = await Permission.microphone.request(); PermissionStatus storageStatus; if (Platform.isAndroid && androidVersion() >= 33) { storageStatus = await Permission.audio.request(); // Untuk Android 13+ } else { storageStatus = await Permission.storage.request(); // Untuk Android 12 ke bawah } if (microphoneStatus.isGranted && storageStatus.isGranted) { isPermissionsGranted.value = true; } } useEffect(() { Future initRecorder() async { await requestPermissions(); if (isPermissionsGranted.value) { await recorder.value.openRecorder(); await player.value.openPlayer(); } } initRecorder(); return () { recorder.value.closeRecorder(); player.value.closePlayer(); }; }, []); void handleBarcode(BarcodeCapture barcodes) { if (barcodes.barcodes.isNotEmpty) { final scannedBarcode = barcodes.barcodes.firstOrNull?.displayValue ?? ""; if (scannedBarcode.isNotEmpty) { ref.read(barcodeX.notifier).state = barcodes.barcodes.first; qrCodeStr.value = scannedBarcode; awalan.value = "QrCode : "; isRekam.value = true; } } } void showLongToast( String title, String message, String typeToast, Duration waktu, ) { if (typeToast == "success") { toastification.show( context: context, title: Text(title), description: Text(message), autoCloseDuration: waktu, type: ToastificationType.success, style: ToastificationStyle.fillColored, ); } else if (typeToast == "error") { toastification.show( context: context, title: Text(title), description: Text(message), autoCloseDuration: waktu, type: ToastificationType.error, style: ToastificationStyle.fillColored, ); } else if (typeToast == "warning") { toastification.show( context: context, title: Text(title), description: Text(message), autoCloseDuration: waktu, type: ToastificationType.warning, style: ToastificationStyle.fillColored, ); } } Future jalankanRekaman() async { try { final dir = await getApplicationDocumentsDirectory(); final filePath = '${dir.path}/myFile.aac'; // final filePath = '${dir.path}/myFile.mp3'; audioPath.value = filePath; await recorder.value.startRecorder( toFile: filePath, ); judulTombol.value = "SELESAI"; isSelesaiRekam.value = true; } catch (e) { print("Error start record ${e.toString()}"); showLongToast( 'Error', "Error start record ${e.toString()}", 'error', Duration(seconds: 3), ); } } Future convertToMp3(String inputPath) async { final dir = await getApplicationDocumentsDirectory(); final outputPath = "${dir.path}/output.mp3"; // Jalankan perintah FFmpeg untuk konversi await FFmpegKit.execute( '-i $inputPath -codec:a libmp3lame -qscale:a 2 $outputPath'); // Pastikan file MP3 berhasil dibuat if (File(outputPath).existsSync()) { return outputPath; } else { return null; } } Future berhentiRekaman() async { try { await recorder.value.stopRecorder(); isSelesaiRekam.value = false; // panggil fungsi untuk kirim ke BE if (audioPath.value.isNotEmpty) { String? mp3Path = await convertToMp3(audioPath.value); if (mp3Path != null) { // await uploadAudioFile(mp3Path); ref.read(uploadRekamProvider.notifier).uploadRekam( host: host, filePath: mp3Path, userId: userId, qrCodeStr: qrCodeStr.value, ); } else { print("Konversi gagal!"); showLongToast( 'Error', "Konversi gagal!", 'error', Duration(seconds: 3), ); } } } catch (e) { print("Error stop record ${e.toString()}"); showLongToast( 'Error', "Error stop record ${e.toString()}", 'error', Duration(seconds: 3), ); } } // list Riwayat Rekam ref.listen(listRiwayatRekamanProvider, (prev, next) { if (next is ListRiwayatRekamanStateLoading) { isLoadingUpload.value = true; } else if (next is ListRiwayatRekamanStateError) { isLoadingUpload.value = false; // errorMessage.value = next.message; showLongToast( 'Error', next.message, 'error', Duration(seconds: 3), ); } else if (next is ListRiwayatRekamanStateDone) { isLoadingUpload.value = false; Navigator.of(context).pop(); Navigator.of(context).pushNamed( editRekamRoute, ); } }); // listen upload rekam ref.listen(uploadRekamProvider, (prev, next) { if (next is UploadRekamStateLoading) { isLoadingUpload.value = true; } else if (next is UploadRekamStateError) { isLoadingUpload.value = false; // errorMessage.value = next.message; print("Err : ${next.message}"); showLongToast( 'Error', next.message, 'error', Duration(seconds: 3), ); } else if (next is UploadRekamStateDone) { ref.read(barcodeX.notifier).state = Barcode(); isLoadingUpload.value = false; ref.read(selectedVoiceIdx.notifier).state = next.model[0].Voice2text_ID; ref.read(selectedEdit.notifier).state = EditVoiceToTextModel( Voice2text_Created: next.model[0].Voice2text_Created, Voice2text_ID: next.model[0].Voice2text_ID, Voice2text_IsActive: next.model[0].Voice2text_IsActive, Voice2text_JsonData: next.model[0].Voice2text_JsonData, Voice2text_Note: next.model[0].Voice2text_Note, Voice2text_Text: next.model[0].Voice2text_Text, Voice2text_Updated: next.model[0].Voice2text_Updated, Voice2text_Url: next.model[0].Voice2text_Url, Voice2text_User_ID: next.model[0].Voice2text_User_ID, ); ref.read(listRiwayatRekamanProvider.notifier).listRiwayatRekaman( host: host, userId: userId, ); } }); // loading proses upload useEffect useEffect(() { WidgetsBinding.instance.addPostFrameCallback((timestamp) { if (isLoadingUpload.value == true) { showLongToast( 'Warning', 'Sedang Upload Rekaman', 'warning', Duration(seconds: 3), ); } }); return () {}; }, [isLoadingUpload.value]); if (!isPermissionsGranted.value) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(), SizedBox(height: 20), Text("Meminta izin..."), ], ), ), ); } return Scaffold( backgroundColor: Constant.bgGrey, bottomNavigationBar: BottomAppBar( color: Colors.blueGrey.shade100, height: Constant.getActualYPhone(context: context, y: 100), shape: const CircularNotchedRectangle(), child: Row( children: [ ElevatedButton.icon( onPressed: () { ref.read(barcodeX.notifier).state = Barcode(); isRekam.value = false; judulTombol.value = "MULAI REKAM"; qrCodeStr.value = ""; isSelesaiRekam.value = false; Navigator.of(context).pop(); }, icon: Icon(Icons.arrow_back, size: 17, color: Constant.textWhite), label: Text( 'Kembali', style: Constant.cardText(context: context) .copyWith(color: Constant.textWhite), ), style: ElevatedButton.styleFrom( backgroundColor: Constant.textBlack, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), elevation: 8, ), ), const Spacer(), ElevatedButton( onPressed: (isRekam.value == false) ? null : () async { if (!isSelesaiRekam.value) { await jalankanRekaman(); } else { await berhentiRekaman(); } }, style: ElevatedButton.styleFrom( backgroundColor: (isRekam.value == false) ? Constant.bgGrey : Constant.bgButton, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), elevation: 8, ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.mic, size: 20, color: (isRekam.value == false) ? Constant.textBlack : Constant.textWhite, ), SizedBox(width: 10), Text( judulTombol.value, style: Constant.titleButton500(context: context).copyWith( color: (isRekam.value == false) ? Constant.textBlack : Constant.textWhite, ), ), ], ), ), ], ), ), appBar: AppBar( title: Text( 'Rekam Audio', style: Constant.titlePosisiHP(context: context), ), automaticallyImplyLeading: false, ), body: Padding( padding: EdgeInsets.symmetric(horizontal: 20), child: ListView( children: [ if (awalan.value == "Info :") SizedBox( width: double.infinity, height: Constant.getActualYPhone( context: context, y: 450, ), child: MobileScanner(onDetect: handleBarcode), ), SizedBox( height: Constant.getActualYPhone( context: context, y: 20, ), ), Container( alignment: Alignment.center, padding: EdgeInsets.symmetric(vertical: 10), color: Constant.inputanGrey, child: Text( "${awalan.value} ${qrCodeStr.value}", style: TextStyle(color: Colors.white), ), ), ], ), ), ); } }