step 6 : try audio recording and play before send to BE

This commit is contained in:
sindhu
2025-02-22 06:30:34 +07:00
parent 61fedad0ca
commit 5f1917c667
11 changed files with 238 additions and 288 deletions

View File

@@ -26,7 +26,7 @@ android {
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
// minSdk = flutter.minSdkVersion
minSdk 23
minSdk 24
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName

View File

@@ -1,5 +1,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<application
android:requestLegacyExternalStorage="true"
android:label="fluttervoice2text"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">

View File

@@ -49,5 +49,7 @@
<true/>
<key>NSCameraUsageDescription</key>
<string>This app needs camera access to scan QR codes</string>
<key>NSMicrophoneUsageDescription</key>
<string>Some message to describe why you need this permission</string>
</dict>
</plist>

View File

@@ -1,11 +1,15 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:fluttervoice2text/provider/voice_to_text_provider.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 'dart:io';
import '../../app/constant.dart';
import '../../provider/voice_to_text_provider.dart';
class RekamScreen extends HookConsumerWidget {
const RekamScreen({super.key});
@@ -21,6 +25,29 @@ class RekamScreen extends HookConsumerWidget {
final judulTombol = useState<String>("MULAI REKAM");
final qrCodeStr = useState<String>("");
final isSelesaiRekam = useState<bool>(false);
final audioPath = useState<String>("");
final recorder = useState(FlutterSoundRecorder());
final player = useState(FlutterSoundPlayer());
Future<void> requestPermissions() async {
await Permission.microphone.request();
await Permission.storage.request();
}
useEffect(() {
Future<void> initRecorder() async {
await requestPermissions();
await recorder.value.openRecorder();
await player.value.openPlayer();
}
initRecorder();
return () {
recorder.value.closeRecorder();
player.value.closePlayer();
};
}, []);
void handleBarcode(BarcodeCapture barcodes) {
if (barcodes.barcodes.isNotEmpty) {
@@ -33,164 +60,150 @@ class RekamScreen extends HookConsumerWidget {
}
}
Widget buildBarcode(Barcode? value) {
final qrCode = value?.displayValue ?? "";
Future<void> jalankanRekaman() async {
try {
final dir = await getApplicationDocumentsDirectory();
final filePath = '${dir.path}/myFile.aac';
audioPath.value = filePath;
if (qrCode.isEmpty) {
return const Text(
'Scan untuk mendapatkan QR Code',
overflow: TextOverflow.fade,
style: TextStyle(color: Colors.white),
await recorder.value.startRecorder(
toFile: filePath,
);
}
return Text(
"Info: $qrCode",
overflow: TextOverflow.fade,
style: const TextStyle(color: Colors.white),
);
judulTombol.value = "SELESAI";
isSelesaiRekam.value = true;
} catch (e) {
print("Error start record ${e.toString()}");
}
}
useEffect(() {
handleBarcode;
return () {};
}, []);
Future<void> berhentiRekaman() async {
try {
await recorder.value.stopRecorder();
isSelesaiRekam.value = false;
isRekam.value = false;
return GestureDetector(
onTap: () {
FocusManager.instance.primaryFocus!.unfocus();
},
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: () {
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: Colors.green.shade400,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
elevation: 8,
shadowColor: Constant.bgButton.withOpacity(0.24),
),
),
const Spacer(),
// button
ElevatedButton(
onPressed: (qrCodeStr.value.isEmpty || isSelesaiRekam.value)
? null
: () {
judulTombol.value = "SELESAI";
isSelesaiRekam.value = true;
},
style: ElevatedButton.styleFrom(
backgroundColor: (qrCodeStr.value.isEmpty || isSelesaiRekam.value)
? Constant.bgGrey
: Constant.bgButton,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
elevation: 8,
shadowColor: Constant.bgButton.withOpacity(0.24),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.mic,
size: Constant.getActualXPhone(
context: context,
x: 20,
),
color: Constant.textWhite,
),
SizedBox(
width: Constant.getActualXPhone(
context: context,
x: 10,
),
),
Text(
judulTombol.value,
style: Constant.titleButton500(context: context).copyWith(
color: Constant.textWhite,
),
),
],
),
),
],
),
),
appBar: AppBar(
title: Text(
'Rekam Audio',
style: Constant.titlePosisiHP(context: context),
),
automaticallyImplyLeading: false,
),
body: Padding(
padding: EdgeInsets.symmetric(
horizontal: Constant.getActualXPhone(context: context, x: 20),
),
child: Column(
children: [
// scanner
if (isRekam.value == false) ...[
SizedBox(
width: double.infinity,
height: Constant.getActualYPhone(
context: context,
y: 450,
),
child: MobileScanner(
onDetect: handleBarcode,
),
),
SizedBox(
height: Constant.getActualYPhone(
context: context,
y: 15,
),
),
],
// panggil fungsi untuk kirim ke BE
} catch (e) {
print("Error stop record ${e.toString()}");
}
}
// text
Container(
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(vertical: 10),
color: Constant.inputanGrey,
child: buildBarcode(ref.watch(barcodeX)),
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()}");
}
}
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: Colors.green.shade400,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
elevation: 8,
),
),
const Spacer(),
ElevatedButton(
onPressed: qrCodeStr.value.isEmpty
? null
: () async {
if (!isSelesaiRekam.value) {
await jalankanRekaman();
} else {
await berhentiRekaman();
}
},
style: ElevatedButton.styleFrom(
backgroundColor: qrCodeStr.value.isNotEmpty
? Constant.bgButton
: Constant.bgGrey,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
elevation: 8,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.mic, size: 20, color: Constant.textWhite),
SizedBox(width: 10),
Text(
judulTombol.value,
style: Constant.titleButton500(context: context)
.copyWith(color: Constant.textWhite),
),
],
),
),
],
),
),
appBar: AppBar(
title: Text(
'Rekam Audio',
style: Constant.titlePosisiHP(context: context),
),
automaticallyImplyLeading: false,
),
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Column(
children: [
if (!isRekam.value)
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("Info: ${qrCodeStr.value}",
style: TextStyle(color: Colors.white)),
),
SizedBox(height: 40),
ElevatedButton(onPressed: playRecording, child: Text('Play')),
],
),
),
);

View File

@@ -6,14 +6,6 @@
#include "generated_plugin_registrant.h"
#include <audioplayers_linux/audioplayers_linux_plugin.h>
#include <record_linux/record_linux_plugin.h>
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);
g_autoptr(FlPluginRegistrar) record_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "RecordLinuxPlugin");
record_linux_plugin_register_with_registrar(record_linux_registrar);
}

View File

@@ -3,8 +3,6 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_linux
record_linux
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST

View File

@@ -5,16 +5,12 @@
import FlutterMacOS
import Foundation
import audioplayers_darwin
import mobile_scanner
import path_provider_foundation
import record_darwin
import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
RecordPlugin.register(with: registry.registrar(forPlugin: "RecordPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
}

View File

@@ -9,62 +9,6 @@ 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:
@@ -145,6 +89,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.7"
etau:
dependency: transitive
description:
name: etau
sha256: "5859ee8438770e2887426dccf05bb55f2c78f20fe6db84043675155c2ebb9a7e"
url: "https://pub.dev"
source: hosted
version: "0.0.14-alpha.5"
fake_async:
dependency: transitive
description:
@@ -206,6 +158,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.6.1"
flutter_sound:
dependency: "direct main"
description:
name: flutter_sound
sha256: a4662bd1bb15a51569b908deb58a3e9ecc14c7080783487202d015644712f1ef
url: "https://pub.dev"
source: hosted
version: "9.23.1"
flutter_sound_platform_interface:
dependency: transitive
description:
name: flutter_sound_platform_interface
sha256: "0ebd302180ddc91c993d4bb061f08d71a047ebb0f04b05befd2ee8de59ea947d"
url: "https://pub.dev"
source: hosted
version: "9.23.1"
flutter_sound_web:
dependency: transitive
description:
name: flutter_sound_web
sha256: "0891d977674be94d8c3f4759c6cd11e7f29a1ed29dc287b874a1393666ac831f"
url: "https://pub.dev"
source: hosted
version: "9.23.1"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -224,14 +200,6 @@ 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:
@@ -296,6 +264,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.0"
logger:
dependency: transitive
description:
name: logger
sha256: be4b23575aac7ebf01f225a241eb7f6b5641eeaf43c6a8613510fc2f8cf187d1
url: "https://pub.dev"
source: hosted
version: "2.5.0"
matcher:
dependency: transitive
description:
@@ -328,6 +304,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.0.6"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
path:
dependency: transitive
description:
@@ -456,62 +440,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.8"
record:
dependency: "direct main"
description:
name: record
sha256: "2e3d56d196abcd69f1046339b75e5f3855b2406fc087e5991f6703f188aa03a6"
url: "https://pub.dev"
source: hosted
version: "5.2.1"
record_android:
provider:
dependency: transitive
description:
name: record_android
sha256: "36e009c3b83e034321a44a7683d95dd055162a231f95600f7da579dcc79701f9"
name: provider
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
url: "https://pub.dev"
source: hosted
version: "1.3.1"
record_darwin:
version: "6.1.2"
recase:
dependency: transitive
description:
name: record_darwin
sha256: e487eccb19d82a9a39cd0126945cfc47b9986e0df211734e2788c95e3f63c82c
name: recase
sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213
url: "https://pub.dev"
source: hosted
version: "1.2.2"
record_linux:
dependency: transitive
description:
name: record_linux
sha256: "74d41a9ebb1eb498a38e9a813dd524e8f0b4fdd627270bda9756f437b110a3e3"
url: "https://pub.dev"
source: hosted
version: "0.7.2"
record_platform_interface:
dependency: transitive
description:
name: record_platform_interface
sha256: "8a575828733d4c3cb5983c914696f40db8667eab3538d4c41c50cbb79e722ef4"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
record_web:
dependency: transitive
description:
name: record_web
sha256: ef6f5c7760f22d6785ee8d97a2133ff14cb839c65e525ad831eb7f891d83f592
url: "https://pub.dev"
source: hosted
version: "1.1.5"
record_windows:
dependency: transitive
description:
name: record_windows
sha256: "26bfebc8899f4fa5b6b044089887dc42115820cd6a907bdf40c16e909e87de0a"
url: "https://pub.dev"
source: hosted
version: "1.0.5"
version: "4.1.0"
riverpod:
dependency: transitive
description:
@@ -637,6 +581,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.3.0+3"
tau_web:
dependency: transitive
description:
name: tau_web
sha256: "81c8a0dc264f87db84678e5957dd1e4ccc015047fdea0f2cdd86d91c3c8a838e"
url: "https://pub.dev"
source: hosted
version: "0.0.14-alpha.5"
term_glyph:
dependency: transitive
description:

View File

@@ -46,10 +46,9 @@ dependencies:
path_provider: ^2.1.5
permission_handler: ^11.3.0
dropdown_button2: ^2.3.9
audioplayers: ^6.2.0
toastification: ^2.3.0
record: ^5.2.1
mobile_scanner: ^6.0.6
flutter_sound: ^9.23.1
dev_dependencies:
flutter_test:

View File

@@ -6,15 +6,9 @@
#include "generated_plugin_registrant.h"
#include <audioplayers_windows/audioplayers_windows_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <record_windows/record_windows_plugin_c_api.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
AudioplayersWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
RecordWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("RecordWindowsPluginCApi"));
}

View File

@@ -3,9 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_windows
permission_handler_windows
record_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST