feat: implement real cross-platform voice recording utilizing record package with mic permission configuration

This commit is contained in:
Hamza-Ayed
2026-05-18 17:32:31 +03:00
parent e18f4195b9
commit c1b149cc21
10 changed files with 262 additions and 50 deletions

View File

@@ -1,7 +1,11 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:record/record.dart';
import 'package:path_provider/path_provider.dart';
import '../services/whatsapp_service.dart';
import '../models/conversation_model.dart';
import '../models/message_model.dart';
@@ -17,6 +21,13 @@ class ChatController extends GetxController {
final inputCtrl = TextEditingController();
final scrollCtrl = ScrollController();
final hasText = false.obs;
// Recording State
final audioRecord = AudioRecorder();
final isRecording = false.obs;
final recordDuration = 0.obs;
Timer? _recordTimer;
StreamSubscription? _eventSub;
@@ -32,6 +43,10 @@ class ChatController extends GetxController {
Get.find<ConversationsController>().clearUnreadCount(conversation.id);
} catch (_) {}
inputCtrl.addListener(() {
hasText.value = inputCtrl.text.trim().isNotEmpty;
});
loadMessages();
markAsRead();
@@ -45,6 +60,8 @@ class ChatController extends GetxController {
_svc.activeChatId.value = null;
}
_eventSub?.cancel();
_recordTimer?.cancel();
audioRecord.dispose();
inputCtrl.dispose();
scrollCtrl.dispose();
super.onClose();
@@ -228,4 +245,72 @@ class ChatController extends GetxController {
return DateFormat('MMMM d, yyyy').format(dt);
}
}
// ── Audio Recording Engine ───────────────────────────────────────────────
Future<void> startRecording() async {
try {
if (await audioRecord.hasPermission()) {
final tempDir = await getTemporaryDirectory();
final path = '${tempDir.path}/rec_${DateTime.now().millisecondsSinceEpoch}.m4a';
await audioRecord.start(
const RecordConfig(encoder: AudioEncoder.aacLc),
path: path,
);
recordDuration.value = 0;
isRecording.value = true;
_recordTimer?.cancel();
_recordTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
recordDuration.value++;
});
} else {
Get.snackbar(
'Permission Denied',
'Microphone permission is required to record voice notes.',
snackPosition: SnackPosition.BOTTOM,
backgroundColor: Colors.redAccent.withOpacity(0.8),
colorText: Colors.white,
);
}
} catch (e) {
print('[START RECORDING ERROR] $e');
}
}
Future<void> stopAndSendRecording() async {
try {
_recordTimer?.cancel();
final path = await audioRecord.stop();
isRecording.value = false;
if (path != null && recordDuration.value > 0) {
final file = File(path);
if (await file.exists()) {
final bytes = await file.readAsBytes();
final base64String = base64Encode(bytes);
await sendMediaMessage(
base64String,
'audio/mp4', // Recorded as M4A (AAC), perfect for all platforms natively!
'voice_note.m4a',
);
}
}
} catch (e) {
print('[STOP RECORDING ERROR] $e');
}
}
Future<void> cancelRecording() async {
try {
_recordTimer?.cancel();
await audioRecord.stop();
isRecording.value = false;
recordDuration.value = 0;
} catch (e) {
print('[CANCEL RECORDING ERROR] $e');
}
}
}