step 11 : play and pause rekaman dan slicing edit screen
This commit is contained in:
@@ -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: ""),
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user