diff --git a/apps/mobile/apps/client/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/apps/mobile/apps/client/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java index f3808646..e6d40294 100644 --- a/apps/mobile/apps/client/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java +++ b/apps/mobile/apps/client/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java @@ -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) { diff --git a/apps/mobile/apps/client/ios/Runner/GeneratedPluginRegistrant.m b/apps/mobile/apps/client/ios/Runner/GeneratedPluginRegistrant.m index 8b0a7da5..6edfc720 100644 --- a/apps/mobile/apps/client/ios/Runner/GeneratedPluginRegistrant.m +++ b/apps/mobile/apps/client/ios/Runner/GeneratedPluginRegistrant.m @@ -36,6 +36,12 @@ @import image_picker_ios; #endif +#if __has_include() +#import +#else +@import record_darwin; +#endif + #if __has_include() #import #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"]]; } diff --git a/apps/mobile/apps/client/linux/flutter/generated_plugin_registrant.cc b/apps/mobile/apps/client/linux/flutter/generated_plugin_registrant.cc index 7299b5cf..adc5a9fe 100644 --- a/apps/mobile/apps/client/linux/flutter/generated_plugin_registrant.cc +++ b/apps/mobile/apps/client/linux/flutter/generated_plugin_registrant.cc @@ -7,12 +7,16 @@ #include "generated_plugin_registrant.h" #include +#include #include 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); diff --git a/apps/mobile/apps/client/linux/flutter/generated_plugins.cmake b/apps/mobile/apps/client/linux/flutter/generated_plugins.cmake index 786ff5c2..1262bd6f 100644 --- a/apps/mobile/apps/client/linux/flutter/generated_plugins.cmake +++ b/apps/mobile/apps/client/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_linux + record_linux url_launcher_linux ) diff --git a/apps/mobile/apps/client/macos/Flutter/GeneratedPluginRegistrant.swift b/apps/mobile/apps/client/macos/Flutter/GeneratedPluginRegistrant.swift index 30780dc6..71798f05 100644 --- a/apps/mobile/apps/client/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/apps/mobile/apps/client/macos/Flutter/GeneratedPluginRegistrant.swift @@ -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")) } diff --git a/apps/mobile/apps/client/windows/flutter/generated_plugin_registrant.cc b/apps/mobile/apps/client/windows/flutter/generated_plugin_registrant.cc index 3a3369d4..3c9a7f78 100644 --- a/apps/mobile/apps/client/windows/flutter/generated_plugin_registrant.cc +++ b/apps/mobile/apps/client/windows/flutter/generated_plugin_registrant.cc @@ -9,6 +9,7 @@ #include #include #include +#include #include 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")); } diff --git a/apps/mobile/apps/client/windows/flutter/generated_plugins.cmake b/apps/mobile/apps/client/windows/flutter/generated_plugins.cmake index b9b24c8b..f2ab3101 100644 --- a/apps/mobile/apps/client/windows/flutter/generated_plugins.cmake +++ b/apps/mobile/apps/client/windows/flutter/generated_plugins.cmake @@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_windows firebase_auth firebase_core + record_windows url_launcher_windows ) diff --git a/apps/mobile/apps/staff/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/apps/mobile/apps/staff/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java index fbdc8215..14bcfe40 100644 --- a/apps/mobile/apps/staff/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java +++ b/apps/mobile/apps/staff/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java @@ -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) { diff --git a/apps/mobile/apps/staff/ios/Runner/GeneratedPluginRegistrant.m b/apps/mobile/apps/staff/ios/Runner/GeneratedPluginRegistrant.m index e8a688bb..7458edf0 100644 --- a/apps/mobile/apps/staff/ios/Runner/GeneratedPluginRegistrant.m +++ b/apps/mobile/apps/staff/ios/Runner/GeneratedPluginRegistrant.m @@ -54,6 +54,12 @@ @import permission_handler_apple; #endif +#if __has_include() +#import +#else +@import record_darwin; +#endif + #if __has_include() #import #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"]]; } diff --git a/apps/mobile/apps/staff/linux/flutter/generated_plugin_registrant.cc b/apps/mobile/apps/staff/linux/flutter/generated_plugin_registrant.cc index 7299b5cf..adc5a9fe 100644 --- a/apps/mobile/apps/staff/linux/flutter/generated_plugin_registrant.cc +++ b/apps/mobile/apps/staff/linux/flutter/generated_plugin_registrant.cc @@ -7,12 +7,16 @@ #include "generated_plugin_registrant.h" #include +#include #include 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); diff --git a/apps/mobile/apps/staff/linux/flutter/generated_plugins.cmake b/apps/mobile/apps/staff/linux/flutter/generated_plugins.cmake index 786ff5c2..1262bd6f 100644 --- a/apps/mobile/apps/staff/linux/flutter/generated_plugins.cmake +++ b/apps/mobile/apps/staff/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_linux + record_linux url_launcher_linux ) diff --git a/apps/mobile/apps/staff/macos/Flutter/GeneratedPluginRegistrant.swift b/apps/mobile/apps/staff/macos/Flutter/GeneratedPluginRegistrant.swift index 56b4b1e5..8c95507f 100644 --- a/apps/mobile/apps/staff/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/apps/mobile/apps/staff/macos/Flutter/GeneratedPluginRegistrant.swift @@ -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")) } diff --git a/apps/mobile/apps/staff/windows/flutter/generated_plugin_registrant.cc b/apps/mobile/apps/staff/windows/flutter/generated_plugin_registrant.cc index f06cf63c..b6746a97 100644 --- a/apps/mobile/apps/staff/windows/flutter/generated_plugin_registrant.cc +++ b/apps/mobile/apps/staff/windows/flutter/generated_plugin_registrant.cc @@ -11,6 +11,7 @@ #include #include #include +#include #include 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")); } diff --git a/apps/mobile/apps/staff/windows/flutter/generated_plugins.cmake b/apps/mobile/apps/staff/windows/flutter/generated_plugins.cmake index e3928570..589f702c 100644 --- a/apps/mobile/apps/staff/windows/flutter/generated_plugins.cmake +++ b/apps/mobile/apps/staff/windows/flutter/generated_plugins.cmake @@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST firebase_core geolocator_windows permission_handler_windows + record_windows url_launcher_windows ) diff --git a/apps/mobile/packages/core/lib/core.dart b/apps/mobile/packages/core/lib/core.dart index 3b8a44c3..d76a363f 100644 --- a/apps/mobile/packages/core/lib/core.dart +++ b/apps/mobile/packages/core/lib/core.dart @@ -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'; diff --git a/apps/mobile/packages/core/lib/src/core_module.dart b/apps/mobile/packages/core/lib/src/core_module.dart index 4759f802..3f1c9f0c 100644 --- a/apps/mobile/packages/core/lib/src/core_module.dart +++ b/apps/mobile/packages/core/lib/src/core_module.dart @@ -40,6 +40,7 @@ class CoreModule extends Module { i.addSingleton(() => CameraService(i.get())); i.addSingleton(() => GalleryService(i.get())); i.addSingleton(FilePickerService.new); + i.addSingleton(AudioRecorderService.new); i.addSingleton( () => DeviceFileUploadService( cameraService: i.get(), diff --git a/apps/mobile/packages/core/lib/src/services/device/audio/audio_recorder_service.dart b/apps/mobile/packages/core/lib/src/services/device/audio/audio_recorder_service.dart new file mode 100644 index 00000000..dd31512a --- /dev/null +++ b/apps/mobile/packages/core/lib/src/services/device/audio/audio_recorder_service.dart @@ -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 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 stopRecording() async { + return action(() async { + return await _recorder.stop(); + }); + } + + /// Checks if the recorder is currently recording. + Future isRecording() async { + return await _recorder.isRecording(); + } + + /// Disposes the recorder resources. + void dispose() { + _recorder.dispose(); + } +} diff --git a/apps/mobile/packages/core/pubspec.yaml b/apps/mobile/packages/core/pubspec.yaml index 08ec902f..f7ea1031 100644 --- a/apps/mobile/packages/core/pubspec.yaml +++ b/apps/mobile/packages/core/pubspec.yaml @@ -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 + diff --git a/apps/mobile/packages/design_system/lib/src/ui_icons.dart b/apps/mobile/packages/design_system/lib/src/ui_icons.dart index 24801c39..f9d97f3e 100644 --- a/apps/mobile/packages/design_system/lib/src/ui_icons.dart +++ b/apps/mobile/packages/design_system/lib/src/ui_icons.dart @@ -285,4 +285,7 @@ class UiIcons { /// Circle dollar icon static const IconData circleDollar = _IconLib.circleDollarSign; + + /// Microphone icon + static const IconData microphone = _IconLib.mic; } diff --git a/apps/mobile/packages/features/client/orders/create_order/lib/src/create_order_module.dart b/apps/mobile/packages/features/client/orders/create_order/lib/src/create_order_module.dart index e459dd35..dfb5784c 100644 --- a/apps/mobile/packages/features/client/orders/create_order/lib/src/create_order_module.dart +++ b/apps/mobile/packages/features/client/orders/create_order/lib/src/create_order_module.dart @@ -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 get imports => [DataConnectModule()]; + List get imports => [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.new); + i.add( + (Injector i) => RapidOrderBloc( + i.get(), + i.get(), + i.get(), + ), + ); i.add(OneTimeOrderBloc.new); i.add(PermanentOrderBloc.new); i.add(RecurringOrderBloc.new); diff --git a/apps/mobile/packages/features/client/orders/create_order/lib/src/data/repositories_impl/client_create_order_repository_impl.dart b/apps/mobile/packages/features/client/orders/create_order/lib/src/data/repositories_impl/client_create_order_repository_impl.dart index aea8a443..b0c62a14 100644 --- a/apps/mobile/packages/features/client/orders/create_order/lib/src/data/repositories_impl/client_create_order_repository_impl.dart +++ b/apps/mobile/packages/features/client/orders/create_order/lib/src/data/repositories_impl/client_create_order_repository_impl.dart @@ -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 createOneTimeOrder(domain.OneTimeOrder order) async { @@ -367,6 +372,13 @@ class ClientCreateOrderRepositoryImpl throw UnimplementedError('Rapid order IA is not connected yet.'); } + @override + Future transcribeRapidOrder(String audioPath) async { + final RapidOrderTranscriptionResponse response = await _rapidOrderService + .transcribeAudio(audioFileUri: audioPath); + return response.transcript; + } + @override Future reorder(String previousOrderId, DateTime newDate) async { // TODO: Implement reorder functionality to fetch the previous order and create a new one with the updated date. diff --git a/apps/mobile/packages/features/client/orders/create_order/lib/src/domain/repositories/client_create_order_repository_interface.dart b/apps/mobile/packages/features/client/orders/create_order/lib/src/domain/repositories/client_create_order_repository_interface.dart index a2c80cd5..d373c9ce 100644 --- a/apps/mobile/packages/features/client/orders/create_order/lib/src/domain/repositories/client_create_order_repository_interface.dart +++ b/apps/mobile/packages/features/client/orders/create_order/lib/src/domain/repositories/client_create_order_repository_interface.dart @@ -24,6 +24,11 @@ abstract interface class ClientCreateOrderRepositoryInterface { /// [description] is the text message (or transcribed voice) describing the need. Future createRapidOrder(String description); + /// Transcribes the audio file for a rapid order. + /// + /// [audioPath] is the local path to the recorded audio file. + Future transcribeRapidOrder(String audioPath); + /// Reorders an existing staffing order with a new date. /// /// [previousOrderId] is the ID of the order to reorder. diff --git a/apps/mobile/packages/features/client/orders/create_order/lib/src/domain/usecases/transcribe_rapid_order_usecase.dart b/apps/mobile/packages/features/client/orders/create_order/lib/src/domain/usecases/transcribe_rapid_order_usecase.dart new file mode 100644 index 00000000..b805ff53 --- /dev/null +++ b/apps/mobile/packages/features/client/orders/create_order/lib/src/domain/usecases/transcribe_rapid_order_usecase.dart @@ -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 call(String audioPath) async { + return _repository.transcribeRapidOrder(audioPath); + } +} diff --git a/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/blocs/rapid_order/rapid_order_bloc.dart b/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/blocs/rapid_order/rapid_order_bloc.dart index b9fdedf5..2a12d9c3 100644 --- a/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/blocs/rapid_order/rapid_order_bloc.dart +++ b/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/blocs/rapid_order/rapid_order_bloc.dart @@ -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 with BlocErrorHandler { - RapidOrderBloc(this._createRapidOrderUseCase) - : super( + RapidOrderBloc( + this._createRapidOrderUseCase, + this._transcribeRapidOrderUseCase, + this._audioRecorderService, + ) : super( const RapidOrderInitial( examples: [ '"We had a call out. Need 2 cooks ASAP"', @@ -25,6 +29,8 @@ class RapidOrderBloc extends Bloc on(_onExampleSelected); } final CreateRapidOrderUseCase _createRapidOrderUseCase; + final TranscribeRapidOrderUseCase _transcribeRapidOrderUseCase; + final AudioRecorderService _audioRecorderService; void _onMessageChanged( RapidOrderMessageChanged event, @@ -43,19 +49,31 @@ class RapidOrderBloc extends Bloc final RapidOrderInitial currentState = state as RapidOrderInitial; final bool newListeningState = !currentState.isListening; - emit(currentState.copyWith(isListening: newListeningState)); - - // Simulate voice recognition if (newListeningState) { - await Future.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 emit((state as RapidOrderInitial).copyWith(message: cleanedExample)); } } -} + @override + Future close() { + _audioRecorderService.dispose(); + return super.close(); + } +} diff --git a/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/widgets/rapid_order/rapid_order_view.dart b/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/widgets/rapid_order/rapid_order_view.dart index 08837105..a3c81660 100644 --- a/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/widgets/rapid_order/rapid_order_view.dart +++ b/apps/mobile/packages/features/client/orders/create_order/lib/src/presentation/widgets/rapid_order/rapid_order_view.dart @@ -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( context, ).add(const RapidOrderVoiceToggled()), diff --git a/apps/mobile/packages/features/client/orders/orders_common/linux/flutter/generated_plugin_registrant.cc b/apps/mobile/packages/features/client/orders/orders_common/linux/flutter/generated_plugin_registrant.cc index 64a0ecea..a81c4d4a 100644 --- a/apps/mobile/packages/features/client/orders/orders_common/linux/flutter/generated_plugin_registrant.cc +++ b/apps/mobile/packages/features/client/orders/orders_common/linux/flutter/generated_plugin_registrant.cc @@ -7,9 +7,13 @@ #include "generated_plugin_registrant.h" #include +#include 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); } diff --git a/apps/mobile/packages/features/client/orders/orders_common/linux/flutter/generated_plugins.cmake b/apps/mobile/packages/features/client/orders/orders_common/linux/flutter/generated_plugins.cmake index 2db3c22a..9609b4d7 100644 --- a/apps/mobile/packages/features/client/orders/orders_common/linux/flutter/generated_plugins.cmake +++ b/apps/mobile/packages/features/client/orders/orders_common/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_linux + record_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/apps/mobile/packages/features/client/orders/orders_common/macos/Flutter/GeneratedPluginRegistrant.swift b/apps/mobile/packages/features/client/orders/orders_common/macos/Flutter/GeneratedPluginRegistrant.swift index 8a0af98d..5cf9ab86 100644 --- a/apps/mobile/packages/features/client/orders/orders_common/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/apps/mobile/packages/features/client/orders/orders_common/macos/Flutter/GeneratedPluginRegistrant.swift @@ -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")) } diff --git a/apps/mobile/packages/features/client/orders/orders_common/windows/flutter/generated_plugin_registrant.cc b/apps/mobile/packages/features/client/orders/orders_common/windows/flutter/generated_plugin_registrant.cc index 5861e0f0..ec331e03 100644 --- a/apps/mobile/packages/features/client/orders/orders_common/windows/flutter/generated_plugin_registrant.cc +++ b/apps/mobile/packages/features/client/orders/orders_common/windows/flutter/generated_plugin_registrant.cc @@ -9,6 +9,7 @@ #include #include #include +#include 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")); } diff --git a/apps/mobile/packages/features/client/orders/orders_common/windows/flutter/generated_plugins.cmake b/apps/mobile/packages/features/client/orders/orders_common/windows/flutter/generated_plugins.cmake index ce851e9d..0125068a 100644 --- a/apps/mobile/packages/features/client/orders/orders_common/windows/flutter/generated_plugins.cmake +++ b/apps/mobile/packages/features/client/orders/orders_common/windows/flutter/generated_plugins.cmake @@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_windows firebase_auth firebase_core + record_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/apps/mobile/pubspec.lock b/apps/mobile/pubspec.lock index 07839283..f7a81d21 100644 --- a/apps/mobile/pubspec.lock +++ b/apps/mobile/pubspec.lock @@ -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: