feat: implement real cross-platform voice recording utilizing record package with mic permission configuration
This commit is contained in:
@@ -1,4 +1,10 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<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_CONTACTS" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:label="mywhatsapp"
|
android:label="mywhatsapp"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:record/record.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
import '../services/whatsapp_service.dart';
|
import '../services/whatsapp_service.dart';
|
||||||
import '../models/conversation_model.dart';
|
import '../models/conversation_model.dart';
|
||||||
import '../models/message_model.dart';
|
import '../models/message_model.dart';
|
||||||
@@ -17,6 +21,13 @@ class ChatController extends GetxController {
|
|||||||
|
|
||||||
final inputCtrl = TextEditingController();
|
final inputCtrl = TextEditingController();
|
||||||
final scrollCtrl = ScrollController();
|
final scrollCtrl = ScrollController();
|
||||||
|
final hasText = false.obs;
|
||||||
|
|
||||||
|
// Recording State
|
||||||
|
final audioRecord = AudioRecorder();
|
||||||
|
final isRecording = false.obs;
|
||||||
|
final recordDuration = 0.obs;
|
||||||
|
Timer? _recordTimer;
|
||||||
|
|
||||||
StreamSubscription? _eventSub;
|
StreamSubscription? _eventSub;
|
||||||
|
|
||||||
@@ -32,6 +43,10 @@ class ChatController extends GetxController {
|
|||||||
Get.find<ConversationsController>().clearUnreadCount(conversation.id);
|
Get.find<ConversationsController>().clearUnreadCount(conversation.id);
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
|
inputCtrl.addListener(() {
|
||||||
|
hasText.value = inputCtrl.text.trim().isNotEmpty;
|
||||||
|
});
|
||||||
|
|
||||||
loadMessages();
|
loadMessages();
|
||||||
markAsRead();
|
markAsRead();
|
||||||
|
|
||||||
@@ -45,6 +60,8 @@ class ChatController extends GetxController {
|
|||||||
_svc.activeChatId.value = null;
|
_svc.activeChatId.value = null;
|
||||||
}
|
}
|
||||||
_eventSub?.cancel();
|
_eventSub?.cancel();
|
||||||
|
_recordTimer?.cancel();
|
||||||
|
audioRecord.dispose();
|
||||||
inputCtrl.dispose();
|
inputCtrl.dispose();
|
||||||
scrollCtrl.dispose();
|
scrollCtrl.dispose();
|
||||||
super.onClose();
|
super.onClose();
|
||||||
@@ -228,4 +245,72 @@ class ChatController extends GetxController {
|
|||||||
return DateFormat('MMMM d, yyyy').format(dt);
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,62 +139,114 @@ class ChatScreen extends StatelessWidget {
|
|||||||
color: AppTheme.surface,
|
color: AppTheme.surface,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Row(
|
child: Obx(() {
|
||||||
children: [
|
if (ctrl.isRecording.value) {
|
||||||
// Attachment button
|
return Row(
|
||||||
IconButton(
|
children: [
|
||||||
icon: const Icon(Icons.add, color: AppTheme.primary, size: 28),
|
const SizedBox(width: 12),
|
||||||
onPressed: () => _showAttachmentSheet(ctrl),
|
const Icon(Icons.fiber_manual_record, color: Colors.red, size: 16),
|
||||||
),
|
const SizedBox(width: 8),
|
||||||
// Input
|
const Text(
|
||||||
Expanded(
|
'Recording...',
|
||||||
child: TextField(
|
style: TextStyle(color: Colors.red, fontWeight: FontWeight.bold, fontSize: 14),
|
||||||
controller: ctrl.inputCtrl,
|
),
|
||||||
style: const TextStyle(color: AppTheme.textPrimary),
|
const SizedBox(width: 12),
|
||||||
maxLines: 5,
|
Text(
|
||||||
minLines: 1,
|
'${(ctrl.recordDuration.value ~/ 60).toString().padLeft(2, '0')}:${(ctrl.recordDuration.value % 60).toString().padLeft(2, '0')}',
|
||||||
textCapitalization: TextCapitalization.sentences,
|
style: const TextStyle(color: AppTheme.textPrimary, fontSize: 14, fontFamily: 'monospace'),
|
||||||
decoration: InputDecoration(
|
),
|
||||||
hintText: 'Message',
|
const Spacer(),
|
||||||
hintStyle: const TextStyle(color: AppTheme.textSecondary),
|
TextButton.icon(
|
||||||
filled: true,
|
icon: const Icon(Icons.delete, color: Colors.redAccent, size: 18),
|
||||||
fillColor: AppTheme.surfaceLight,
|
label: const Text('Cancel', style: TextStyle(color: Colors.redAccent)),
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
onPressed: ctrl.cancelRecording,
|
||||||
horizontal: 16, vertical: 10,
|
),
|
||||||
),
|
const SizedBox(width: 8),
|
||||||
border: OutlineInputBorder(
|
GestureDetector(
|
||||||
borderRadius: BorderRadius.circular(24),
|
onTap: ctrl.stopAndSendRecording,
|
||||||
borderSide: BorderSide.none,
|
child: Container(
|
||||||
|
width: 44,
|
||||||
|
height: 44,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: AppTheme.primary,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: const Icon(Icons.check, color: Colors.white, size: 20),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onSubmitted: (_) => ctrl.sendMessage(),
|
const SizedBox(width: 8),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
// Attachment button
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.add, color: AppTheme.primary, size: 28),
|
||||||
|
onPressed: () => _showAttachmentSheet(ctrl),
|
||||||
),
|
),
|
||||||
),
|
// Input
|
||||||
const SizedBox(width: 8),
|
Expanded(
|
||||||
// Send button
|
child: TextField(
|
||||||
Obx(() => GestureDetector(
|
controller: ctrl.inputCtrl,
|
||||||
onTap: ctrl.sendMessage,
|
style: const TextStyle(color: AppTheme.textPrimary),
|
||||||
child: AnimatedContainer(
|
maxLines: 5,
|
||||||
duration: const Duration(milliseconds: 200),
|
minLines: 1,
|
||||||
width: 48,
|
textCapitalization: TextCapitalization.sentences,
|
||||||
height: 48,
|
decoration: InputDecoration(
|
||||||
decoration: const BoxDecoration(
|
hintText: 'Message',
|
||||||
color: AppTheme.primary,
|
hintStyle: const TextStyle(color: AppTheme.textSecondary),
|
||||||
shape: BoxShape.circle,
|
filled: true,
|
||||||
|
fillColor: AppTheme.surfaceLight,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16, vertical: 10,
|
||||||
|
),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
borderSide: BorderSide.none,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onSubmitted: (_) => ctrl.sendMessage(),
|
||||||
),
|
),
|
||||||
child: ctrl.isSending.value
|
),
|
||||||
? const Padding(
|
const SizedBox(width: 8),
|
||||||
padding: EdgeInsets.all(12),
|
// Dynamic Send / Mic Button
|
||||||
child: CircularProgressIndicator(
|
GestureDetector(
|
||||||
strokeWidth: 2,
|
onTap: () {
|
||||||
|
if (ctrl.hasText.value) {
|
||||||
|
ctrl.sendMessage();
|
||||||
|
} else {
|
||||||
|
ctrl.startRecording();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: AppTheme.primary,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: ctrl.isSending.value
|
||||||
|
? const Padding(
|
||||||
|
padding: EdgeInsets.all(12),
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Icon(
|
||||||
|
ctrl.hasText.value ? Icons.send : Icons.mic,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
|
size: 20,
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
: const Icon(Icons.send, color: Colors.white, size: 20),
|
|
||||||
),
|
),
|
||||||
)),
|
const SizedBox(width: 4),
|
||||||
],
|
],
|
||||||
),
|
);
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include <audioplayers_linux/audioplayers_linux_plugin.h>
|
#include <audioplayers_linux/audioplayers_linux_plugin.h>
|
||||||
#include <file_selector_linux/file_selector_plugin.h>
|
#include <file_selector_linux/file_selector_plugin.h>
|
||||||
|
#include <record_linux/record_linux_plugin.h>
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar =
|
g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar =
|
||||||
@@ -16,4 +17,7 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
|||||||
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
||||||
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
file_selector_plugin_register_with_registrar(file_selector_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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
audioplayers_linux
|
audioplayers_linux
|
||||||
file_selector_linux
|
file_selector_linux
|
||||||
|
record_linux
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import file_selector_macos
|
|||||||
import firebase_core
|
import firebase_core
|
||||||
import firebase_messaging
|
import firebase_messaging
|
||||||
import flutter_local_notifications
|
import flutter_local_notifications
|
||||||
|
import record_darwin
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
import sqflite_darwin
|
import sqflite_darwin
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||||
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
|
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
|
||||||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||||
|
RecordPlugin.register(with: registry.registrar(forPlugin: "RecordPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -601,7 +601,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.9.1"
|
||||||
path_provider:
|
path_provider:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||||
@@ -680,6 +680,46 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
|
record:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: record
|
||||||
|
sha256: "2e3d56d196abcd69f1046339b75e5f3855b2406fc087e5991f6703f188aa03a6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.2.1"
|
||||||
|
record_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: record_android
|
||||||
|
sha256: "94783f08403aed33ffb68797bf0715b0812eb852f3c7985644c945faea462ba1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.1"
|
||||||
|
record_darwin:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: record_darwin
|
||||||
|
sha256: e487eccb19d82a9a39cd0126945cfc47b9986e0df211734e2788c95e3f63c82c
|
||||||
|
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: "8a81dbc4e14e1272a285bbfef6c9136d070a47d9b0d1f40aa6193516253ee2f6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.0"
|
||||||
record_use:
|
record_use:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -688,6 +728,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.0"
|
version: "0.6.0"
|
||||||
|
record_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: record_web
|
||||||
|
sha256: a12856d0b3dd03d336b4b10d7520a8b3e21649a06a8f95815318feaa8f07adbb
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.9"
|
||||||
|
record_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: record_windows
|
||||||
|
sha256: "223258060a1d25c62bae18282c16783f28581ec19401d17e56b5205b9f039d78"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.7"
|
||||||
rxdart:
|
rxdart:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ dependencies:
|
|||||||
flutter_contacts: ^1.1.7
|
flutter_contacts: ^1.1.7
|
||||||
image_picker: ^1.0.7
|
image_picker: ^1.0.7
|
||||||
audioplayers: ^6.0.0
|
audioplayers: ^6.0.0
|
||||||
|
record: ^5.1.2
|
||||||
|
path_provider: ^2.1.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <audioplayers_windows/audioplayers_windows_plugin.h>
|
#include <audioplayers_windows/audioplayers_windows_plugin.h>
|
||||||
#include <file_selector_windows/file_selector_windows.h>
|
#include <file_selector_windows/file_selector_windows.h>
|
||||||
#include <firebase_core/firebase_core_plugin_c_api.h>
|
#include <firebase_core/firebase_core_plugin_c_api.h>
|
||||||
|
#include <record_windows/record_windows_plugin_c_api.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
AudioplayersWindowsPluginRegisterWithRegistrar(
|
AudioplayersWindowsPluginRegisterWithRegistrar(
|
||||||
@@ -17,4 +18,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||||
FirebaseCorePluginCApiRegisterWithRegistrar(
|
FirebaseCorePluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
||||||
|
RecordWindowsPluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("RecordWindowsPluginCApi"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
audioplayers_windows
|
audioplayers_windows
|
||||||
file_selector_windows
|
file_selector_windows
|
||||||
firebase_core
|
firebase_core
|
||||||
|
record_windows
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
|||||||
Reference in New Issue
Block a user