diff --git a/analysis_options.yaml b/analysis_options.yaml index 601622e..b06f1fe 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -3,6 +3,7 @@ include: package:flutter_lints/flutter.yaml analyzer: errors: constant_identifier_names: ignore + must_be_immutable: ignore no_wildcard_variable_uses: ignore use_super_parameters: ignore diff --git a/example/lib/main.dart b/example/lib/main.dart index 3743f99..9a028c0 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -14,7 +14,9 @@ void main() { // configure logs for debugging Logger.root.level = Level.FINE; Logger.root.onRecord.listen((record) { - //print('${format.format(record.time)}: ${record.message}'); + if (kDebugMode) { + print('${format.format(record.time)}: ${record.message}'); + } }); WidgetsFlutterBinding.ensureInitialized(); @@ -62,23 +64,10 @@ class MyHomePage extends StatelessWidget { @override Widget build(BuildContext context) { return LivekitRoom( - roomContext: RoomContext( - /*url: 'ws://192.168.2.241:7880', - token: - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3Mjc5MjM2MjIsImlzcyI6IkFQSXJramtRYVZRSjVERSIsIm5hbWUiOiJmZiIsIm5iZiI6MTcyNjEyMzYyMiwic3ViIjoiZmYiLCJ2aWRlbyI6eyJyb29tIjoibGl2ZSIsInJvb21Kb2luIjp0cnVlfX0.dFzRZA88EeVIgb99f304NxcjsfL-16W9dbf05V6rOvQ',*/ - ), + roomContext: RoomContext(), builder: (context) { return Consumer( builder: (context, roomCtx, child) => Scaffold( - /*appBar: AppBar( - title: Selector( - selector: (context, roomCtx) => roomCtx.roomName ?? '', - builder: (context, roomName, child) => Text( - 'Room: $roomName Connected: ${roomCtx.connectState}', - style: Theme.of(context).textTheme.bodyLarge, - ), - ), - ),*/ body: !roomCtx.connected ? Prejoin( onJoinPressed: (name, roomName) => diff --git a/example/lib/src/prejoin.dart b/example/lib/src/prejoin.dart index d55bf11..0d6b872 100644 --- a/example/lib/src/prejoin.dart +++ b/example/lib/src/prejoin.dart @@ -80,16 +80,8 @@ class Prejoin extends StatelessWidget { height: 64, child: Container( padding: const EdgeInsets.all(8.0), - child: TextButton( + child: JoinButton( onPressed: () => _handleJoinPressed(context), - style: ButtonStyle( - backgroundColor: - WidgetStateProperty.all(LKColors.lkBlue), - ), - child: const Text( - 'Join Room', - style: TextStyle(color: Colors.white, fontSize: 16), - ), ), ), ), diff --git a/lib/livekit_components.dart b/lib/livekit_components.dart index 86204d7..9b1c259 100644 --- a/lib/livekit_components.dart +++ b/lib/livekit_components.dart @@ -3,14 +3,11 @@ export 'src/context/participant.dart'; export 'src/context/permission.dart'; export 'src/types/theme.dart'; export 'src/types/types.dart'; -export 'src/ui/mediadevice/camera_preview.dart'; -export 'src/ui/mediadevice/camera_select_button.dart'; -export 'src/ui/mediadevice/microphone_select_button.dart'; -export 'src/ui/buttons/microphone_toggle.dart'; -export 'src/ui/buttons/camera_toggle.dart'; +export 'src/ui/room/camera_preview.dart'; +export 'src/ui/buttons/camera_select_button.dart'; +export 'src/ui/buttons/microphone_select_button.dart'; export 'src/ui/buttons/chat_toggle.dart'; export 'src/ui/buttons/join_button.dart'; -export 'src/ui/buttons/disconnect_button.dart'; export 'src/ui/buttons/screenshare_toggle.dart'; export 'src/ui/chat/chat.dart'; export 'src/ui/room/room.dart'; diff --git a/lib/src/context/chat.dart b/lib/src/context/chat.dart index ce78d39..d9d910c 100644 --- a/lib/src/context/chat.dart +++ b/lib/src/context/chat.dart @@ -1,9 +1,8 @@ import 'dart:convert'; -import 'dart:typed_data'; import 'package:flutter/material.dart'; + import 'package:livekit_client/livekit_client.dart'; -import 'package:livekit_components/src/ui/debug/logger.dart'; // topic: lk-chat-topic class ChatMessage { @@ -49,19 +48,25 @@ class ChatMessage { mixin ChatContextMixin on ChangeNotifier { final List _messages = []; - List get messages => _messages; - LocalParticipant? _localParticipant; + EventsListener? _listener; void chatContextSetup( - EventsListener listener, LocalParticipant localParticipant) { - listener.on((event) { - var str = utf8.decode(Uint8List.fromList(event.data)); - Debug.log('DataReceivedEvent $str'); - addMessageFromMap(str, event.participant); - }); + EventsListener? listener, LocalParticipant? localParticipant) { + _listener = listener; _localParticipant = localParticipant; + if (listener != null) { + _listener!.on((event) { + if (event.topic == 'lk-chat-topic') { + addMessageFromMap( + const Utf8Decoder().convert(event.data), event.participant); + } + }); + } else { + _listener = null; + _messages.clear(); + } } void sendMessage(String message) { diff --git a/lib/src/context/media_device.dart b/lib/src/context/media_device.dart index ea2d544..11ab580 100644 --- a/lib/src/context/media_device.dart +++ b/lib/src/context/media_device.dart @@ -1,10 +1,16 @@ import 'dart:async'; -import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_background/flutter_background.dart'; +import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; import 'package:livekit_client/livekit_client.dart'; +import '../ui/debug/logger.dart'; + mixin MediaDeviceContextMixin on ChangeNotifier { + Room? _room; + CameraPosition position = CameraPosition.front; List? _audioInputs; @@ -31,41 +37,86 @@ mixin MediaDeviceContextMixin on ChangeNotifier { notifyListeners(); } + void setRoom(Room? room) { + _room = room; + } + LocalVideoTrack? _localVideoTrack; LocalVideoTrack? get localVideoTrack => _localVideoTrack; - bool get cameraOpened => _localVideoTrack != null; + bool get cameraOpened => + _room != null ? isCameraEnabled : _localVideoTrack != null; - Future setLocalVideoTrack(bool enabled) async { - if (enabled) { - _localVideoTrack ??= await LocalVideoTrack.createCameraTrack( - CameraCaptureOptions( - cameraPosition: position, - deviceId: selectedVideoInputDeviceId, + Future resetLocalTracks() async { + _localAudioTrack = null; + _localVideoTrack = null; + notifyListeners(); + } + + LocalAudioTrack? _localAudioTrack; + + LocalAudioTrack? get localAudioTrack => _localAudioTrack; + + bool get microphoneOpened => + _room != null ? isMicrophoneEnabled : _localAudioTrack != null; + + void selectAudioInput(MediaDevice device) async { + selectedAudioInputDeviceId = device.deviceId; + if (_room != null) { + await _room!.setAudioInputDevice(device); + } else { + await _localAudioTrack?.dispose(); + _localAudioTrack = await LocalAudioTrack.create( + AudioCaptureOptions( + deviceId: selectedAudioInputDeviceId, ), ); + } + notifyListeners(); + } + + Future selectVideoInput(MediaDevice device) async { + selectedVideoInputDeviceId = device.deviceId; + if (_room != null) { + await _room!.setVideoInputDevice(device); } else { - await _localVideoTrack?.stop(); await _localVideoTrack?.dispose(); - _localVideoTrack = null; + _localVideoTrack = await LocalVideoTrack.createCameraTrack( + CameraCaptureOptions( + cameraPosition: position, + deviceId: device.deviceId, + ), + ); } notifyListeners(); } - LocalAudioTrack? _localAudioTrack; + bool get isMicrophoneEnabled => + _room?.localParticipant?.isMicrophoneEnabled() ?? false; - LocalAudioTrack? get localAudioTrack => _localAudioTrack; - - bool get microphoneOpened => _localAudioTrack != null; - - Future setLocalAudioTrack(bool enabled) async { - if (enabled) { + void enableMicrophone() async { + if (microphoneOpened) { + return; + } + if (_room?.localParticipant != null) { + await _room?.localParticipant?.setMicrophoneEnabled(true); + } else { _localAudioTrack ??= await LocalAudioTrack.create( AudioCaptureOptions( deviceId: selectedAudioInputDeviceId, ), ); + } + notifyListeners(); + } + + void disableMicrophone() async { + if (!microphoneOpened) { + return; + } + if (_room != null) { + await _room?.localParticipant?.setMicrophoneEnabled(false); } else { await _localAudioTrack?.dispose(); _localAudioTrack = null; @@ -73,29 +124,131 @@ mixin MediaDeviceContextMixin on ChangeNotifier { notifyListeners(); } - void selectAudioInput(MediaDevice device) async { - selectedAudioInputDeviceId = device.deviceId; - await _localAudioTrack?.dispose(); + bool get isCameraEnabled => + _room?.localParticipant?.isCameraEnabled() ?? false; + + void enableCamera() async { + if (cameraOpened) { + return; + } + if (_room != null) { + await _room?.localParticipant?.setCameraEnabled(true, + cameraCaptureOptions: CameraCaptureOptions( + deviceId: selectedVideoInputDeviceId, + )); + } else { + _localVideoTrack ??= await LocalVideoTrack.createCameraTrack( + CameraCaptureOptions( + cameraPosition: position, + deviceId: selectedVideoInputDeviceId, + ), + ); + } + notifyListeners(); - _localAudioTrack = await LocalAudioTrack.create( - AudioCaptureOptions( - deviceId: device.deviceId, - ), - ); + } + void disableCamera() async { + if (!cameraOpened) { + return; + } + if (_room != null) { + await _room?.localParticipant?.setCameraEnabled(false); + } else { + await _localVideoTrack?.dispose(); + _localVideoTrack = null; + } notifyListeners(); } - Future selectVideoInput(MediaDevice device) async { - selectedVideoInputDeviceId = device.deviceId; - await _localVideoTrack?.dispose(); + bool get isScreenShareEnabled => + _room?.localParticipant?.isScreenShareEnabled() ?? false; + + Future enableScreenShare(context) async { + if (lkPlatformIsDesktop()) { + try { + final source = await showDialog( + context: context, + builder: (context) => ScreenSelectDialog(), + ); + if (source == null) { + Debug.log('cancelled screenshare'); + return; + } + Debug.log('DesktopCapturerSource: ${source.id}'); + var track = await LocalVideoTrack.createScreenShareTrack( + ScreenShareCaptureOptions( + sourceId: source.id, + maxFrameRate: 15.0, + ), + ); + await _room?.localParticipant?.publishVideoTrack(track); + } catch (e) { + Debug.log('could not publish video: $e'); + } + return; + } + if (lkPlatformIs(PlatformType.android)) { + // Android specific + bool hasCapturePermission = await rtc.Helper.requestCapturePermission(); + if (!hasCapturePermission) { + return; + } + + requestBackgroundPermission([bool isRetry = false]) async { + // Required for android screenshare. + try { + bool hasPermissions = await FlutterBackground.hasPermissions; + if (!isRetry) { + const androidConfig = FlutterBackgroundAndroidConfig( + notificationTitle: 'Screen Sharing', + notificationText: 'LiveKit Example is sharing the screen.', + notificationImportance: AndroidNotificationImportance.normal, + notificationIcon: AndroidResource( + name: 'livekit_ic_launcher', defType: 'mipmap'), + ); + hasPermissions = await FlutterBackground.initialize( + androidConfig: androidConfig); + } + if (hasPermissions && + !FlutterBackground.isBackgroundExecutionEnabled) { + await FlutterBackground.enableBackgroundExecution(); + } + } catch (e) { + if (!isRetry) { + return await Future.delayed(const Duration(seconds: 1), + () => requestBackgroundPermission(true)); + } + Debug.log('could not publish video: $e'); + } + } + + await requestBackgroundPermission(); + } + if (lkPlatformIs(PlatformType.iOS)) { + var track = await LocalVideoTrack.createScreenShareTrack( + const ScreenShareCaptureOptions( + useiOSBroadcastExtension: true, + maxFrameRate: 15.0, + ), + ); + await _room?.localParticipant?.publishVideoTrack(track); + return; + } + + if (lkPlatformIsWebMobile()) { + await context + .showErrorDialog('Screen share is not supported on mobile web'); + return; + } + + await _room?.localParticipant + ?.setScreenShareEnabled(true, captureScreenAudio: true); notifyListeners(); - _localVideoTrack = await LocalVideoTrack.createCameraTrack( - CameraCaptureOptions( - cameraPosition: position, - deviceId: device.deviceId, - ), - ); + } + + Future disableScreenShare() async { + await _room?.localParticipant?.setScreenShareEnabled(false); notifyListeners(); } } diff --git a/lib/src/context/room.dart b/lib/src/context/room.dart index 8384509..6133b1a 100644 --- a/lib/src/context/room.dart +++ b/lib/src/context/room.dart @@ -1,10 +1,7 @@ import 'package:flutter/material.dart' hide ConnectionState; -import 'package:flutter_background/flutter_background.dart'; -import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; import 'package:livekit_client/livekit_client.dart'; -import '../ui/debug/logger.dart'; import 'chat.dart'; import 'media_device.dart'; import 'toast.dart'; @@ -25,29 +22,30 @@ class RoomContext extends ChangeNotifier _listener ..on((event) { chatContextSetup(_listener, _room.localParticipant!); + setRoom(_room); showConnectionStateToast(room.connectionState); notifyListeners(); }) ..on((event) { showConnectionStateToast(room.connectionState); + setRoom(null); + chatContextSetup(null, null); notifyListeners(); }) - ..on((event) { - notifyListeners(); - }) - ..on((event) { - notifyListeners(); - }) - ..on((event) { - notifyListeners(); - }) - ..on((event) { - notifyListeners(); - }) - ..on((event) { - notifyListeners(); + ..on((event) { + [ + ParticipantConnectedEvent, + ParticipantDisconnectedEvent, + LocalTrackPublishedEvent, + TrackPublishedEvent, + TrackUnpublishedEvent + ].contains(event.runtimeType) + ? notifyListeners() + : null; }); + loadDevices(); + if (connect && url != null && token != null) { this.connect( url: url, @@ -58,11 +56,22 @@ class RoomContext extends ChangeNotifier } } - @override - void dispose() { - _listener.dispose(); - super.dispose(); - } + final ConnectOptions? _connectOptions; + FastConnectOptions? _fastConnectOptions; + late EventsListener _listener; + + String? _url; + String? _token; + late Room _room; + + Room get room => _room; + + String? get roomName => _room.name; + String? get roomMetadata => _room.metadata; + + ConnectionState get connectState => _room.connectionState; + bool get connected => _room.connectionState == ConnectionState.connected; + int get participantCount => participants.length; Future connect({ String? url, @@ -73,6 +82,7 @@ class RoomContext extends ChangeNotifier microphone: TrackOption(track: localAudioTrack!), camera: TrackOption(track: localVideoTrack!), ); + await resetLocalTracks(); } await _room.connect( @@ -92,29 +102,6 @@ class RoomContext extends ChangeNotifier notifyListeners(); } - final ConnectOptions? _connectOptions; - - FastConnectOptions? _fastConnectOptions; - - late EventsListener _listener; - - String? _url; - - String? _token; - - Room get room => _room; - - late Room _room; - - bool get isMicrophoneEnabled => - _room.localParticipant?.isMicrophoneEnabled() ?? false; - - String? get roomName => _room.name; - - String? get roomMetadata => _room.metadata; - - int get participantCount => participants.length; - List get participants { if (!connected) { return []; @@ -129,25 +116,7 @@ class RoomContext extends ChangeNotifier ]; } - ConnectionState get connectState => _room.connectionState; - - bool get connected => _room.connectionState == ConnectionState.connected; - - bool get isCameraEnabled => - _room.localParticipant?.isCameraEnabled() ?? false; - - void enableCamera() async { - await _room.localParticipant?.setCameraEnabled(true); - notifyListeners(); - } - - void disableCamera() async { - await _room.localParticipant?.setCameraEnabled(false); - notifyListeners(); - } - bool _chatEnabled = false; - bool get isChatEnabled => _chatEnabled; void enableChat() { @@ -160,104 +129,9 @@ class RoomContext extends ChangeNotifier notifyListeners(); } - void enableMicrophone() async { - await _room.localParticipant?.setMicrophoneEnabled(true); - notifyListeners(); - } - - void disableMicrophone() async { - await _room.localParticipant?.setMicrophoneEnabled(false); - notifyListeners(); - } - - bool get isScreenShareEnabled => - _room.localParticipant?.isScreenShareEnabled() ?? false; - - Future enableScreenShare(context) async { - if (lkPlatformIsDesktop()) { - try { - final source = await showDialog( - context: context, - builder: (context) => ScreenSelectDialog(), - ); - if (source == null) { - Debug.log('cancelled screenshare'); - return; - } - Debug.log('DesktopCapturerSource: ${source.id}'); - var track = await LocalVideoTrack.createScreenShareTrack( - ScreenShareCaptureOptions( - sourceId: source.id, - maxFrameRate: 15.0, - ), - ); - await _room.localParticipant?.publishVideoTrack(track); - } catch (e) { - Debug.log('could not publish video: $e'); - } - return; - } - if (lkPlatformIs(PlatformType.android)) { - // Android specific - bool hasCapturePermission = await rtc.Helper.requestCapturePermission(); - if (!hasCapturePermission) { - return; - } - - requestBackgroundPermission([bool isRetry = false]) async { - // Required for android screenshare. - try { - bool hasPermissions = await FlutterBackground.hasPermissions; - if (!isRetry) { - const androidConfig = FlutterBackgroundAndroidConfig( - notificationTitle: 'Screen Sharing', - notificationText: 'LiveKit Example is sharing the screen.', - notificationImportance: AndroidNotificationImportance.normal, - notificationIcon: AndroidResource( - name: 'livekit_ic_launcher', defType: 'mipmap'), - ); - hasPermissions = await FlutterBackground.initialize( - androidConfig: androidConfig); - } - if (hasPermissions && - !FlutterBackground.isBackgroundExecutionEnabled) { - await FlutterBackground.enableBackgroundExecution(); - } - } catch (e) { - if (!isRetry) { - return await Future.delayed(const Duration(seconds: 1), - () => requestBackgroundPermission(true)); - } - Debug.log('could not publish video: $e'); - } - } - - await requestBackgroundPermission(); - } - if (lkPlatformIs(PlatformType.iOS)) { - var track = await LocalVideoTrack.createScreenShareTrack( - const ScreenShareCaptureOptions( - useiOSBroadcastExtension: true, - maxFrameRate: 15.0, - ), - ); - await _room.localParticipant?.publishVideoTrack(track); - return; - } - - if (lkPlatformIsWebMobile()) { - await context - .showErrorDialog('Screen share is not supported on mobile web'); - return; - } - - await _room.localParticipant - ?.setScreenShareEnabled(true, captureScreenAudio: true); - notifyListeners(); - } - - Future disableScreenShare() async { - await _room.localParticipant?.setScreenShareEnabled(false); - notifyListeners(); + @override + void dispose() { + _listener.dispose(); + super.dispose(); } } diff --git a/lib/src/context/toast.dart b/lib/src/context/toast.dart index f73f867..2b5a895 100644 --- a/lib/src/context/toast.dart +++ b/lib/src/context/toast.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart' hide ConnectionState; + import 'package:fluttertoast/fluttertoast.dart'; import 'package:livekit_client/livekit_client.dart'; diff --git a/lib/src/laylout/grid_layout.dart b/lib/src/laylout/grid_layout.dart index e69de29..8b13789 100644 --- a/lib/src/laylout/grid_layout.dart +++ b/lib/src/laylout/grid_layout.dart @@ -0,0 +1 @@ + diff --git a/lib/src/types/theme.dart b/lib/src/types/theme.dart index 53f9a9c..11fc6a9 100644 --- a/lib/src/types/theme.dart +++ b/lib/src/types/theme.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; + import 'package:google_fonts/google_fonts.dart'; // @@ -9,6 +10,7 @@ import 'package:google_fonts/google_fonts.dart'; // extension LKColors on Colors { + static const lkLightBlue = Color(0xFF8AB4FF); static const lkBlue = Color(0xFF5A8BFF); static const lkDarkBlue = Color(0xFF00153C); } diff --git a/lib/src/types/types.dart b/lib/src/types/types.dart index 4185ec2..88b72eb 100644 --- a/lib/src/types/types.dart +++ b/lib/src/types/types.dart @@ -2,7 +2,7 @@ import 'package:flutter/widgets.dart'; GlobalKey fToastNavigatorKey = GlobalKey(); -class WatchValue { +class WatchValue { WatchValue(this.value); T value; @@ -11,4 +11,4 @@ class WatchValue { /* class Selector>( selector: (context, tracks) => participantContext.videoTracks, - builder: (context, tracks, child)*/ \ No newline at end of file + builder: (context, tracks, child)*/ diff --git a/lib/src/ui/buttons/camera_select_button.dart b/lib/src/ui/buttons/camera_select_button.dart new file mode 100644 index 0000000..2d81dfc --- /dev/null +++ b/lib/src/ui/buttons/camera_select_button.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; + +import 'package:livekit_client/livekit_client.dart'; +import 'package:provider/provider.dart'; + +import '../../context/room.dart'; +import '../../types/theme.dart'; + +class CameraSelectButton extends StatelessWidget { + const CameraSelectButton({super.key}); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, roomCtx, child) { + return Row( + children: [ + ElevatedButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all(roomCtx.cameraOpened + ? LKColors.lkBlue + : Colors.grey.withOpacity(0.6)), + foregroundColor: WidgetStateProperty.all(Colors.white), + overlayColor: WidgetStateProperty.all( + roomCtx.cameraOpened ? LKColors.lkLightBlue : Colors.grey), + shape: WidgetStateProperty.all(const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20.0), + bottomLeft: Radius.circular(20.0)))), + padding: WidgetStateProperty.all( + const EdgeInsets.fromLTRB(10, 20, 10, 20)), + ), + onPressed: () => roomCtx.cameraOpened + ? roomCtx.disableCamera() + : roomCtx.enableCamera(), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(roomCtx.cameraOpened + ? Icons.videocam + : Icons.videocam_off), + const Text('Camera'), + ], + ), + ), + const SizedBox(width: 0.2), + Selector( + selector: (context, roomCtx) => + roomCtx.selectedVideoInputDeviceId, + builder: (context, selectedVideoInputDeviceId, child) { + return PopupMenuButton( + padding: const EdgeInsets.all(12), + offset: + Offset(0, ((roomCtx.videoInputs?.length ?? 1) * -55.0)), + icon: const Icon(Icons.arrow_drop_down), + style: ButtonStyle( + backgroundColor: + WidgetStateProperty.all(Colors.grey.withOpacity(0.6)), + foregroundColor: WidgetStateProperty.all(Colors.white), + overlayColor: WidgetStateProperty.all(Colors.grey), + elevation: WidgetStateProperty.all(20), + shape: WidgetStateProperty.all(const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topRight: Radius.circular(20.0), + bottomRight: Radius.circular(20.0)))), + ), + enabled: roomCtx.cameraOpened, + itemBuilder: (BuildContext context) { + return [ + if (roomCtx.videoInputs != null) + ...roomCtx.videoInputs!.map((device) { + return PopupMenuItem( + value: device, + child: ListTile( + selected: (device.deviceId == + selectedVideoInputDeviceId), + selectedColor: LKColors.lkBlue, + leading: (device.deviceId == + selectedVideoInputDeviceId) + ? Icon( + Icons.check_box_outlined, + color: (device.deviceId == + selectedVideoInputDeviceId) + ? LKColors.lkBlue + : Colors.white, + ) + : const Icon( + Icons.check_box_outline_blank, + color: Colors.white, + ), + title: Text(device.label), + ), + onTap: () => roomCtx.selectVideoInput(device), + ); + }) + ]; + }, + ); + }, + ), + ], + ); + }, + ); + } +} diff --git a/lib/src/ui/buttons/camera_toggle.dart b/lib/src/ui/buttons/camera_toggle.dart deleted file mode 100644 index f38cab8..0000000 --- a/lib/src/ui/buttons/camera_toggle.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:provider/provider.dart'; - -import '../../context/room.dart'; - -class CameraToggleButton extends StatelessWidget { - const CameraToggleButton({super.key}); - - void _disableCamera(roomCtx) async { - await roomCtx.disableCamera(); - } - - void _enableCamera(roomCtx) async { - await roomCtx.enableCamera(); - } - - @override - Widget build(BuildContext context) { - return Consumer(builder: (context, roomCtx, child) { - return Selector( - selector: (context, enabled) => roomCtx.isCameraEnabled, - builder: (context, enabled, child) => IconButton( - onPressed: () => - enabled ? _disableCamera(roomCtx) : _enableCamera(roomCtx), - icon: Icon(enabled ? Icons.videocam_off : Icons.videocam), - tooltip: enabled ? 'mute cam' : 'unmute cam', - )); - }); - } -} diff --git a/lib/src/ui/buttons/chat_button.dart b/lib/src/ui/buttons/chat_button.dart deleted file mode 100644 index 3a1a13f..0000000 --- a/lib/src/ui/buttons/chat_button.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:livekit_components/livekit_components.dart'; - -import 'package:provider/provider.dart'; - -class ChatButton extends StatelessWidget { - const ChatButton({super.key}); - - void _disableChat(roomCtx) async { - await roomCtx.disableChat(); - } - - void _enableChat(roomCtx) async { - await roomCtx.enableChat(); - } - - @override - Widget build(BuildContext context) { - return Consumer(builder: (context, roomCtx, child) { - return Selector( - selector: (context, isChatEnabled) => roomCtx.isChatEnabled, - builder: (context, isChatEnabled, child) { - return Row( - children: [ - Container( - padding: const EdgeInsets.symmetric( - horizontal: 10.0, vertical: 10.0), - decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(20.0)), - color: isChatEnabled - ? Colors.grey - : Colors.grey.withOpacity(0.6)), - child: GestureDetector( - onTap: () => isChatEnabled - ? _disableChat(roomCtx) - : _enableChat(roomCtx), - child: const FocusableActionDetector( - child: Row( - children: [ - Icon( - Icons.chat_outlined, - ), - SizedBox(width: 4.0), - Text('Chat'), - /*Badge.count( - count: 2, - backgroundColor: LKColors.lkBlue, - child: const Text('Chat'), - ),*/ - ], - ), - ), - ), - ), - ], - ); - }, - ); - }); - } -} diff --git a/lib/src/ui/buttons/chat_toggle.dart b/lib/src/ui/buttons/chat_toggle.dart index ddd9af3..99a842b 100644 --- a/lib/src/ui/buttons/chat_toggle.dart +++ b/lib/src/ui/buttons/chat_toggle.dart @@ -2,30 +2,51 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import '../../context/room.dart'; +import 'package:livekit_components/livekit_components.dart'; -class ChatToggleButton extends StatelessWidget { - const ChatToggleButton({super.key}); - - void _disableChat(roomCtx) async { - await roomCtx.disableChat(); - } - - void _enableChat(roomCtx) async { - await roomCtx.enableChat(); - } +class ChatToggle extends StatelessWidget { + const ChatToggle({super.key}); @override Widget build(BuildContext context) { return Consumer(builder: (context, roomCtx, child) { return Selector( - selector: (context, enabled) => roomCtx.isChatEnabled, - builder: (context, enabled, child) => IconButton( - onPressed: () => - enabled ? _disableChat(roomCtx) : _enableChat(roomCtx), - icon: const Icon(Icons.chat), - tooltip: 'Chat', - )); + selector: (context, isChatEnabled) => roomCtx.isChatEnabled, + builder: (context, isChatEnabled, child) { + return ElevatedButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all(isChatEnabled + ? LKColors.lkBlue + : Colors.grey.withOpacity(0.6)), + foregroundColor: WidgetStateProperty.all(Colors.white), + overlayColor: WidgetStateProperty.all( + isChatEnabled ? LKColors.lkLightBlue : Colors.grey), + shape: WidgetStateProperty.all( + const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(20.0), + ), + ), + ), + padding: WidgetStateProperty.all( + const EdgeInsets.fromLTRB(12, 20, 10, 20)), + ), + onPressed: () => + isChatEnabled ? roomCtx.disableChat() : roomCtx.enableChat(), + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.chat_outlined), + SizedBox(width: 2), + Text( + 'Chat', + style: TextStyle(fontSize: 14), + ), + ], + ), + ); + }, + ); }); } } diff --git a/lib/src/ui/buttons/disconnect_button.dart b/lib/src/ui/buttons/disconnect_button.dart deleted file mode 100644 index 782ab14..0000000 --- a/lib/src/ui/buttons/disconnect_button.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:provider/provider.dart'; - -import '../../context/room.dart'; - -class DisconnectButton extends StatelessWidget { - const DisconnectButton({super.key}); - - @override - Widget build(BuildContext context) { - return Consumer(builder: (context, roomCtx, child) { - return Selector( - selector: (context, connected) => roomCtx.connected, - builder: (context, connected, child) => IconButton( - disabledColor: Colors.grey, - color: connected ? Colors.red : Colors.grey, - onPressed: () => connected ? roomCtx.disconnect() : null, - icon: const Icon(Icons.close), - tooltip: connected ? 'Disconnect' : '', - )); - }); - } -} diff --git a/lib/src/ui/buttons/join_button.dart b/lib/src/ui/buttons/join_button.dart index 735d079..b98090e 100644 --- a/lib/src/ui/buttons/join_button.dart +++ b/lib/src/ui/buttons/join_button.dart @@ -1,21 +1,40 @@ import 'package:flutter/material.dart'; -import 'package:livekit_components/livekit_components.dart'; import 'package:provider/provider.dart'; +import 'package:livekit_components/livekit_components.dart'; + class JoinButton extends StatelessWidget { - const JoinButton({super.key}); + JoinButton({super.key, this.onPressed}); + + void Function()? onPressed; @override Widget build(BuildContext context) { return Consumer(builder: (context, roomCtx, child) { return Selector( selector: (context, connected) => roomCtx.connected, - builder: (context, connected, child) => TextButton( - onPressed: () => !connected ? roomCtx.connect() : null, + builder: (context, connected, child) => ElevatedButton( + onPressed: () => onPressed != null + ? onPressed!() + : !connected + ? roomCtx.connect() + : null, style: ButtonStyle( backgroundColor: WidgetStateProperty.all( connected ? Colors.grey : LKColors.lkBlue), + foregroundColor: WidgetStateProperty.all(Colors.white), + //overlayColor: WidgetStateProperty.all( + // connected ? Colors.grey : LKColors.lkLightBlue), + shape: WidgetStateProperty.all( + const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(20.0), + ), + ), + ), + padding: WidgetStateProperty.all( + const EdgeInsets.fromLTRB(10, 20, 10, 20)), ), child: const Text( 'Join Room', diff --git a/lib/src/ui/buttons/leave_button.dart b/lib/src/ui/buttons/leave_button.dart index cfdf6c7..3dd0776 100644 --- a/lib/src/ui/buttons/leave_button.dart +++ b/lib/src/ui/buttons/leave_button.dart @@ -13,29 +13,35 @@ class LeaveButton extends StatelessWidget { return Selector( selector: (context, connected) => roomCtx.connected, builder: (context, connected, child) { - return Row( - children: [ - Container( - padding: const EdgeInsets.symmetric( - horizontal: 10.0, vertical: 10.0), - decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(20.0)), - color: - connected ? Colors.red : Colors.grey.withOpacity(0.6)), - child: GestureDetector( - onTap: () => connected ? roomCtx.disconnect() : null, - child: const FocusableActionDetector( - child: Row( - children: [ - Icon(Icons.logout), - SizedBox(width: 4.0), - Text('Leave'), - ], - ), + return ElevatedButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + connected ? Colors.red : Colors.grey.withOpacity(0.6)), + foregroundColor: WidgetStateProperty.all(Colors.white), + overlayColor: WidgetStateProperty.all( + connected ? Colors.redAccent : Colors.grey), + shape: WidgetStateProperty.all( + const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(20.0), ), ), ), - ], + padding: WidgetStateProperty.all( + const EdgeInsets.fromLTRB(10, 20, 10, 20)), + ), + onPressed: () => connected ? roomCtx.disconnect() : null, + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.logout), + SizedBox(width: 2), + Text( + 'Leave', + style: TextStyle(fontSize: 14), + ), + ], + ), ); }, ); diff --git a/lib/src/ui/buttons/microphone_select_button.dart b/lib/src/ui/buttons/microphone_select_button.dart new file mode 100644 index 0000000..282ab7d --- /dev/null +++ b/lib/src/ui/buttons/microphone_select_button.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; + +import 'package:livekit_client/livekit_client.dart'; +import 'package:provider/provider.dart'; + +import 'package:livekit_components/livekit_components.dart'; + +class MicrophoneSelectButton extends StatelessWidget { + const MicrophoneSelectButton({super.key}); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, roomCtx, child) { + return Row(children: [ + ElevatedButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all(roomCtx.microphoneOpened + ? LKColors.lkBlue + : Colors.grey.withOpacity(0.6)), + foregroundColor: WidgetStateProperty.all(Colors.white), + overlayColor: WidgetStateProperty.all(roomCtx.microphoneOpened + ? LKColors.lkLightBlue + : Colors.grey), + shape: WidgetStateProperty.all(const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20.0), + bottomLeft: Radius.circular(20.0)))), + padding: WidgetStateProperty.all( + const EdgeInsets.fromLTRB(10, 20, 10, 20)), + ), + onPressed: () => roomCtx.microphoneOpened + ? roomCtx.disableMicrophone() + : roomCtx.enableMicrophone(), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(roomCtx.microphoneOpened ? Icons.mic : Icons.mic_off), + const SizedBox(width: 2), + const Text( + 'Microphone', + style: TextStyle(fontSize: 14), + ), + ], + ), + ), + const SizedBox(width: 0.2), + Selector( + selector: (context, roomCtx) => roomCtx.selectedAudioInputDeviceId, + builder: (context, selectedAudioInputDeviceId, child) { + return PopupMenuButton( + padding: const EdgeInsets.all(12), + icon: const Icon(Icons.arrow_drop_down), + offset: Offset(0, ((roomCtx.audioInputs?.length ?? 1) * -55.0)), + style: ButtonStyle( + backgroundColor: + WidgetStateProperty.all(Colors.grey.withOpacity(0.6)), + foregroundColor: WidgetStateProperty.all(Colors.white), + overlayColor: WidgetStateProperty.all(Colors.grey), + elevation: WidgetStateProperty.all(20), + shape: WidgetStateProperty.all(const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topRight: Radius.circular(20.0), + bottomRight: Radius.circular(20.0)))), + ), + enabled: roomCtx.microphoneOpened, + itemBuilder: (BuildContext context) { + return [ + if (roomCtx.audioInputs != null) + ...roomCtx.audioInputs!.map((device) { + return PopupMenuItem( + value: device, + child: ListTile( + selected: + (device.deviceId == selectedAudioInputDeviceId), + selectedColor: LKColors.lkBlue, + leading: + (device.deviceId == selectedAudioInputDeviceId) + ? Icon( + Icons.check_box_outlined, + color: (device.deviceId == + selectedAudioInputDeviceId) + ? LKColors.lkBlue + : Colors.white, + ) + : const Icon( + Icons.check_box_outline_blank, + color: Colors.white, + ), + title: Text(device.label), + ), + onTap: () => roomCtx.selectAudioInput(device), + ); + }) + ]; + }, + ); + }, + ), + ]); + }, + ); + } +} diff --git a/lib/src/ui/buttons/microphone_toggle.dart b/lib/src/ui/buttons/microphone_toggle.dart deleted file mode 100644 index a21e816..0000000 --- a/lib/src/ui/buttons/microphone_toggle.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:provider/provider.dart'; - -import '../../context/room.dart'; - -class MicrophoneToggleButton extends StatelessWidget { - const MicrophoneToggleButton({super.key}); - - void _disableAudio(roomCtx) async { - await roomCtx.disableMicrophone(); - } - - void _enableAudio(roomCtx) async { - await roomCtx.enableMicrophone(); - } - - @override - Widget build(BuildContext context) { - return Consumer(builder: (context, roomCtx, child) { - return Selector( - selector: (context, enabled) => roomCtx.isMicrophoneEnabled, - builder: (context, enabled, child) => IconButton( - onPressed: () => - enabled ? _disableAudio(roomCtx) : _enableAudio(roomCtx), - icon: Icon(enabled ? Icons.mic_off : Icons.mic), - tooltip: enabled ? 'mute audio' : 'unmute audio', - )); - }); - } -} diff --git a/lib/src/ui/buttons/screen_share_button.dart b/lib/src/ui/buttons/screen_share_button.dart deleted file mode 100644 index 27a476f..0000000 --- a/lib/src/ui/buttons/screen_share_button.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:livekit_components/src/context/media_device.dart'; - -import 'package:provider/provider.dart'; - -import '../../context/room.dart'; - -class ScreenShareButton extends StatelessWidget { - const ScreenShareButton({super.key}); - - @override - Widget build(BuildContext context) { - return Consumer(builder: (context, roomCtx, child) { - return Selector( - selector: (context, screenShareEnabled) => roomCtx.isScreenShareEnabled, - builder: (context, screenShareEnabled, child) { - return Row( - children: [ - Container( - padding: const EdgeInsets.symmetric( - horizontal: 10.0, vertical: 10.0), - decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(20.0)), - color: screenShareEnabled - ? Colors.grey - : Colors.grey.withOpacity(0.6)), - child: GestureDetector( - onTap: () => screenShareEnabled - ? roomCtx.disableScreenShare() - : roomCtx.enableScreenShare(context), - child: const FocusableActionDetector( - child: Row( - children: [ - Icon(Icons.screen_share_outlined), - SizedBox(width: 4.0), - Text('Screen Share'), - ], - ), - ), - ), - ), - ], - ); - }, - ); - }); - } -} diff --git a/lib/src/ui/buttons/screenshare_toggle.dart b/lib/src/ui/buttons/screenshare_toggle.dart index ac64576..c34c73b 100644 --- a/lib/src/ui/buttons/screenshare_toggle.dart +++ b/lib/src/ui/buttons/screenshare_toggle.dart @@ -2,32 +2,54 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import '../../context/room.dart'; +import 'package:livekit_components/livekit_components.dart'; -class ScreenShareToggleButton extends StatelessWidget { - const ScreenShareToggleButton({super.key}); - - void _disableScreenShare(RoomContext roomCtx) async { - await roomCtx.disableScreenShare(); - } - - void _enableScreenShare(context, RoomContext roomCtx) async { - await roomCtx.enableScreenShare(context); - } +class ScreenShareToggle extends StatelessWidget { + const ScreenShareToggle({super.key}); @override Widget build(BuildContext context) { return Consumer(builder: (context, roomCtx, child) { return Selector( - selector: (context, enabled) => roomCtx.isScreenShareEnabled, - builder: (context, enabled, child) => IconButton( - onPressed: () => enabled - ? _disableScreenShare(roomCtx) - : _enableScreenShare(context, roomCtx), - icon: const Icon(Icons.screen_share), - tooltip: - enabled ? 'Stop screen sharing' : 'Start screen sharing', - )); + selector: (context, screenShareEnabled) => roomCtx.isScreenShareEnabled, + builder: (context, screenShareEnabled, child) { + return ElevatedButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all(screenShareEnabled + ? LKColors.lkBlue + : Colors.grey.withOpacity(0.6)), + foregroundColor: WidgetStateProperty.all(Colors.white), + overlayColor: WidgetStateProperty.all( + screenShareEnabled ? LKColors.lkLightBlue : Colors.grey), + shape: WidgetStateProperty.all( + const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(20.0), + ), + ), + ), + padding: WidgetStateProperty.all( + const EdgeInsets.fromLTRB(10, 20, 10, 20)), + ), + onPressed: () => screenShareEnabled + ? roomCtx.disableScreenShare() + : roomCtx.enableScreenShare(context), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(screenShareEnabled + ? Icons.stop_screen_share_outlined + : Icons.screen_share_outlined), + const SizedBox(width: 2), + Text( + screenShareEnabled ? 'Stop screen share ' : 'Screen share', + style: const TextStyle(fontSize: 14), + ), + ], + ), + ); + }, + ); }); } } diff --git a/lib/src/ui/chat/chat.dart b/lib/src/ui/chat/chat.dart index 28da4dd..9409922 100644 --- a/lib/src/ui/chat/chat.dart +++ b/lib/src/ui/chat/chat.dart @@ -1,11 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:livekit_components/livekit_components.dart'; -import 'package:livekit_components/src/context/chat.dart'; - -import 'package:provider/provider.dart'; import 'package:chat_bubbles/chat_bubbles.dart'; +import 'package:provider/provider.dart'; +import 'package:livekit_components/livekit_components.dart'; +import 'package:livekit_components/src/context/chat.dart'; import 'data_chip.dart'; class Chat extends StatelessWidget { diff --git a/lib/src/ui/chat/data_chip.dart b/lib/src/ui/chat/data_chip.dart index dc1cc60..b9f7350 100644 --- a/lib/src/ui/chat/data_chip.dart +++ b/lib/src/ui/chat/data_chip.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; + import 'package:intl/intl.dart'; class CustomDateChip extends StatelessWidget { @@ -24,7 +25,9 @@ class CustomDateChip extends StatelessWidget { ), child: Container( decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(6)), + borderRadius: const BorderRadius.all( + Radius.circular(6), + ), color: color, ), child: Padding( diff --git a/lib/src/ui/debug/logger.dart b/lib/src/ui/debug/logger.dart index c0fac0c..7bff949 100644 --- a/lib/src/ui/debug/logger.dart +++ b/lib/src/ui/debug/logger.dart @@ -1,4 +1,5 @@ import 'package:flutter/foundation.dart'; + import 'package:intl/intl.dart'; class Debug { diff --git a/lib/src/ui/mediadevice/camera_select_button.dart b/lib/src/ui/mediadevice/camera_select_button.dart deleted file mode 100644 index 0200eef..0000000 --- a/lib/src/ui/mediadevice/camera_select_button.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:livekit_client/livekit_client.dart'; - -import 'package:provider/provider.dart'; - -import '../../context/room.dart'; -import '../../types/theme.dart'; - -class CameraSelectButton extends StatelessWidget { - const CameraSelectButton({super.key}); - - @override - Widget build(BuildContext context) { - return Consumer( - builder: (context, roomCtx, child) { - return Row( - children: [ - Container( - padding: - const EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0), - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(20.0), - bottomLeft: Radius.circular(20.0)), - color: roomCtx.cameraOpened - ? Colors.grey - : Colors.grey.withOpacity(0.6)), - child: GestureDetector( - onTap: () => roomCtx.setLocalVideoTrack(!roomCtx.cameraOpened), - child: FocusableActionDetector( - child: Row( - children: [ - Icon(roomCtx.cameraOpened - ? Icons.videocam - : Icons.videocam_off), - const SizedBox(width: 4.0), - const Text('Camera'), - ], - ), - ), - ), - ), - Container( - padding: - const EdgeInsets.symmetric(horizontal: 2.0, vertical: 2.0), - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topRight: Radius.circular(20.0), - bottomRight: Radius.circular(20.0)), - color: Colors.grey.withOpacity(0.6)), - child: Selector( - selector: (context, md) => roomCtx.selectedVideoInputDeviceId, - builder: (context, selectedVideoInputDeviceId, child) { - return PopupMenuButton( - enabled: roomCtx.cameraOpened, - icon: const Icon(Icons.arrow_drop_down), - offset: const Offset(0, -65), - itemBuilder: (BuildContext context) { - return [ - if (roomCtx.videoInputs != null) - ...roomCtx.videoInputs!.map((device) { - return PopupMenuItem( - value: device, - child: ListTile( - selected: (device.deviceId == - selectedVideoInputDeviceId), - selectedColor: LKColors.lkBlue, - leading: (device.deviceId == - selectedVideoInputDeviceId) - ? Icon( - Icons.check_box_outlined, - color: (device.deviceId == - selectedVideoInputDeviceId) - ? LKColors.lkBlue - : Colors.white, - ) - : const Icon( - Icons.check_box_outline_blank, - color: Colors.white, - ), - title: Text(device.label), - ), - onTap: () => roomCtx.selectVideoInput(device), - ); - }) - ]; - }, - ); - }, - ), - ), - ], - ); - }, - ); - } -} diff --git a/lib/src/ui/mediadevice/microphone_select_button.dart b/lib/src/ui/mediadevice/microphone_select_button.dart deleted file mode 100644 index e4e5927..0000000 --- a/lib/src/ui/mediadevice/microphone_select_button.dart +++ /dev/null @@ -1,93 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:livekit_client/livekit_client.dart'; -import 'package:livekit_components/livekit_components.dart'; -import 'package:livekit_components/src/context/media_device.dart'; - -import 'package:provider/provider.dart'; - -class MicrophoneSelectButton extends StatelessWidget { - const MicrophoneSelectButton({super.key}); - - @override - Widget build(BuildContext context) { - return Consumer( - builder: (context, roomCtx, child) { - return Row(children: [ - Container( - padding: - const EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0), - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(20.0), - bottomLeft: Radius.circular(20.0)), - color: roomCtx.microphoneOpened - ? Colors.grey - : Colors.grey.withOpacity(0.6)), - child: GestureDetector( - onTap: () => - roomCtx.setLocalAudioTrack(!roomCtx.microphoneOpened), - child: FocusableActionDetector( - child: Row( - children: [ - Icon(roomCtx.microphoneOpened ? Icons.mic : Icons.mic_off), - const SizedBox(width: 4.0), - const Text('Microphone'), - ], - ), - ), - ), - ), - Container( - padding: const EdgeInsets.symmetric(horizontal: 2.0, vertical: 2.0), - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topRight: Radius.circular(20.0), - bottomRight: Radius.circular(20.0)), - color: Colors.grey.withOpacity(0.6)), - child: Selector( - selector: (context, roomCtx) => - roomCtx.selectedAudioInputDeviceId, - builder: (context, selectedAudioInputDeviceId, child) { - return PopupMenuButton( - icon: const Icon(Icons.arrow_drop_down), - offset: const Offset(0, -65), - enabled: roomCtx.microphoneOpened, - itemBuilder: (BuildContext context) { - return [ - if (roomCtx.audioInputs != null) - ...roomCtx.audioInputs!.map((device) { - return PopupMenuItem( - value: device, - child: ListTile( - selected: (device.deviceId == - selectedAudioInputDeviceId), - selectedColor: LKColors.lkBlue, - leading: (device.deviceId == - selectedAudioInputDeviceId) - ? Icon( - Icons.check_box_outlined, - color: (device.deviceId == - selectedAudioInputDeviceId) - ? LKColors.lkBlue - : Colors.white, - ) - : const Icon( - Icons.check_box_outline_blank, - color: Colors.white, - ), - title: Text(device.label), - ), - onTap: () => roomCtx.selectAudioInput(device), - ); - }) - ]; - }, - ); - }, - ), - ), - ]); - }, - ); - } -} diff --git a/lib/src/ui/participant/is_speaking_indicator.dart b/lib/src/ui/participant/is_speaking_indicator.dart index 7b276e8..118f8a2 100644 --- a/lib/src/ui/participant/is_speaking_indicator.dart +++ b/lib/src/ui/participant/is_speaking_indicator.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; + import 'package:provider/provider.dart'; import '../../context/participant.dart'; diff --git a/lib/src/ui/participant/participant.dart b/lib/src/ui/participant/participant.dart index 4138e17..0e3c6d8 100644 --- a/lib/src/ui/participant/participant.dart +++ b/lib/src/ui/participant/participant.dart @@ -1,10 +1,11 @@ -import 'package:flutter/material.dart'; import 'dart:math' as math; + +import 'package:flutter/material.dart'; + import 'package:livekit_client/livekit_client.dart'; import 'package:provider/provider.dart'; import '../../context/participant.dart'; - import '../../types/theme.dart'; import '../debug/logger.dart'; import 'is_speaking_indicator.dart'; diff --git a/lib/src/ui/participant/participant_builder.dart b/lib/src/ui/participant/participant_builder.dart index 4869b52..d8fcc0e 100644 --- a/lib/src/ui/participant/participant_builder.dart +++ b/lib/src/ui/participant/participant_builder.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:livekit_client/livekit_client.dart'; +import 'package:livekit_client/livekit_client.dart'; import 'package:provider/provider.dart'; import '../../../livekit_components.dart'; diff --git a/lib/src/ui/participant/track_list_builder.dart b/lib/src/ui/participant/track_list_builder.dart index 0204a1c..758d532 100644 --- a/lib/src/ui/participant/track_list_builder.dart +++ b/lib/src/ui/participant/track_list_builder.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; + import 'package:livekit_client/livekit_client.dart'; import 'package:provider/provider.dart'; diff --git a/lib/src/ui/mediadevice/camera_preview.dart b/lib/src/ui/room/camera_preview.dart similarity index 96% rename from lib/src/ui/mediadevice/camera_preview.dart rename to lib/src/ui/room/camera_preview.dart index 67bfeb0..3758081 100644 --- a/lib/src/ui/mediadevice/camera_preview.dart +++ b/lib/src/ui/room/camera_preview.dart @@ -1,11 +1,12 @@ -import 'package:flutter/material.dart'; import 'dart:math' as math; +import 'package:flutter/material.dart'; + import 'package:livekit_client/livekit_client.dart'; -import 'package:livekit_components/livekit_components.dart'; -import 'package:livekit_components/src/context/media_device.dart'; import 'package:provider/provider.dart'; +import 'package:livekit_components/livekit_components.dart'; + class CameraPreview extends StatelessWidget { const CameraPreview({super.key}); diff --git a/lib/src/ui/room/control_bar.dart b/lib/src/ui/room/control_bar.dart index d9719a2..87aab52 100644 --- a/lib/src/ui/room/control_bar.dart +++ b/lib/src/ui/room/control_bar.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:livekit_components/src/ui/buttons/leave_button.dart'; + import 'package:provider/provider.dart'; -import 'package:livekit_components/livekit_components.dart'; -import '../buttons/chat_button.dart'; -import '../buttons/screen_share_button.dart'; +import 'package:livekit_components/livekit_components.dart'; +import 'package:livekit_components/src/ui/buttons/leave_button.dart'; class ControlBar extends StatelessWidget { const ControlBar( @@ -27,20 +26,25 @@ class ControlBar extends StatelessWidget { Widget build(BuildContext context) { return SizedBox( height: 80, - child: Consumer( - builder: (context, roomCtx, child) { - return Wrap( - runSpacing: 6, - direction: Axis.vertical, - children: [ - if (microphone) const MicrophoneSelectButton(), - if (camera) const CameraSelectButton(), - if (screenShare) const ScreenShareButton(), - if (chat) const ChatButton(), - if (leave) const LeaveButton(), - ], - ); - }, + child: Center( + child: Container( + padding: const EdgeInsets.fromLTRB(0, 0, 0, 30), + child: Consumer( + builder: (context, roomCtx, child) { + return Wrap( + runSpacing: 8, + direction: Axis.vertical, + children: [ + if (microphone) const MicrophoneSelectButton(), + if (camera) const CameraSelectButton(), + if (screenShare) const ScreenShareToggle(), + if (chat) const ChatToggle(), + if (leave) const LeaveButton(), + ], + ); + }, + ), + ), ), ); } diff --git a/test/livekit_components_test.dart b/test/livekit_components_test.dart index efd7773..0da434d 100644 --- a/test/livekit_components_test.dart +++ b/test/livekit_components_test.dart @@ -1,6 +1,5 @@ import 'package:flutter_test/flutter_test.dart'; - void main() { test('adds one to input values', () {}); }