step 11 : play and pause rekaman dan slicing edit screen

This commit is contained in:
sindhu
2025-02-22 12:37:52 +07:00
parent c9cc76fbe7
commit 1206d7de24
11 changed files with 440 additions and 78 deletions

View File

@@ -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<EditVoiceToTextModel>(
//
final barcodeX = StateProvider<Barcode>(
(ref) => Barcode(),
);
);
final noteCtr = StateProvider<TextEditingController>(
(ref) => TextEditingController(text: ""),
);
final textCtr = StateProvider<TextEditingController>(
(ref) => TextEditingController(text: ""),
);

View File

@@ -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,

View File

@@ -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<void> 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,
),
),
),
),
),
],
),
),

View File

@@ -84,78 +84,6 @@ class RekamScreen extends HookConsumerWidget {
}
}
Future<void> 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<String?> 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<void> 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<void> 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<void> 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<String?> 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<void> 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(