feat: implement real cross-platform voice recording utilizing record package with mic permission configuration
This commit is contained in:
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user