feat: Implement audio recording and transcription for rapid order creation across platforms.

This commit is contained in:
Achintha Isuru
2026-02-27 11:49:17 -05:00
parent 52bdf48155
commit 55a31661a1
31 changed files with 260 additions and 21 deletions

View File

@@ -50,6 +50,11 @@ public final class GeneratedPluginRegistrant {
} catch (Exception e) {
Log.e(TAG, "Error registering plugin path_provider_android, io.flutter.plugins.pathprovider.PathProviderPlugin", e);
}
try {
flutterEngine.getPlugins().add(new com.llfbandit.record.RecordPlugin());
} catch (Exception e) {
Log.e(TAG, "Error registering plugin record_android, com.llfbandit.record.RecordPlugin", e);
}
try {
flutterEngine.getPlugins().add(new io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin());
} catch (Exception e) {

View File

@@ -36,6 +36,12 @@
@import image_picker_ios;
#endif
#if __has_include(<record_darwin/RecordPlugin.h>)
#import <record_darwin/RecordPlugin.h>
#else
@import record_darwin;
#endif
#if __has_include(<shared_preferences_foundation/SharedPreferencesPlugin.h>)
#import <shared_preferences_foundation/SharedPreferencesPlugin.h>
#else
@@ -56,6 +62,7 @@
[FLTFirebaseAuthPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTFirebaseAuthPlugin"]];
[FLTFirebaseCorePlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTFirebaseCorePlugin"]];
[FLTImagePickerPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTImagePickerPlugin"]];
[RecordPlugin registerWithRegistrar:[registry registrarForPlugin:@"RecordPlugin"]];
[SharedPreferencesPlugin registerWithRegistrar:[registry registrarForPlugin:@"SharedPreferencesPlugin"]];
[URLLauncherPlugin registerWithRegistrar:[registry registrarForPlugin:@"URLLauncherPlugin"]];
}

View File

@@ -7,12 +7,16 @@
#include "generated_plugin_registrant.h"
#include <file_selector_linux/file_selector_plugin.h>
#include <record_linux/record_linux_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
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);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);

View File

@@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
file_selector_linux
record_linux
url_launcher_linux
)

View File

@@ -10,6 +10,7 @@ import file_selector_macos
import firebase_app_check
import firebase_auth
import firebase_core
import record_darwin
import shared_preferences_foundation
import url_launcher_macos
@@ -19,6 +20,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FLTFirebaseAppCheckPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAppCheckPlugin"))
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
RecordPlugin.register(with: registry.registrar(forPlugin: "RecordPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
}

View File

@@ -9,6 +9,7 @@
#include <file_selector_windows/file_selector_windows.h>
#include <firebase_auth/firebase_auth_plugin_c_api.h>
#include <firebase_core/firebase_core_plugin_c_api.h>
#include <record_windows/record_windows_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
@@ -18,6 +19,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi"));
FirebaseCorePluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
RecordWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("RecordWindowsPluginCApi"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
}

View File

@@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
file_selector_windows
firebase_auth
firebase_core
record_windows
url_launcher_windows
)

View File

@@ -65,6 +65,11 @@ public final class GeneratedPluginRegistrant {
} catch (Exception e) {
Log.e(TAG, "Error registering plugin permission_handler_android, com.baseflow.permissionhandler.PermissionHandlerPlugin", e);
}
try {
flutterEngine.getPlugins().add(new com.llfbandit.record.RecordPlugin());
} catch (Exception e) {
Log.e(TAG, "Error registering plugin record_android, com.llfbandit.record.RecordPlugin", e);
}
try {
flutterEngine.getPlugins().add(new io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin());
} catch (Exception e) {

View File

@@ -54,6 +54,12 @@
@import permission_handler_apple;
#endif
#if __has_include(<record_darwin/RecordPlugin.h>)
#import <record_darwin/RecordPlugin.h>
#else
@import record_darwin;
#endif
#if __has_include(<shared_preferences_foundation/SharedPreferencesPlugin.h>)
#import <shared_preferences_foundation/SharedPreferencesPlugin.h>
#else
@@ -77,6 +83,7 @@
[FLTGoogleMapsPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTGoogleMapsPlugin"]];
[FLTImagePickerPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTImagePickerPlugin"]];
[PermissionHandlerPlugin registerWithRegistrar:[registry registrarForPlugin:@"PermissionHandlerPlugin"]];
[RecordPlugin registerWithRegistrar:[registry registrarForPlugin:@"RecordPlugin"]];
[SharedPreferencesPlugin registerWithRegistrar:[registry registrarForPlugin:@"SharedPreferencesPlugin"]];
[URLLauncherPlugin registerWithRegistrar:[registry registrarForPlugin:@"URLLauncherPlugin"]];
}

View File

@@ -7,12 +7,16 @@
#include "generated_plugin_registrant.h"
#include <file_selector_linux/file_selector_plugin.h>
#include <record_linux/record_linux_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
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);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);

View File

@@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
file_selector_linux
record_linux
url_launcher_linux
)

View File

@@ -11,6 +11,7 @@ import firebase_app_check
import firebase_auth
import firebase_core
import geolocator_apple
import record_darwin
import shared_preferences_foundation
import url_launcher_macos
@@ -21,6 +22,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
RecordPlugin.register(with: registry.registrar(forPlugin: "RecordPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
}

View File

@@ -11,6 +11,7 @@
#include <firebase_core/firebase_core_plugin_c_api.h>
#include <geolocator_windows/geolocator_windows.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <record_windows/record_windows_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
@@ -24,6 +25,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("GeolocatorWindows"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
RecordWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("RecordWindowsPluginCApi"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
}

View File

@@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
firebase_core
geolocator_windows
permission_handler_windows
record_windows
url_launcher_windows
)

View File

@@ -31,3 +31,4 @@ export 'src/services/device/camera/camera_service.dart';
export 'src/services/device/gallery/gallery_service.dart';
export 'src/services/device/file/file_picker_service.dart';
export 'src/services/device/file_upload/device_file_upload_service.dart';
export 'src/services/device/audio/audio_recorder_service.dart';

View File

@@ -40,6 +40,7 @@ class CoreModule extends Module {
i.addSingleton<CameraService>(() => CameraService(i.get<ImagePicker>()));
i.addSingleton<GalleryService>(() => GalleryService(i.get<ImagePicker>()));
i.addSingleton<FilePickerService>(FilePickerService.new);
i.addSingleton<AudioRecorderService>(AudioRecorderService.new);
i.addSingleton<DeviceFileUploadService>(
() => DeviceFileUploadService(
cameraService: i.get<CameraService>(),

View File

@@ -0,0 +1,55 @@
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:record/record.dart';
import 'package:krow_domain/krow_domain.dart';
/// Service for recording audio using the device microphone.
class AudioRecorderService extends BaseDeviceService {
/// Creates an [AudioRecorderService].
AudioRecorderService() : _recorder = AudioRecorder();
final AudioRecorder _recorder;
/// Starts recording audio to a temporary file.
///
/// Returns the path where the audio is being recorded.
Future<void> startRecording() async {
return action(() async {
if (await _recorder.hasPermission()) {
final Directory tempDir = await getTemporaryDirectory();
final String path =
'${tempDir.path}/rapid_order_audio_${DateTime.now().millisecondsSinceEpoch}.m4a';
// Configure the recording
const RecordConfig config = RecordConfig(
encoder: AudioEncoder.aacLc, // Good balance of quality and size
bitRate: 128000,
sampleRate: 44100,
);
await _recorder.start(config, path: path);
} else {
throw Exception('Microphone permission not granted');
}
});
}
/// Stops the current recording.
///
/// Returns the path to the recorded audio file, or null if no recording was active.
Future<String?> stopRecording() async {
return action(() async {
return await _recorder.stop();
});
}
/// Checks if the recorder is currently recording.
Future<bool> isRecording() async {
return await _recorder.isRecording();
}
/// Disposes the recorder resources.
void dispose() {
_recorder.dispose();
}
}

View File

@@ -25,4 +25,6 @@ dependencies:
image_picker: ^1.1.2
path_provider: ^2.1.3
file_picker: ^8.1.7
record: ^5.2.0
firebase_auth: ^6.1.4

View File

@@ -285,4 +285,7 @@ class UiIcons {
/// Circle dollar icon
static const IconData circleDollar = _IconLib.circleDollarSign;
/// Microphone icon
static const IconData microphone = _IconLib.mic;
}

View File

@@ -9,6 +9,7 @@ import 'domain/usecases/create_permanent_order_usecase.dart';
import 'domain/usecases/create_recurring_order_usecase.dart';
import 'domain/usecases/create_rapid_order_usecase.dart';
import 'domain/usecases/get_order_details_for_reorder_usecase.dart';
import 'domain/usecases/transcribe_rapid_order_usecase.dart';
import 'presentation/blocs/index.dart';
import 'presentation/pages/create_order_page.dart';
import 'presentation/pages/one_time_order_page.dart';
@@ -23,7 +24,7 @@ import 'presentation/pages/recurring_order_page.dart';
/// presentation layer BLoCs.
class ClientCreateOrderModule extends Module {
@override
List<Module> get imports => <Module>[DataConnectModule()];
List<Module> get imports => <Module>[DataConnectModule(), CoreModule()];
@override
void binds(Injector i) {
@@ -37,10 +38,17 @@ class ClientCreateOrderModule extends Module {
i.addLazySingleton(CreatePermanentOrderUseCase.new);
i.addLazySingleton(CreateRecurringOrderUseCase.new);
i.addLazySingleton(CreateRapidOrderUseCase.new);
i.addLazySingleton(TranscribeRapidOrderUseCase.new);
i.addLazySingleton(GetOrderDetailsForReorderUseCase.new);
// BLoCs
i.add<RapidOrderBloc>(RapidOrderBloc.new);
i.add<RapidOrderBloc>(
(Injector i) => RapidOrderBloc(
i.get<CreateRapidOrderUseCase>(),
i.get<TranscribeRapidOrderUseCase>(),
i.get<AudioRecorderService>(),
),
);
i.add<OneTimeOrderBloc>(OneTimeOrderBloc.new);
i.add<PermanentOrderBloc>(PermanentOrderBloc.new);
i.add<RecurringOrderBloc>(RecurringOrderBloc.new);

View File

@@ -1,5 +1,6 @@
import 'package:firebase_data_connect/firebase_data_connect.dart';
import 'package:intl/intl.dart';
import 'package:krow_core/core.dart';
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
import 'package:krow_domain/krow_domain.dart' as domain;
import '../../domain/repositories/client_create_order_repository_interface.dart';
@@ -13,10 +14,14 @@ import '../../domain/repositories/client_create_order_repository_interface.dart'
/// on delegation and data mapping, without business logic.
class ClientCreateOrderRepositoryImpl
implements ClientCreateOrderRepositoryInterface {
ClientCreateOrderRepositoryImpl({required dc.DataConnectService service})
: _service = service;
ClientCreateOrderRepositoryImpl({
required dc.DataConnectService service,
required RapidOrderService rapidOrderService,
}) : _service = service,
_rapidOrderService = rapidOrderService;
final dc.DataConnectService _service;
final RapidOrderService _rapidOrderService;
@override
Future<void> createOneTimeOrder(domain.OneTimeOrder order) async {
@@ -367,6 +372,13 @@ class ClientCreateOrderRepositoryImpl
throw UnimplementedError('Rapid order IA is not connected yet.');
}
@override
Future<String> transcribeRapidOrder(String audioPath) async {
final RapidOrderTranscriptionResponse response = await _rapidOrderService
.transcribeAudio(audioFileUri: audioPath);
return response.transcript;
}
@override
Future<void> reorder(String previousOrderId, DateTime newDate) async {
// TODO: Implement reorder functionality to fetch the previous order and create a new one with the updated date.

View File

@@ -24,6 +24,11 @@ abstract interface class ClientCreateOrderRepositoryInterface {
/// [description] is the text message (or transcribed voice) describing the need.
Future<void> createRapidOrder(String description);
/// Transcribes the audio file for a rapid order.
///
/// [audioPath] is the local path to the recorded audio file.
Future<String> transcribeRapidOrder(String audioPath);
/// Reorders an existing staffing order with a new date.
///
/// [previousOrderId] is the ID of the order to reorder.

View File

@@ -0,0 +1,16 @@
import '../repositories/client_create_order_repository_interface.dart';
/// Use case for transcribing audio for a rapid order.
class TranscribeRapidOrderUseCase {
/// Creates a [TranscribeRapidOrderUseCase].
TranscribeRapidOrderUseCase(this._repository);
final ClientCreateOrderRepositoryInterface _repository;
/// Executes the use case.
///
/// [audioPath] is the local path to the audio file.
Future<String> call(String audioPath) async {
return _repository.transcribeRapidOrder(audioPath);
}
}

View File

@@ -1,5 +1,6 @@
import 'package:client_create_order/src/domain/arguments/rapid_order_arguments.dart';
import 'package:client_create_order/src/domain/usecases/create_rapid_order_usecase.dart';
import 'package:client_create_order/src/domain/usecases/transcribe_rapid_order_usecase.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart';
@@ -9,8 +10,11 @@ import 'rapid_order_state.dart';
/// BLoC for managing the rapid (urgent) order creation flow.
class RapidOrderBloc extends Bloc<RapidOrderEvent, RapidOrderState>
with BlocErrorHandler<RapidOrderState> {
RapidOrderBloc(this._createRapidOrderUseCase)
: super(
RapidOrderBloc(
this._createRapidOrderUseCase,
this._transcribeRapidOrderUseCase,
this._audioRecorderService,
) : super(
const RapidOrderInitial(
examples: <String>[
'"We had a call out. Need 2 cooks ASAP"',
@@ -25,6 +29,8 @@ class RapidOrderBloc extends Bloc<RapidOrderEvent, RapidOrderState>
on<RapidOrderExampleSelected>(_onExampleSelected);
}
final CreateRapidOrderUseCase _createRapidOrderUseCase;
final TranscribeRapidOrderUseCase _transcribeRapidOrderUseCase;
final AudioRecorderService _audioRecorderService;
void _onMessageChanged(
RapidOrderMessageChanged event,
@@ -43,19 +49,31 @@ class RapidOrderBloc extends Bloc<RapidOrderEvent, RapidOrderState>
final RapidOrderInitial currentState = state as RapidOrderInitial;
final bool newListeningState = !currentState.isListening;
emit(currentState.copyWith(isListening: newListeningState));
// Simulate voice recognition
if (newListeningState) {
await Future<void>.delayed(const Duration(seconds: 2));
if (state is RapidOrderInitial) {
emit(
(state as RapidOrderInitial).copyWith(
message: 'Need 2 servers for a banquet right now.',
isListening: false,
),
);
}
// Start Recording
await _audioRecorderService.startRecording();
emit(currentState.copyWith(isListening: true));
} else {
// Stop Recording and Transcribe
emit(currentState.copyWith(isListening: false));
await handleError(
emit: emit.call,
action: () async {
final String? path = await _audioRecorderService.stopRecording();
if (path != null) {
final String transcript = await _transcribeRapidOrderUseCase(
path,
);
if (state is RapidOrderInitial) {
emit(
(state as RapidOrderInitial).copyWith(message: transcript),
);
}
}
},
onError: (String errorKey) => RapidOrderFailure(errorKey),
);
}
}
}
@@ -91,5 +109,10 @@ class RapidOrderBloc extends Bloc<RapidOrderEvent, RapidOrderState>
emit((state as RapidOrderInitial).copyWith(message: cleanedExample));
}
}
}
@override
Future<void> close() {
_audioRecorderService.dispose();
return super.close();
}
}

View File

@@ -29,7 +29,7 @@ class RapidOrderView extends StatelessWidget {
title: labels.success_title,
message: labels.success_message,
buttonLabel: labels.back_to_orders,
onDone: () => Modular.to.navigate(ClientPaths.orders),
onDone: () => Modular.to.toClientOrders(),
);
}
@@ -273,7 +273,7 @@ class _RapidOrderActions extends StatelessWidget {
Expanded(
child: UiButton.secondary(
text: isListening ? labels.listening : labels.speak,
leadingIcon: UiIcons.bell, // Placeholder for mic
leadingIcon: UiIcons.microphone,
onPressed: () => BlocProvider.of<RapidOrderBloc>(
context,
).add(const RapidOrderVoiceToggled()),

View File

@@ -7,9 +7,13 @@
#include "generated_plugin_registrant.h"
#include <file_selector_linux/file_selector_plugin.h>
#include <record_linux/record_linux_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
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);
}

View File

@@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
file_selector_linux
record_linux
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST

View File

@@ -10,6 +10,7 @@ import file_selector_macos
import firebase_app_check
import firebase_auth
import firebase_core
import record_darwin
import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
@@ -18,5 +19,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FLTFirebaseAppCheckPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAppCheckPlugin"))
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
RecordPlugin.register(with: registry.registrar(forPlugin: "RecordPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
}

View File

@@ -9,6 +9,7 @@
#include <file_selector_windows/file_selector_windows.h>
#include <firebase_auth/firebase_auth_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) {
FileSelectorWindowsRegisterWithRegistrar(
@@ -17,4 +18,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi"));
FirebaseCorePluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
RecordWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("RecordWindowsPluginCApi"));
}

View File

@@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
file_selector_windows
firebase_auth
firebase_core
record_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST

View File

@@ -1221,6 +1221,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.5.0"
record:
dependency: transitive
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_web:
dependency: transitive
description:
name: record_web
sha256: "7e9846981c1f2d111d86f0ae3309071f5bba8b624d1c977316706f08fc31d16d"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
record_windows:
dependency: transitive
description:
name: record_windows
sha256: "223258060a1d25c62bae18282c16783f28581ec19401d17e56b5205b9f039d78"
url: "https://pub.dev"
source: hosted
version: "1.0.7"
rename:
dependency: transitive
description: