diff --git a/lib/provider/voice_to_text_provider.dart b/lib/provider/voice_to_text_provider.dart index 101300a..4f765f3 100644 --- a/lib/provider/voice_to_text_provider.dart +++ b/lib/provider/voice_to_text_provider.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import '../../model/edit_voice_to_text_model.dart'; @@ -25,4 +26,12 @@ final selectedEdit = StateProvider( // final barcodeX = StateProvider( (ref) => Barcode(), -); \ No newline at end of file +); + +final noteCtr = StateProvider( + (ref) => TextEditingController(text: ""), +); + +final textCtr = StateProvider( + (ref) => TextEditingController(text: ""), +); diff --git a/lib/screen/home/card_riwayat_rekaman.dart b/lib/screen/home/card_riwayat_rekaman.dart index 8c05588..021e2ec 100644 --- a/lib/screen/home/card_riwayat_rekaman.dart +++ b/lib/screen/home/card_riwayat_rekaman.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:fluttervoice2text/app/route.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../model/edit_voice_to_text_model.dart'; import '../../model/voice_to_text_model.dart'; @@ -62,9 +63,9 @@ class CardRiwayatRekaman extends HookConsumerWidget { Voice2text_Updated: data.Voice2text_Updated, Voice2text_User_ID: data.Voice2text_User_ID, ); - // Navigator.of(context).pushNamed( - // editRekamanRoute, - // ); + Navigator.of(context).pushNamed( + editRekamRoute, + ); }, child: Icon( Icons.edit, diff --git a/lib/screen/rekaman/edit_rekam_screen.dart b/lib/screen/rekaman/edit_rekam_screen.dart index f3426f8..6f8e21a 100644 --- a/lib/screen/rekaman/edit_rekam_screen.dart +++ b/lib/screen/rekaman/edit_rekam_screen.dart @@ -1,13 +1,115 @@ +import 'package:audioplayers/audioplayers.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:toastification/toastification.dart'; import '../../app/constant.dart'; +import '../../provider/current_user_provider.dart'; +import '../../provider/voice_to_text_provider.dart'; class EditRekamScreen extends HookConsumerWidget { const EditRekamScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { + SystemChrome.setEnabledSystemUIMode( + SystemUiMode.manual, + overlays: [SystemUiOverlay.bottom], + ); + + final currentUser = ref.watch(currentUserProvider); + final host = currentUser?.host ?? ""; + final userId = currentUser?.model.userId ?? ""; + final qrCodeStr = useState(""); + final editData = ref.watch(selectedEdit); + final baseUrl = "https://$host/"; + final player = useState(AudioPlayer()); + final urlRekaman = baseUrl + editData.Voice2text_Url; + final isPlaying = useState(false); + + useEffect(() { + WidgetsBinding.instance.addPostFrameCallback((timestamp) { + ref.read(noteCtr.notifier).state = TextEditingController( + text: editData.Voice2text_Note, + ); + ref.read(textCtr.notifier).state = TextEditingController( + text: editData.Voice2text_Text, + ); + String afterVoice = editData.Voice2text_Url.split("voice-")[1]; + String qrCodeExtract = afterVoice.split(".mp3")[0]; + qrCodeStr.value = "Qrcode : $qrCodeExtract"; + }); + return () {}; + }, []); + + 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 playRekaman() async { + if (urlRekaman.isNotEmpty) { + try { + if (isPlaying.value) { + await player.value.pause(); // Pause jika sedang diputar + } else { + await player.value.setSourceUrl(urlRekaman); + await player.value.resume(); // Putar jika tidak sedang diputar + } + isPlaying.value = !isPlaying.value; + } catch (e) { + print("Error saat memutar rekaman: ${e.toString()}"); + showLongToast( + 'Error', + "Error saat memutar rekaman: ${e.toString()}", + 'error', + Duration(seconds: 3), + ); + } + } else { + print("URL rekaman kosong"); + showLongToast( + 'Error', + "URL rekaman kosong", + 'error', + Duration(seconds: 3), + ); + } + } + return GestureDetector( onTap: () { FocusManager.instance.primaryFocus!.unfocus(); @@ -15,6 +117,56 @@ class EditRekamScreen extends HookConsumerWidget { child: Scaffold( resizeToAvoidBottomInset: true, 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: () { + 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: playRekaman, + style: ElevatedButton.styleFrom( + backgroundColor: Constant.bgButton, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + elevation: 8, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'SAVE', + style: Constant.titleButton500(context: context).copyWith( + color: Constant.textWhite, + ), + ), + ], + ), + ), + ], + ), + ), body: Column( children: [ // atas @@ -23,6 +175,125 @@ class EditRekamScreen extends HookConsumerWidget { width: double.infinity, fit: BoxFit.cover, ), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 50, + ), + ), + // qrcode + Padding( + padding: EdgeInsets.symmetric( + horizontal: 20, + ), + child: Container( + alignment: Alignment.center, + padding: EdgeInsets.symmetric(vertical: 10), + color: Constant.inputanGrey, + child: Text( + qrCodeStr.value, + style: TextStyle(color: Colors.white), + ), + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 20, + ), + ), + // button play record + Padding( + padding: EdgeInsets.symmetric( + horizontal: 20, + ), + child: SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: playRekaman, + style: ElevatedButton.styleFrom( + backgroundColor: Constant.textRed, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + elevation: 8, + shadowColor: Constant.bgButton.withOpacity(0.24), + ), + child: Text( + isPlaying.value ? 'STOP' : 'PUTAR REKAMAN', + style: Constant.titleButton500(context: context).copyWith( + color: Constant.textWhite, + ), + ), + ), + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 20, + ), + ), + // note + Padding( + padding: EdgeInsets.symmetric( + horizontal: 20, + ), + child: TextField( + controller: ref.read(noteCtr), + maxLines: null, + keyboardType: TextInputType.multiline, + decoration: InputDecoration( + label: Text('Note'), + hintText: "Note", + enabledBorder: const OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey, + width: 1, + ), + ), + focusedBorder: const OutlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + width: 2, + ), + ), + ), + ), + ), + SizedBox( + height: Constant.getActualYPhone( + context: context, + y: 20, + ), + ), + // hasil transcribe + Padding( + padding: EdgeInsets.symmetric( + horizontal: 20, + ), + child: TextField( + controller: ref.read(textCtr), + maxLines: null, + keyboardType: TextInputType.multiline, + decoration: InputDecoration( + label: Text('Text'), + hintText: "Text", + enabledBorder: const OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey, + width: 1, + ), + ), + focusedBorder: const OutlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + width: 2, + ), + ), + ), + ), + ), ], ), ), diff --git a/lib/screen/rekaman/rekam_screen.dart b/lib/screen/rekaman/rekam_screen.dart index 577c61b..7c94991 100644 --- a/lib/screen/rekaman/rekam_screen.dart +++ b/lib/screen/rekaman/rekam_screen.dart @@ -84,78 +84,6 @@ class RekamScreen extends HookConsumerWidget { } } - 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()}"); - } - } - - 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 playRecording() async { - try { - if (audioPath.value.isNotEmpty && File(audioPath.value).existsSync()) { - await player.value.startPlayer(fromURI: audioPath.value); - } else { - print("Error: Path rekaman kosong atau tidak ditemukan."); - } - } catch (e) { - print("Error saat memutar rekaman: ${e.toString()}"); - } - } - - 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, - ); - print('mp3 convert $mp3Path'); - } else { - print("Konversi gagal!"); - } - } - } catch (e) { - print("Error stop record ${e.toString()}"); - } - } - void showLongToast( String title, String message, @@ -192,6 +120,83 @@ class RekamScreen extends HookConsumerWidget { } } + 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) { @@ -307,7 +312,7 @@ class RekamScreen extends HookConsumerWidget { .copyWith(color: Constant.textWhite), ), style: ElevatedButton.styleFrom( - backgroundColor: Colors.green.shade400, + backgroundColor: Constant.textBlack, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), @@ -370,7 +375,7 @@ class RekamScreen extends HookConsumerWidget { padding: EdgeInsets.symmetric(horizontal: 20), child: Column( children: [ - if (awalan.value == "Info :") + if (awalan.value == "Info :") SizedBox( width: double.infinity, height: Constant.getActualYPhone( diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e71a16d..1830e5c 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin"); + audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2e1de87..e9abb91 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 3fbb018..60f77d2 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,12 +5,14 @@ import FlutterMacOS import Foundation +import audioplayers_darwin import ffmpeg_kit_flutter_audio import mobile_scanner import path_provider_foundation import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) FFmpegKitFlutterPlugin.register(with: registry.registrar(forPlugin: "FFmpegKitFlutterPlugin")) MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 2bca9f2..c105f33 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -9,6 +9,62 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + audioplayers: + dependency: "direct main" + description: + name: audioplayers + sha256: b3d02a23918b980073d4a76c4e5659eaf5127251ca91a6a804869ba88ef1c8bf + url: "https://pub.dev" + source: hosted + version: "6.2.0" + audioplayers_android: + dependency: transitive + description: + name: audioplayers_android + sha256: "56830454da1a943f33c686cdbfca52a8d24f4c6fd6ce88c6b3501020dbaaf48b" + url: "https://pub.dev" + source: hosted + version: "5.0.3" + audioplayers_darwin: + dependency: transitive + description: + name: audioplayers_darwin + sha256: "34e1fb3a88ba02566615c6ca7173aa93b39cf61a38a05a729b60ba14c4899169" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + audioplayers_linux: + dependency: transitive + description: + name: audioplayers_linux + sha256: ce8383c1ff0db1ba93b5b0b8ef5b260ec2d0ce2598ad37dc8b946b773dd2c5fa + url: "https://pub.dev" + source: hosted + version: "4.1.0" + audioplayers_platform_interface: + dependency: transitive + description: + name: audioplayers_platform_interface + sha256: "6834dd48dfb7bc6c2404998ebdd161f79cd3774a7e6779e1348d54a3bfdcfaa5" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + audioplayers_web: + dependency: transitive + description: + name: audioplayers_web + sha256: "3609bdf0e05e66a3d9750ee40b1e37f2a622c4edb796cc600b53a90a30a2ace4" + url: "https://pub.dev" + source: hosted + version: "5.0.1" + audioplayers_windows: + dependency: transitive + description: + name: audioplayers_windows + sha256: "52b57c706a20fc85daa911be67381b3a5c9d03f8e27cb9fdb226de5bddf293b1" + url: "https://pub.dev" + source: hosted + version: "4.1.0" boolean_selector: dependency: transitive description: @@ -216,6 +272,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.6.1" + http: + dependency: transitive + description: + name: http + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + url: "https://pub.dev" + source: hosted + version: "1.3.0" http_parser: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 64dbae7..bf31470 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -50,6 +50,7 @@ dependencies: mobile_scanner: ^6.0.6 flutter_sound: ^9.23.1 ffmpeg_kit_flutter_audio: ^6.0.3 + audioplayers: ^6.2.0 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 48de52b..3d58508 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,9 +6,12 @@ #include "generated_plugin_registrant.h" +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + AudioplayersWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 0e69e40..c984fc8 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_windows permission_handler_windows )