From 07b2b43048091b14edf03371a99de1c82bae3541 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:11:02 +0800 Subject: [PATCH 01/24] Update lints ver --- analysis_options.yaml | 14 ++++++++++---- pubspec.yaml | 5 ++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 1923cb6..ad88315 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,6 +1,11 @@ include: package:lints/recommended.yaml analyzer: + language: + strict-casts: true + strict-inference: true + strict-raw-types: true + errors: constant_identifier_names: ignore must_be_immutable: ignore @@ -9,10 +14,11 @@ analyzer: linter: rules: - # - # Additional recommended rules - # prefer_single_quotes: true unnecessary_brace_in_string_interps: false - unawaited_futures: true depend_on_referenced_packages: false + unawaited_futures: true + avoid_print: true + +formatter: + trailing_commas: preserve diff --git a/pubspec.yaml b/pubspec.yaml index d72c2ce..8204ab2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ version: 1.2.2+hotfix.1 homepage: https://github.com/livekit/components-flutter environment: - sdk: ^3.5.1 + sdk: ">=3.6.0 <4.0.0" flutter: ">=1.17.0" dependencies: @@ -13,7 +13,6 @@ dependencies: flutter: sdk: flutter flutter_background: ^1.3.0+1 - flutter_webrtc: ^1.0.0 google_fonts: ^6.2.1 http: ^1.2.2 intl: ^0.20.2 @@ -24,7 +23,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^4.0.0 + flutter_lints: ^6.0.0 import_sorter: ^4.6.0 topics: From 0248b1eae3c931f53398bb5f9c797cef2d028040 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:13:57 +0800 Subject: [PATCH 02/24] Update pubspec.yaml --- pubspec.yaml | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 8204ab2..019e92b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -32,39 +32,3 @@ topics: - livestream - conference - agent -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. -# flutter: - -# To add assets to your package, add an assets section, like this: -# assets: -# - images/a_dot_burr.jpeg -# - images/a_dot_ham.jpeg -# -# For details regarding assets in packages, see -# https://flutter.dev/to/asset-from-package -# -# An image asset can refer to one or more resolution-specific "variants", see -# https://flutter.dev/to/resolution-aware-images - -# To add custom fonts to your package, add a fonts section here, -# in this "flutter" section. Each entry in this list should have a -# "family" key with the font family name, and a "fonts" key with a -# list giving the asset and other descriptors for the font. For -# example: -# fonts: -# - family: Schyler -# fonts: -# - asset: fonts/Schyler-Regular.ttf -# - asset: fonts/Schyler-Italic.ttf -# style: italic -# - family: Trajan Pro -# fonts: -# - asset: fonts/TrajanPro.ttf -# - asset: fonts/TrajanPro_Bold.ttf -# weight: 700 -# -# For details regarding fonts in packages, see -# https://flutter.dev/to/font-from-package From d43e8be1adf8441ae2f757348f7147bdb6fbcca1 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:14:51 +0800 Subject: [PATCH 03/24] Fixes 1 --- lib/src/context/chat_context.dart | 20 ++++++++------------ lib/src/context/media_device_context.dart | 2 +- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/lib/src/context/chat_context.dart b/lib/src/context/chat_context.dart index f6ad1d6..337bbf5 100644 --- a/lib/src/context/chat_context.dart +++ b/lib/src/context/chat_context.dart @@ -48,14 +48,13 @@ class ChatMessage { String toJson() => const JsonEncoder().convert(toMap()); factory ChatMessage.fromJsonString(String source, Participant? participant) => - ChatMessage.fromMap(const JsonDecoder().convert(source), participant); + ChatMessage.fromMap(const JsonDecoder().convert(source) as Map, participant); - factory ChatMessage.fromMap( - Map map, Participant? participant) { + factory ChatMessage.fromMap(Map map, Participant? participant) { return ChatMessage( - message: map['message'], - timestamp: map['timestamp'], - id: map['id'], + message: map['message'] as String, + timestamp: map['timestamp'] as int, + id: map['id'] as String, participant: participant, sender: false, ); @@ -68,8 +67,7 @@ mixin ChatContextMixin on ChangeNotifier { LocalParticipant? _localParticipant; EventsListener? _listener; - void chatContextSetup( - EventsListener? listener, LocalParticipant? localParticipant) { + void chatContextSetup(EventsListener? listener, LocalParticipant? localParticipant) { _listener = listener; _localParticipant = localParticipant; if (listener != null) { @@ -77,8 +75,7 @@ mixin ChatContextMixin on ChangeNotifier { Debug.event('ChatContext: DataReceivedEvent'); if (event.topic == 'lk-chat-topic') { - addMessageFromMap( - const Utf8Decoder().convert(event.data), event.participant); + addMessageFromMap(const Utf8Decoder().convert(event.data), event.participant); } }); } else { @@ -96,8 +93,7 @@ mixin ChatContextMixin on ChangeNotifier { participant: _localParticipant, ); addMessage(msg); - _localParticipant?.publishData(const Utf8Encoder().convert(msg.toJson()), - topic: 'lk-chat-topic'); + _localParticipant?.publishData(const Utf8Encoder().convert(msg.toJson()), topic: 'lk-chat-topic'); } void addMessage(ChatMessage message) { diff --git a/lib/src/context/media_device_context.dart b/lib/src/context/media_device_context.dart index fa7e96e..9ab0995 100644 --- a/lib/src/context/media_device_context.dart +++ b/lib/src/context/media_device_context.dart @@ -187,7 +187,7 @@ class MediaDeviceContext extends ChangeNotifier { bool get isScreenShareEnabled => _room?.localParticipant?.isScreenShareEnabled() ?? false; - Future enableScreenShare(context) async { + Future enableScreenShare(BuildContext context) async { if (lkPlatformIsDesktop()) { try { final source = await showDialog( From d026ddfcc83fd790373c69a48671013bec870643 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:20:57 +0800 Subject: [PATCH 04/24] lint fixes --- lib/livekit_components.dart | 2 -- lib/src/context/media_device_context.dart | 10 +++++----- lib/src/context/room_context.dart | 6 +++--- lib/src/ui/builder/room/camera_switch.dart | 2 +- lib/src/ui/builder/room/chat.dart | 2 +- lib/src/ui/builder/room/disconnect_button.dart | 2 +- lib/src/ui/builder/room/join_button.dart | 2 +- lib/src/ui/builder/room/screenshare_toggle.dart | 2 +- lib/src/ui/builder/room/speaker_switch.dart | 2 +- lib/src/ui/builder/room/transcription.dart | 2 +- lib/src/ui/prejoin/prejoin.dart | 2 +- lib/src/ui/prejoin/text_input.dart | 2 +- lib/src/ui/widgets/room/camera_switch_button.dart | 2 +- lib/src/ui/widgets/room/chat_toggle.dart | 2 +- lib/src/ui/widgets/room/chat_widget.dart | 4 ++-- lib/src/ui/widgets/room/disconnect_button.dart | 11 ++++++++--- lib/src/ui/widgets/room/join_button.dart | 2 +- .../ui/widgets/room/media_device_select_button.dart | 4 ++-- lib/src/ui/widgets/room/speaker_switch_button.dart | 2 +- lib/src/ui/widgets/toast.dart | 8 ++++---- 20 files changed, 37 insertions(+), 34 deletions(-) diff --git a/lib/livekit_components.dart b/lib/livekit_components.dart index 12f7918..61f4f3e 100644 --- a/lib/livekit_components.dart +++ b/lib/livekit_components.dart @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -library livekit_components; - export 'src/context/chat_context.dart'; export 'src/context/media_device_context.dart'; export 'src/context/participant_context.dart'; diff --git a/lib/src/context/media_device_context.dart b/lib/src/context/media_device_context.dart index 9ab0995..573beb3 100644 --- a/lib/src/context/media_device_context.dart +++ b/lib/src/context/media_device_context.dart @@ -62,14 +62,14 @@ class MediaDeviceContext extends ChangeNotifier { LocalAudioTrack? get localAudioTrack => _roomCtx.localAudioTrack; - StreamSubscription? _deviceChangeSub; + StreamSubscription>? _deviceChangeSub; Future loadDevices() async { _loadDevices(await Hardware.instance.enumerateDevices()); _deviceChangeSub = Hardware.instance.onDeviceChange.stream.listen(_loadDevices); } - _loadDevices(List devices) { + void _loadDevices(List devices) { _audioInputs = devices.where((d) => d.kind == 'audioinput').toList(); _audioOutputs = devices.where((d) => d.kind == 'audiooutput').toList(); _videoInputs = devices.where((d) => d.kind == 'videoinput').toList(); @@ -256,7 +256,7 @@ class MediaDeviceContext extends ChangeNotifier { } if (lkPlatformIsWebMobile()) { - await context.showErrorDialog('Screen share is not supported on mobile web'); + logger.warning('Screen share is not supported on mobile web'); return; } @@ -291,7 +291,7 @@ class MediaDeviceContext extends ChangeNotifier { try { await track.setCameraPosition(newPosition); } catch (error) { - print('could not restart track: $error'); + logger.warning('could not restart track: $error'); return; } notifyListeners(); @@ -307,7 +307,7 @@ class MediaDeviceContext extends ChangeNotifier { try { await track.setCameraPosition(newPosition); } catch (error) { - print('could not restart track: $error'); + logger.warning('could not restart track: $error'); return; } diff --git a/lib/src/context/room_context.dart b/lib/src/context/room_context.dart index e98ece3..ab2ceb8 100644 --- a/lib/src/context/room_context.dart +++ b/lib/src/context/room_context.dart @@ -198,11 +198,11 @@ class RoomContext extends ChangeNotifier notifyListeners(); } - final Function()? onConnected; + final void Function()? onConnected; - final Function()? onDisconnected; + final void Function()? onDisconnected; - final Function(LiveKitException? error)? onError; + final void Function(LiveKitException? error)? onError; final ConnectOptions? _connectOptions; FastConnectOptions? _fastConnectOptions; diff --git a/lib/src/ui/builder/room/camera_switch.dart b/lib/src/ui/builder/room/camera_switch.dart index 1eac20b..c78b09c 100644 --- a/lib/src/ui/builder/room/camera_switch.dart +++ b/lib/src/ui/builder/room/camera_switch.dart @@ -28,7 +28,7 @@ class CameraSwitch extends StatelessWidget { required this.builder, }); - final Function(BuildContext context, RoomContext roomCtx, + final Widget Function(BuildContext context, RoomContext roomCtx, MediaDeviceContext deviceCtx, CameraPosition? position) builder; @override diff --git a/lib/src/ui/builder/room/chat.dart b/lib/src/ui/builder/room/chat.dart index d924cc9..4306616 100644 --- a/lib/src/ui/builder/room/chat.dart +++ b/lib/src/ui/builder/room/chat.dart @@ -25,7 +25,7 @@ class ChatBuilder extends StatelessWidget { required this.builder, }); - final Function(BuildContext context, bool enabled, ChatContextMixin chatCtx, + final Widget Function(BuildContext context, bool enabled, ChatContextMixin chatCtx, List messages) builder; @override diff --git a/lib/src/ui/builder/room/disconnect_button.dart b/lib/src/ui/builder/room/disconnect_button.dart index bde1836..3a2176f 100644 --- a/lib/src/ui/builder/room/disconnect_button.dart +++ b/lib/src/ui/builder/room/disconnect_button.dart @@ -21,7 +21,7 @@ import '../../../context/room_context.dart'; class DisconnectButton extends StatelessWidget { const DisconnectButton({super.key, required this.builder}); - final Function(BuildContext context, RoomContext roomCtx, bool connected) + final Widget Function(BuildContext context, RoomContext roomCtx, bool connected) builder; @override diff --git a/lib/src/ui/builder/room/join_button.dart b/lib/src/ui/builder/room/join_button.dart index 5db5c5a..64a28d9 100644 --- a/lib/src/ui/builder/room/join_button.dart +++ b/lib/src/ui/builder/room/join_button.dart @@ -24,7 +24,7 @@ class JoinButton extends StatelessWidget { required this.builder, }); - final Function(BuildContext context, RoomContext roomCtx, bool connected) + final Widget Function(BuildContext context, RoomContext roomCtx, bool connected) builder; @override diff --git a/lib/src/ui/builder/room/screenshare_toggle.dart b/lib/src/ui/builder/room/screenshare_toggle.dart index 2b77fa7..6fae876 100644 --- a/lib/src/ui/builder/room/screenshare_toggle.dart +++ b/lib/src/ui/builder/room/screenshare_toggle.dart @@ -25,7 +25,7 @@ class ScreenShareToggle extends StatelessWidget { required this.builder, }); - final Function(BuildContext context, RoomContext roomCtx, + final Widget Function(BuildContext context, RoomContext roomCtx, MediaDeviceContext deviceCtx, bool screenShareEnabled) builder; @override diff --git a/lib/src/ui/builder/room/speaker_switch.dart b/lib/src/ui/builder/room/speaker_switch.dart index 8a10ea5..587496b 100644 --- a/lib/src/ui/builder/room/speaker_switch.dart +++ b/lib/src/ui/builder/room/speaker_switch.dart @@ -25,7 +25,7 @@ class SpeakerSwitch extends StatelessWidget { required this.builder, }); - final Function(BuildContext context, RoomContext roomCtx, + final Widget Function(BuildContext context, RoomContext roomCtx, MediaDeviceContext deviceCtx, bool? isSpeakerOn) builder; @override diff --git a/lib/src/ui/builder/room/transcription.dart b/lib/src/ui/builder/room/transcription.dart index 8a07dcb..648cabc 100644 --- a/lib/src/ui/builder/room/transcription.dart +++ b/lib/src/ui/builder/room/transcription.dart @@ -27,7 +27,7 @@ import '../../../types/transcription.dart'; /// The builder function receives the current build context and a list of transcriptions, /// allowing you to build custom UI components that display transcription data. class TranscriptionBuilder extends StatelessWidget { - final Function(BuildContext context, List transcriptions) builder; + final Widget Function(BuildContext context, List transcriptions) builder; const TranscriptionBuilder({ super.key, diff --git a/lib/src/ui/prejoin/prejoin.dart b/lib/src/ui/prejoin/prejoin.dart index daa2109..9bbe73f 100644 --- a/lib/src/ui/prejoin/prejoin.dart +++ b/lib/src/ui/prejoin/prejoin.dart @@ -30,7 +30,7 @@ class Prejoin extends StatelessWidget { Prejoin( {super.key, required this.token, required this.url, this.onJoinPressed}); - final Function(RoomContext roomCtx, String url, String token)? onJoinPressed; + final void Function(RoomContext roomCtx, String url, String token)? onJoinPressed; String token; diff --git a/lib/src/ui/prejoin/text_input.dart b/lib/src/ui/prejoin/text_input.dart index 36403ca..081139d 100644 --- a/lib/src/ui/prejoin/text_input.dart +++ b/lib/src/ui/prejoin/text_input.dart @@ -24,7 +24,7 @@ class TextInput extends StatelessWidget { final TextEditingController _textController; - final Function(String) onTextChanged; + final void Function(String) onTextChanged; final String hintText; diff --git a/lib/src/ui/widgets/room/camera_switch_button.dart b/lib/src/ui/widgets/room/camera_switch_button.dart index 139433a..38b7999 100644 --- a/lib/src/ui/widgets/room/camera_switch_button.dart +++ b/lib/src/ui/widgets/room/camera_switch_button.dart @@ -28,7 +28,7 @@ class CameraSwitchButton extends StatelessWidget { }); final CameraPosition? currentPosition; - final Function(CameraPosition position)? onToggle; + final void Function(CameraPosition position)? onToggle; final bool disabled; final Color backgroundColor; final Color foregroundColor; diff --git a/lib/src/ui/widgets/room/chat_toggle.dart b/lib/src/ui/widgets/room/chat_toggle.dart index bc9c26d..2269a05 100644 --- a/lib/src/ui/widgets/room/chat_toggle.dart +++ b/lib/src/ui/widgets/room/chat_toggle.dart @@ -32,7 +32,7 @@ class ChatToggleWidget extends StatelessWidget { }); final bool isChatOpen; - final Function(bool enabled) toggleChat; + final void Function(bool enabled) toggleChat; final bool showLabel; final Color backgroundColor; final Color foregroundColor; diff --git a/lib/src/ui/widgets/room/chat_widget.dart b/lib/src/ui/widgets/room/chat_widget.dart index 06600ef..4ee8e89 100644 --- a/lib/src/ui/widgets/room/chat_widget.dart +++ b/lib/src/ui/widgets/room/chat_widget.dart @@ -29,8 +29,8 @@ class ChatWidget extends StatelessWidget { }); final List messages; - final Function(String) onSend; - final Function() onClose; + final void Function(String) onSend; + final void Function() onClose; final ScrollController _scrollController = ScrollController(); List _buildMessages(List messages) { diff --git a/lib/src/ui/widgets/room/disconnect_button.dart b/lib/src/ui/widgets/room/disconnect_button.dart index 6d9c524..06ffdf3 100644 --- a/lib/src/ui/widgets/room/disconnect_button.dart +++ b/lib/src/ui/widgets/room/disconnect_button.dart @@ -45,7 +45,7 @@ class DisconnectButtonWidget extends StatelessWidget { final Color selectedColor; final Color selectedOverlayColor; - final Function()? onPressed; + final void Function()? onPressed; @override Widget build(BuildContext context) { @@ -70,8 +70,13 @@ class DisconnectButtonWidget extends StatelessWidget { : const EdgeInsets.fromLTRB(12, 20, 12, 20), ), ), - onPressed: () => - onPressed?.call() ?? connected ? roomCtx.disconnect() : null, + onPressed: () { + if (onPressed != null) { + onPressed!(); + } else if (connected) { + roomCtx.disconnect(); + } + }, child: Row( mainAxisSize: MainAxisSize.min, children: [ diff --git a/lib/src/ui/widgets/room/join_button.dart b/lib/src/ui/widgets/room/join_button.dart index 2f1baf0..ed9dfec 100644 --- a/lib/src/ui/widgets/room/join_button.dart +++ b/lib/src/ui/widgets/room/join_button.dart @@ -32,7 +32,7 @@ class JoinButtonWidget extends StatelessWidget { RoomContext roomCtx; bool connected; - final Function()? onPressed; + final void Function()? onPressed; final Color backgroundColor; final Color foregroundColor; diff --git a/lib/src/ui/widgets/room/media_device_select_button.dart b/lib/src/ui/widgets/room/media_device_select_button.dart index ca6baaa..98079ea 100644 --- a/lib/src/ui/widgets/room/media_device_select_button.dart +++ b/lib/src/ui/widgets/room/media_device_select_button.dart @@ -50,8 +50,8 @@ class MediaDeviceSelectWidget extends StatelessWidget { final Color foregroundColor; final String? selectedDeviceId; final List deviceList; - final Function(MediaDevice device)? onSelect; - final Function(bool enabled)? onToggle; + final void Function(MediaDevice device)? onSelect; + final void Function(bool enabled)? onToggle; final bool toggleAvailable; final bool defaultSelectable; diff --git a/lib/src/ui/widgets/room/speaker_switch_button.dart b/lib/src/ui/widgets/room/speaker_switch_button.dart index a35be40..d891969 100644 --- a/lib/src/ui/widgets/room/speaker_switch_button.dart +++ b/lib/src/ui/widgets/room/speaker_switch_button.dart @@ -26,7 +26,7 @@ class SpeakerSwitchButton extends StatelessWidget { }); final bool isSpeakerOn; - final Function(bool speakerOn)? onToggle; + final void Function(bool speakerOn)? onToggle; final bool disabled; final Color backgroundColor; diff --git a/lib/src/ui/widgets/toast.dart b/lib/src/ui/widgets/toast.dart index 2629f4d..4e4e95f 100644 --- a/lib/src/ui/widgets/toast.dart +++ b/lib/src/ui/widgets/toast.dart @@ -42,16 +42,16 @@ class ToastWidget extends StatefulWidget { class ToastState extends State with SingleTickerProviderStateMixin { AnimationController? _animationController; - late Animation _fadeAnimation; + late Animation _fadeAnimation; Timer? _timer; ConnectionState _connectionState = ConnectionState.disconnected; - showIt() { + void showIt() { _animationController!.forward(); } - hideIt() { + void hideIt() { _animationController!.reverse(); _timer?.cancel(); _timer = null; @@ -107,7 +107,7 @@ class ToastState extends State child: IgnorePointer( ignoring: widget.ignorePointer, child: FadeTransition( - opacity: _fadeAnimation as Animation, + opacity: _fadeAnimation, child: Center( child: Material( color: Colors.transparent, From 7f8b66d676ba7f3c22567fa9e9139cd22f31c01e Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:25:56 +0800 Subject: [PATCH 05/24] format -l 120 --- example/lib/main.dart | 50 +++------ lib/src/context/participant_context.dart | 39 +++---- lib/src/context/room_context.dart | 27 ++--- lib/src/context/track_reference_context.dart | 30 ++---- lib/src/types/track_identifier.dart | 7 +- lib/src/ui/builder/camera_preview.dart | 6 +- .../participant/participant_attributes.dart | 9 +- .../builder/participant/participant_kind.dart | 3 +- .../builder/participant/participant_loop.dart | 18 ++-- .../participant/participant_metadata.dart | 6 +- .../participant_muted_indicator.dart | 6 +- .../builder/participant/participant_name.dart | 3 +- .../participant/participant_permissions.dart | 9 +- .../participant/participant_selector.dart | 9 +- .../participant_transcription.dart | 9 +- lib/src/ui/builder/room/camera_switch.dart | 4 +- lib/src/ui/builder/room/chat.dart | 4 +- lib/src/ui/builder/room/chat_toggle.dart | 6 +- .../ui/builder/room/disconnect_button.dart | 6 +- lib/src/ui/builder/room/join_button.dart | 6 +- lib/src/ui/builder/room/media_device.dart | 6 +- .../room/media_device_select_button.dart | 3 +- lib/src/ui/builder/room/room.dart | 6 +- .../room/room_active_recording_indicator.dart | 3 +- .../builder/room/room_connection_state.dart | 6 +- .../ui/builder/room/room_participants.dart | 3 +- .../ui/builder/room/screenshare_toggle.dart | 7 +- lib/src/ui/builder/room/speaker_switch.dart | 4 +- .../track/connection_quality_indicator.dart | 15 +-- .../track/e2e_encryption_indicator.dart | 6 +- .../builder/track/is_speaking_indicator.dart | 3 +- lib/src/ui/layout/carousel_layout.dart | 6 +- lib/src/ui/layout/grid_layout.dart | 5 +- lib/src/ui/layout/sorting.dart | 3 +- lib/src/ui/prejoin/prejoin.dart | 15 +-- lib/src/ui/prejoin/text_input.dart | 9 +- lib/src/ui/widgets/camera_preview.dart | 4 +- .../connection_quality_indicator.dart | 4 +- .../participant/participant_status_bar.dart | 100 +++++++++--------- .../participant/participant_tile_widget.dart | 3 +- .../ui/widgets/room/camera_select_button.dart | 3 +- .../ui/widgets/room/camera_switch_button.dart | 16 ++- lib/src/ui/widgets/room/chat_toggle.dart | 7 +- lib/src/ui/widgets/room/chat_widget.dart | 11 +- lib/src/ui/widgets/room/control_bar.dart | 26 ++--- .../ui/widgets/room/disconnect_button.dart | 6 +- lib/src/ui/widgets/room/join_button.dart | 6 +- .../room/media_device_select_button.dart | 39 +++---- .../room/microphone_select_button.dart | 4 +- .../ui/widgets/room/screenshare_toggle.dart | 16 +-- .../widgets/room/speaker_switch_button.dart | 7 +- lib/src/ui/widgets/theme.dart | 10 +- lib/src/ui/widgets/toast.dart | 9 +- lib/src/ui/widgets/track/focus_toggle.dart | 6 +- .../ui/widgets/track/track_stats_widget.dart | 15 +-- 55 files changed, 230 insertions(+), 419 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 46e4108..81e7277 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -118,8 +118,7 @@ class _MyHomePageState extends State { var deviceScreenType = getDeviceType(MediaQuery.of(context).size); return Scaffold( appBar: AppBar( - title: const Text('LiveKit Components', - style: TextStyle(color: Colors.white)), + title: const Text('LiveKit Components', style: TextStyle(color: Colors.white)), actions: [ /// show clear pin button if (roomCtx.connected) const ClearPinButton(), @@ -141,16 +140,13 @@ class _MyHomePageState extends State { Row( children: [ /// show chat widget on mobile - (deviceScreenType == DeviceScreenType.mobile && - roomCtx.isChatEnabled) + (deviceScreenType == DeviceScreenType.mobile && roomCtx.isChatEnabled) ? Expanded( child: ChatBuilder( - builder: - (context, enabled, chatCtx, messages) { + builder: (context, enabled, chatCtx, messages) { return ChatWidget( messages: messages, - onSend: (message) => - chatCtx.sendMessage(message), + onSend: (message) => chatCtx.sendMessage(message), onClose: () { chatCtx.toggleChat(false); }, @@ -179,37 +175,28 @@ class _MyHomePageState extends State { showParticipantPlaceholder: true, /// layout builder - layoutBuilder: - roomCtx.pinnedTracks.isNotEmpty - ? const CarouselLayoutBuilder() - : const GridLayoutBuilder(), + layoutBuilder: roomCtx.pinnedTracks.isNotEmpty + ? const CarouselLayoutBuilder() + : const GridLayoutBuilder(), /// participant builder - participantTrackBuilder: - (context, identifier) { + participantTrackBuilder: (context, identifier) { // build participant widget for each Track return Padding( padding: const EdgeInsets.all(2.0), child: Stack( children: [ /// video track widget in the background - identifier.isAudio && - roomCtx - .enableAudioVisulizer + identifier.isAudio && roomCtx.enableAudioVisulizer ? const AudioVisualizerWidget( - backgroundColor: - LKColors.lkDarkBlue, + backgroundColor: LKColors.lkDarkBlue, ) : IsSpeakingIndicator( - builder: (context, - isSpeaking) { - return isSpeaking != - null + builder: (context, isSpeaking) { + return isSpeaking != null ? IsSpeakingIndicatorWidget( - isSpeaking: - isSpeaking, - child: - const VideoTrackWidget(), + isSpeaking: isSpeaking, + child: const VideoTrackWidget(), ) : const VideoTrackWidget(); }, @@ -254,19 +241,16 @@ class _MyHomePageState extends State { ), /// show chat widget on desktop - (deviceScreenType != DeviceScreenType.mobile && - roomCtx.isChatEnabled) + (deviceScreenType != DeviceScreenType.mobile && roomCtx.isChatEnabled) ? Expanded( flex: 2, child: SizedBox( width: 400, child: ChatBuilder( - builder: - (context, enabled, chatCtx, messages) { + builder: (context, enabled, chatCtx, messages) { return ChatWidget( messages: messages, - onSend: (message) => - chatCtx.sendMessage(message), + onSend: (message) => chatCtx.sendMessage(message), onClose: () { chatCtx.toggleChat(false); }, diff --git a/lib/src/context/participant_context.dart b/lib/src/context/participant_context.dart index 12a2b44..9f5a901 100644 --- a/lib/src/context/participant_context.dart +++ b/lib/src/context/participant_context.dart @@ -28,12 +28,10 @@ class ParticipantContext extends ChangeNotifier { return Provider.of(context); } - ParticipantContext(this._participant) - : _listener = _participant.createListener() { + ParticipantContext(this._participant) : _listener = _participant.createListener() { _listener ..on((event) { - if (event.participant.identity == identity && - isSpeaking != event.speaking) { + if (event.participant.identity == identity && isSpeaking != event.speaking) { Debug.event( 'ParticipantContext: SpeakingChangedEvent identity = ${_participant.identity}, speaking = ${event.speaking}'); _isSpeaking = event.speaking; @@ -41,14 +39,12 @@ class ParticipantContext extends ChangeNotifier { } }) ..on((event) { - Debug.event( - 'ParticipantContext: ParticipantNameUpdatedEvent name = ${event.name}'); + Debug.event('ParticipantContext: ParticipantNameUpdatedEvent name = ${event.name}'); notifyListeners(); }) ..on((event) { if (event.metadata != _metadata) { - Debug.event( - 'ParticipantContext: ParticipantMetadataUpdatedEvent metadata = ${event.metadata}'); + Debug.event('ParticipantContext: ParticipantMetadataUpdatedEvent metadata = ${event.metadata}'); _metadata = event.metadata; notifyListeners(); @@ -67,10 +63,8 @@ class ParticipantContext extends ChangeNotifier { if (_permissions?.canPublish != event.permissions.canPublish || _permissions?.canSubscribe != event.permissions.canSubscribe || _permissions?.canPublishData != event.permissions.canPublishData || - _permissions?.canUpdateMetadata != - event.permissions.canUpdateMetadata || - _permissions?.canPublishSources != - event.permissions.canPublishSources) { + _permissions?.canUpdateMetadata != event.permissions.canUpdateMetadata || + _permissions?.canPublishSources != event.permissions.canPublishSources) { Debug.event( 'ParticipantContext: ParticipantPermissionsUpdatedEvent permissions canPublish = ${event.permissions.canPublish}, canSubscribe = ${event.permissions.canSubscribe}, canPublishData = ${event.permissions.canPublishData}, canUpdateMetadata = ${event.permissions.canUpdateMetadata}, canPublishSources = ${event.permissions.canPublishSources}'); _permissions = event.permissions; @@ -86,23 +80,19 @@ class ParticipantContext extends ChangeNotifier { notifyListeners(); }) ..on((event) { - Debug.event( - 'ParticipantContext: ParticipantAttributesChanged attributes = ${event.attributes}'); + Debug.event('ParticipantContext: ParticipantAttributesChanged attributes = ${event.attributes}'); _attributes = event.attributes; notifyListeners(); }) ..on((event) { - if (event.participant.identity == identity && - event.publication.kind == TrackType.AUDIO) { + if (event.participant.identity == identity && event.publication.kind == TrackType.AUDIO) { Debug.event('ParticipantContext: TrackMutedEvent for ${_participant.sid}'); notifyListeners(); } }) ..on((event) { - if (event.participant.identity == identity && - event.publication.kind == TrackType.AUDIO) { - Debug.event( - 'ParticipantContext: TrackUnmutedEvent for ${_participant.sid}'); + if (event.participant.identity == identity && event.publication.kind == TrackType.AUDIO) { + Debug.event('ParticipantContext: TrackUnmutedEvent for ${_participant.sid}'); notifyListeners(); } }) @@ -132,14 +122,12 @@ class ParticipantContext extends ChangeNotifier { bool get isLocal => _participant is LocalParticipant; - List get tracks => - _participant.trackPublications.values.toList(); + List get tracks => _participant.trackPublications.values.toList(); final Participant _participant; final EventsListener _listener; - bool get isEncrypted => - _participant.trackPublications.isNotEmpty && _participant.isEncrypted; + bool get isEncrypted => _participant.trackPublications.isNotEmpty && _participant.isEncrypted; String get identity => _participant.identity; @@ -152,8 +140,7 @@ class ParticipantContext extends ChangeNotifier { String? _metadata; String? get metadata => _metadata; - String get name => - _participant.name == '' ? _participant.identity : _participant.name; + String get name => _participant.name == '' ? _participant.identity : _participant.name; bool get isMuted => _participant.isMuted; diff --git a/lib/src/context/room_context.dart b/lib/src/context/room_context.dart index ab2ceb8..7097564 100644 --- a/lib/src/context/room_context.dart +++ b/lib/src/context/room_context.dart @@ -23,8 +23,7 @@ import 'package:livekit_components/src/context/transcription_context.dart'; import '../debug/logger.dart'; import 'chat_context.dart'; -class RoomContext extends ChangeNotifier - with ChatContextMixin, TranscriptionContextMixin { +class RoomContext extends ChangeNotifier with ChatContextMixin, TranscriptionContextMixin { /// Get the [RoomContext] from the [context]. /// this method must be called under the [LivekitRoom] widget. static RoomContext? of(BuildContext context) { @@ -96,27 +95,22 @@ class RoomContext extends ChangeNotifier notifyListeners(); }) ..on((event) { - Debug.event( - 'RoomContext: RoomMetadataChangedEvent $roomName metadata = ${event.metadata}'); + Debug.event('RoomContext: RoomMetadataChangedEvent $roomName metadata = ${event.metadata}'); _roomMetadata = event.metadata; notifyListeners(); }) ..on((event) { - Debug.event( - 'RoomContext: RoomRecordingStatusChanged activeRecording = ${event.activeRecording}'); + Debug.event('RoomContext: RoomRecordingStatusChanged activeRecording = ${event.activeRecording}'); _activeRecording = event.activeRecording; notifyListeners(); }) ..on((event) { - Debug.event( - 'RoomContext: ParticipantConnectedEvent $roomName participant = ${event.participant.identity}'); + Debug.event('RoomContext: ParticipantConnectedEvent $roomName participant = ${event.participant.identity}'); _buildParticipants(); }) ..on((event) { - Debug.event( - 'RoomContext: ParticipantDisconnectedEvent $roomName participant = ${event.participant.identity}'); - _participants - .removeWhere((p) => p.identity == event.participant.identity); + Debug.event('RoomContext: ParticipantDisconnectedEvent $roomName participant = ${event.participant.identity}'); + _participants.removeWhere((p) => p.identity == event.participant.identity); notifyListeners(); }) ..on((event) { @@ -130,13 +124,11 @@ class RoomContext extends ChangeNotifier _buildParticipants(); }) ..on((event) { - Debug.event( - 'RoomContext: LocalTrackPublishedEvent track = ${event.publication.sid}'); + Debug.event('RoomContext: LocalTrackPublishedEvent track = ${event.publication.sid}'); _buildParticipants(); }) ..on((event) { - Debug.event( - 'RoomContext: LocalTrackUnpublishedEvent track = ${event.publication.sid}'); + Debug.event('RoomContext: LocalTrackUnpublishedEvent track = ${event.publication.sid}'); _buildParticipants(); }) ..on((event) { @@ -320,8 +312,7 @@ class RoomContext extends ChangeNotifier bool get microphoneOpened => isMicrophoneEnabled ?? _localAudioTrack != null; - bool? get isMicrophoneEnabled => - _room.localParticipant?.isMicrophoneEnabled(); + bool? get isMicrophoneEnabled => _room.localParticipant?.isMicrophoneEnabled(); Future resetLocalTracks() async { _localAudioTrack = null; diff --git a/lib/src/context/track_reference_context.dart b/lib/src/context/track_reference_context.dart index 87e9af8..561b97e 100644 --- a/lib/src/context/track_reference_context.dart +++ b/lib/src/context/track_reference_context.dart @@ -39,29 +39,25 @@ class TrackReferenceContext extends ChangeNotifier { }) ..on((event) { if (event.publication.sid == pub?.sid) { - Debug.event( - 'TrackContext: TrackUnmutedEvent for ${_participant.sid}'); + Debug.event('TrackContext: TrackUnmutedEvent for ${_participant.sid}'); notifyListeners(); } }) ..on((event) { if (event.publication.sid == pub?.sid) { - Debug.event( - 'TrackContext: LocalTrackPublishedEvent for ${_participant.sid}'); + Debug.event('TrackContext: LocalTrackPublishedEvent for ${_participant.sid}'); notifyListeners(); } }) ..on((event) { if (event.publication.sid == pub?.sid) { - Debug.event( - 'TrackContext: TrackSubscribedEvent for ${_participant.sid}'); + Debug.event('TrackContext: TrackSubscribedEvent for ${_participant.sid}'); notifyListeners(); } }) ..on((event) { if (event.publication.sid == pub?.sid) { - Debug.event( - 'TrackContext: TrackStreamStateUpdatedEvent for ${_participant.sid}'); + Debug.event('TrackContext: TrackStreamStateUpdatedEvent for ${_participant.sid}'); notifyListeners(); } }); @@ -140,17 +136,14 @@ class TrackReferenceContext extends ChangeNotifier { stats['layer-$key'] = '${value.frameWidth ?? 0}x${value.frameHeight ?? 0} ${value.framesPerSecond?.toDouble() ?? 0} fps, ${event.bitrateForLayers[key] ?? 0} kbps'; }); - var firstStats = - event.stats['f'] ?? event.stats['h'] ?? event.stats['q']; + var firstStats = event.stats['f'] ?? event.stats['h'] ?? event.stats['q']; if (firstStats != null) { stats['encoder'] = firstStats.encoderImplementation ?? ''; if (firstStats.mimeType != null) { - stats['codec'] = - '${firstStats.mimeType!.split('/')[1]}/${firstStats.clockRate}'; + stats['codec'] = '${firstStats.mimeType!.split('/')[1]}/${firstStats.clockRate}'; } stats['payload'] = '${firstStats.payloadType}'; - stats['qualityLimitationReason'] = - firstStats.qualityLimitationReason ?? ''; + stats['qualityLimitationReason'] = firstStats.qualityLimitationReason ?? ''; } _stats = stats; @@ -161,8 +154,7 @@ class TrackReferenceContext extends ChangeNotifier { Map stats = {}; stats['rx'] = '${event.currentBitrate.toInt()} kpbs'; if (event.stats.mimeType != null) { - stats['codec'] = - '${event.stats.mimeType!.split('/')[1]}/${event.stats.clockRate}'; + stats['codec'] = '${event.stats.mimeType!.split('/')[1]}/${event.stats.clockRate}'; } stats['payload'] = '${event.stats.payloadType}'; stats['size/fps'] = @@ -183,8 +175,7 @@ class TrackReferenceContext extends ChangeNotifier { Map stats = {}; stats['tx'] = '${event.currentBitrate.toInt()} kpbs'; if (event.stats.mimeType != null) { - stats['codec'] = - '${event.stats.mimeType!.split('/')[1]}/${event.stats.clockRate}/${event.stats.channels}'; + stats['codec'] = '${event.stats.mimeType!.split('/')[1]}/${event.stats.clockRate}/${event.stats.channels}'; } stats['payload'] = '${event.stats.payloadType}'; _stats = stats; @@ -196,8 +187,7 @@ class TrackReferenceContext extends ChangeNotifier { stats['rx'] = '${event.currentBitrate.toInt()} kpbs'; if (event.stats.mimeType != null) { - stats['codec'] = - '${event.stats.mimeType!.split('/')[1]}/${event.stats.clockRate}/${event.stats.channels}'; + stats['codec'] = '${event.stats.mimeType!.split('/')[1]}/${event.stats.clockRate}/${event.stats.channels}'; } stats['payload'] = '${event.stats.payloadType}'; stats['jitter'] = '${event.stats.jitter} s'; diff --git a/lib/src/types/track_identifier.dart b/lib/src/types/track_identifier.dart index f9e2a45..39ebe0b 100644 --- a/lib/src/types/track_identifier.dart +++ b/lib/src/types/track_identifier.dart @@ -32,13 +32,10 @@ class TrackIdentifier { TrackSource get source => track?.source ?? TrackSource.unknown; /// Returns true if the track is an audio source. - bool get isAudio => - source == TrackSource.microphone || - source == TrackSource.screenShareAudio; + bool get isAudio => source == TrackSource.microphone || source == TrackSource.screenShareAudio; /// Returns true if the track is a video source. - bool get isVideo => - source == TrackSource.camera || source == TrackSource.screenShareVideo; + bool get isVideo => source == TrackSource.camera || source == TrackSource.screenShareVideo; /// Returns true if the participant is local. bool get isLocal => participant is LocalParticipant; diff --git a/lib/src/ui/builder/camera_preview.dart b/lib/src/ui/builder/camera_preview.dart index 99efd1d..b31e38c 100644 --- a/lib/src/ui/builder/camera_preview.dart +++ b/lib/src/ui/builder/camera_preview.dart @@ -25,14 +25,12 @@ class CameraPreview extends StatelessWidget { required this.builder, }); - final Widget Function(BuildContext context, LocalVideoTrack? videoTrack) - builder; + final Widget Function(BuildContext context, LocalVideoTrack? videoTrack) builder; @override Widget build(BuildContext context) { return Consumer( - builder: (context, roomCtx, child) => - Selector( + builder: (context, roomCtx, child) => Selector( selector: (context, track) => roomCtx.localVideoTrack, builder: (context, track, child) => builder(context, track), ), diff --git a/lib/src/ui/builder/participant/participant_attributes.dart b/lib/src/ui/builder/participant/participant_attributes.dart index 05a6106..4ff94e8 100644 --- a/lib/src/ui/builder/participant/participant_attributes.dart +++ b/lib/src/ui/builder/participant/participant_attributes.dart @@ -25,15 +25,12 @@ class ParticipantAttributes extends StatelessWidget { required this.builder, }); - final Widget Function(BuildContext context, Map? attributes) - builder; + final Widget Function(BuildContext context, Map? attributes) builder; @override Widget build(BuildContext context) { - return Consumer( - builder: (context, participantContext, child) { - Debug.log( - '====> ParticipantAttributes for ${participantContext.attributes}'); + return Consumer(builder: (context, participantContext, child) { + Debug.log('====> ParticipantAttributes for ${participantContext.attributes}'); return Selector?>( selector: (context, attributes) => participantContext.attributes, builder: (context, attributes, child) { diff --git a/lib/src/ui/builder/participant/participant_kind.dart b/lib/src/ui/builder/participant/participant_kind.dart index f6a4b86..0d7d709 100644 --- a/lib/src/ui/builder/participant/participant_kind.dart +++ b/lib/src/ui/builder/participant/participant_kind.dart @@ -30,8 +30,7 @@ class ParticipantKind extends StatelessWidget { @override Widget build(BuildContext context) { - return Consumer( - builder: (context, participantContext, child) { + return Consumer(builder: (context, participantContext, child) { Debug.log('====> ParticipantKind for ${participantContext.kind}'); return Selector( selector: (context, name) => participantContext.kind, diff --git a/lib/src/ui/builder/participant/participant_loop.dart b/lib/src/ui/builder/participant/participant_loop.dart index ac9cc7b..88b5f40 100644 --- a/lib/src/ui/builder/participant/participant_loop.dart +++ b/lib/src/ui/builder/participant/participant_loop.dart @@ -25,8 +25,7 @@ import '../../layout/layouts.dart'; import '../../layout/sorting.dart'; import 'participant_track.dart'; -typedef PaticipantTrackBuilder = Widget Function( - BuildContext context, TrackIdentifier identifier); +typedef PaticipantTrackBuilder = Widget Function(BuildContext context, TrackIdentifier identifier); class ParticipantLoop extends StatelessWidget { const ParticipantLoop({ @@ -64,8 +63,7 @@ class ParticipantLoop extends StatelessWidget { } trackMap.add(MapEntry(TrackIdentifier(participant, track), track)); - Debug.log( - '=> ${track.source.toString()} track ${track.sid} for ${participant.identity}'); + Debug.log('=> ${track.source.toString()} track ${track.sid} for ${participant.identity}'); } if (!audio && !tracks.any((t) => t.kind == TrackType.VIDEO) || @@ -92,8 +90,7 @@ class ParticipantLoop extends StatelessWidget { builder: (context, participants, child) { List trackWidgets = []; - var trackMap = buildTracksMap( - showAudioTracks, showVideoTracks, participants); + var trackMap = buildTracksMap(showAudioTracks, showVideoTracks, participants); for (var item in trackMap) { var identifier = item.key; @@ -105,8 +102,7 @@ class ParticipantLoop extends StatelessWidget { ParticipantTrack( participant: identifier.participant, track: track, - builder: (context) => - participantTrackBuilder(context, identifier), + builder: (context) => participantTrackBuilder(context, identifier), ), ), ); @@ -116,8 +112,7 @@ class ParticipantLoop extends StatelessWidget { identifier, ParticipantTrack( participant: identifier.participant, - builder: (context) => - participantTrackBuilder(context, identifier), + builder: (context) => participantTrackBuilder(context, identifier), ), ), ); @@ -129,8 +124,7 @@ class ParticipantLoop extends StatelessWidget { return Selector>( selector: (context, pinnedTracks) => roomCtx.pinnedTracks, builder: (context, pinnedTracks, child) { - return layoutBuilder.build( - context, trackWidgets, pinnedTracks); + return layoutBuilder.build(context, trackWidgets, pinnedTracks); }); }); }, diff --git a/lib/src/ui/builder/participant/participant_metadata.dart b/lib/src/ui/builder/participant/participant_metadata.dart index 71e6f41..9c52931 100644 --- a/lib/src/ui/builder/participant/participant_metadata.dart +++ b/lib/src/ui/builder/participant/participant_metadata.dart @@ -29,10 +29,8 @@ class ParticipantMetadata extends StatelessWidget { @override Widget build(BuildContext context) { - return Consumer( - builder: (context, participantContext, child) { - Debug.log( - '====> ParticipantMetadata for ${participantContext.metadata}'); + return Consumer(builder: (context, participantContext, child) { + Debug.log('====> ParticipantMetadata for ${participantContext.metadata}'); return Selector( selector: (context, metadata) => participantContext.metadata, builder: (context, metadata, child) { diff --git a/lib/src/ui/builder/participant/participant_muted_indicator.dart b/lib/src/ui/builder/participant/participant_muted_indicator.dart index e0ebc6a..a028937 100644 --- a/lib/src/ui/builder/participant/participant_muted_indicator.dart +++ b/lib/src/ui/builder/participant/participant_muted_indicator.dart @@ -29,10 +29,8 @@ class ParticipantMutedIndicator extends StatelessWidget { @override Widget build(BuildContext context) { - return Consumer( - builder: (context, participantContext, child) { - Debug.log( - '====> ParticipantMutedIndicator for ${participantContext.name}'); + return Consumer(builder: (context, participantContext, child) { + Debug.log('====> ParticipantMutedIndicator for ${participantContext.name}'); return Selector( selector: (context, isMuted) => participantContext.isMuted, builder: (context, isMuted, child) => builder(context, isMuted), diff --git a/lib/src/ui/builder/participant/participant_name.dart b/lib/src/ui/builder/participant/participant_name.dart index d898277..d37c7c5 100644 --- a/lib/src/ui/builder/participant/participant_name.dart +++ b/lib/src/ui/builder/participant/participant_name.dart @@ -30,8 +30,7 @@ class ParticipantName extends StatelessWidget { @override Widget build(BuildContext context) { - return Consumer( - builder: (context, participantContext, child) { + return Consumer(builder: (context, participantContext, child) { Debug.log('====> ParticipantName for ${participantContext.name}'); var trackCtx = Provider.of(context); bool isScreenShare = trackCtx?.isScreenShare ?? false; diff --git a/lib/src/ui/builder/participant/participant_permissions.dart b/lib/src/ui/builder/participant/participant_permissions.dart index b3de588..4e1048f 100644 --- a/lib/src/ui/builder/participant/participant_permissions.dart +++ b/lib/src/ui/builder/participant/participant_permissions.dart @@ -26,15 +26,12 @@ class ParticipantPermissions extends StatelessWidget { required this.builder, }); - final Widget Function(BuildContext context, lk.ParticipantPermissions?) - builder; + final Widget Function(BuildContext context, lk.ParticipantPermissions?) builder; @override Widget build(BuildContext context) { - return Consumer( - builder: (context, participantContext, child) { - Debug.log( - '====> ParticipantPermissions for ${participantContext.permissions}'); + return Consumer(builder: (context, participantContext, child) { + Debug.log('====> ParticipantPermissions for ${participantContext.permissions}'); return Selector( selector: (context, permissions) => participantContext.permissions, builder: (context, permissions, child) { diff --git a/lib/src/ui/builder/participant/participant_selector.dart b/lib/src/ui/builder/participant/participant_selector.dart index 4cd59f4..781811b 100644 --- a/lib/src/ui/builder/participant/participant_selector.dart +++ b/lib/src/ui/builder/participant/participant_selector.dart @@ -11,8 +11,7 @@ import 'participant_track.dart'; class ParticipantSelector extends StatelessWidget { final bool Function(TrackIdentifier identifier) filter; - final Widget Function(BuildContext context, TrackIdentifier identifier) - builder; + final Widget Function(BuildContext context, TrackIdentifier identifier) builder; const ParticipantSelector({ required this.filter, required this.builder, @@ -36,8 +35,7 @@ class ParticipantSelector extends StatelessWidget { } trackMap.add(MapEntry(TrackIdentifier(participant, track), track)); - Debug.log( - '=> ${track.source.toString()} track ${track.sid} for ${participant.identity}'); + Debug.log('=> ${track.source.toString()} track ${track.sid} for ${participant.identity}'); } if (!audio && !tracks.any((t) => t.kind == TrackType.VIDEO) || @@ -61,8 +59,7 @@ class ParticipantSelector extends StatelessWidget { shouldRebuild: (previous, next) => previous.length != next.length, builder: (context, participants, child) { var trackMap = buildTracksMap(true, true, participants); - var identifier = - trackMap.firstWhereOrNull((entry) => filter(entry.key))?.key; + var identifier = trackMap.firstWhereOrNull((entry) => filter(entry.key))?.key; if (identifier == null) { return const SizedBox(); diff --git a/lib/src/ui/builder/participant/participant_transcription.dart b/lib/src/ui/builder/participant/participant_transcription.dart index 523d23b..3b1a682 100644 --- a/lib/src/ui/builder/participant/participant_transcription.dart +++ b/lib/src/ui/builder/participant/participant_transcription.dart @@ -26,15 +26,12 @@ class ParticipantTranscription extends StatelessWidget { required this.builder, }); - final Widget Function( - BuildContext context, List segments) builder; + final Widget Function(BuildContext context, List segments) builder; @override Widget build(BuildContext context) { - return Consumer( - builder: (context, participantContext, child) { - Debug.log( - '====> ParticipantTranscription for ${participantContext.segments}'); + return Consumer(builder: (context, participantContext, child) { + Debug.log('====> ParticipantTranscription for ${participantContext.segments}'); return Selector>( selector: (context, segments) => participantContext.segments, builder: (context, segments, child) { diff --git a/lib/src/ui/builder/room/camera_switch.dart b/lib/src/ui/builder/room/camera_switch.dart index c78b09c..291c01f 100644 --- a/lib/src/ui/builder/room/camera_switch.dart +++ b/lib/src/ui/builder/room/camera_switch.dart @@ -28,8 +28,8 @@ class CameraSwitch extends StatelessWidget { required this.builder, }); - final Widget Function(BuildContext context, RoomContext roomCtx, - MediaDeviceContext deviceCtx, CameraPosition? position) builder; + final Widget Function( + BuildContext context, RoomContext roomCtx, MediaDeviceContext deviceCtx, CameraPosition? position) builder; @override Widget build(BuildContext context) { diff --git a/lib/src/ui/builder/room/chat.dart b/lib/src/ui/builder/room/chat.dart index 4306616..b4aee50 100644 --- a/lib/src/ui/builder/room/chat.dart +++ b/lib/src/ui/builder/room/chat.dart @@ -25,8 +25,8 @@ class ChatBuilder extends StatelessWidget { required this.builder, }); - final Widget Function(BuildContext context, bool enabled, ChatContextMixin chatCtx, - List messages) builder; + final Widget Function(BuildContext context, bool enabled, ChatContextMixin chatCtx, List messages) + builder; @override Widget build(BuildContext context) { diff --git a/lib/src/ui/builder/room/chat_toggle.dart b/lib/src/ui/builder/room/chat_toggle.dart index aed7955..86d936a 100644 --- a/lib/src/ui/builder/room/chat_toggle.dart +++ b/lib/src/ui/builder/room/chat_toggle.dart @@ -21,16 +21,14 @@ import '../../../context/room_context.dart'; class ChatToggle extends StatelessWidget { const ChatToggle({super.key, required this.builder}); - final Widget Function( - BuildContext context, RoomContext roomCtx, bool isChatEnabled) builder; + final Widget Function(BuildContext context, RoomContext roomCtx, bool isChatEnabled) builder; @override Widget build(BuildContext context) { return Consumer(builder: (context, roomCtx, child) { return Selector( selector: (context, isChatEnabled) => roomCtx.isChatEnabled, - builder: (context, isChatEnabled, child) => - builder(context, roomCtx, isChatEnabled), + builder: (context, isChatEnabled, child) => builder(context, roomCtx, isChatEnabled), ); }); } diff --git a/lib/src/ui/builder/room/disconnect_button.dart b/lib/src/ui/builder/room/disconnect_button.dart index 3a2176f..fccc286 100644 --- a/lib/src/ui/builder/room/disconnect_button.dart +++ b/lib/src/ui/builder/room/disconnect_button.dart @@ -21,8 +21,7 @@ import '../../../context/room_context.dart'; class DisconnectButton extends StatelessWidget { const DisconnectButton({super.key, required this.builder}); - final Widget Function(BuildContext context, RoomContext roomCtx, bool connected) - builder; + final Widget Function(BuildContext context, RoomContext roomCtx, bool connected) builder; @override Widget build(BuildContext context) { @@ -30,8 +29,7 @@ class DisconnectButton extends StatelessWidget { builder: (context, roomCtx, child) { return Selector( selector: (context, connected) => roomCtx.connected, - builder: (context, connected, child) => - builder(context, roomCtx, connected), + builder: (context, connected, child) => builder(context, roomCtx, connected), ); }, ); diff --git a/lib/src/ui/builder/room/join_button.dart b/lib/src/ui/builder/room/join_button.dart index 64a28d9..777abba 100644 --- a/lib/src/ui/builder/room/join_button.dart +++ b/lib/src/ui/builder/room/join_button.dart @@ -24,16 +24,14 @@ class JoinButton extends StatelessWidget { required this.builder, }); - final Widget Function(BuildContext context, RoomContext roomCtx, bool connected) - builder; + final Widget Function(BuildContext context, RoomContext roomCtx, bool connected) builder; @override Widget build(BuildContext context) { return Consumer(builder: (context, roomCtx, child) { return Selector( selector: (context, connected) => roomCtx.connected, - builder: (context, connected, child) => - builder(context, roomCtx, connected), + builder: (context, connected, child) => builder(context, roomCtx, connected), ); }); } diff --git a/lib/src/ui/builder/room/media_device.dart b/lib/src/ui/builder/room/media_device.dart index c660f4d..66eaf18 100644 --- a/lib/src/ui/builder/room/media_device.dart +++ b/lib/src/ui/builder/room/media_device.dart @@ -22,8 +22,7 @@ import '../../../context/room_context.dart'; class MediaDeviceContextBuilder extends StatelessWidget { const MediaDeviceContextBuilder({super.key, required this.builder}); - final Widget Function(BuildContext context, RoomContext roomContext, - MediaDeviceContext mediaDeviceContext) builder; + final Widget Function(BuildContext context, RoomContext roomContext, MediaDeviceContext mediaDeviceContext) builder; @override Widget build(BuildContext context) { @@ -31,8 +30,7 @@ class MediaDeviceContextBuilder extends StatelessWidget { builder: (context, roomCtx, child) => ChangeNotifierProvider( create: (_) => MediaDeviceContext(roomCtx: roomCtx), child: Consumer( - builder: (context, mediaDeviceCtx, child) => - builder(context, roomCtx, mediaDeviceCtx), + builder: (context, mediaDeviceCtx, child) => builder(context, roomCtx, mediaDeviceCtx), ), ), ); diff --git a/lib/src/ui/builder/room/media_device_select_button.dart b/lib/src/ui/builder/room/media_device_select_button.dart index 678c11c..45a7e87 100644 --- a/lib/src/ui/builder/room/media_device_select_button.dart +++ b/lib/src/ui/builder/room/media_device_select_button.dart @@ -35,8 +35,7 @@ class MediaDeviceSelectButton extends StatelessWidget { Widget build(BuildContext context) { return Consumer( builder: (context, roomCtx, child) => Consumer( - builder: (context, deviceCtx, child) => - builder(context, roomCtx, deviceCtx), + builder: (context, deviceCtx, child) => builder(context, roomCtx, deviceCtx), ), ); } diff --git a/lib/src/ui/builder/room/room.dart b/lib/src/ui/builder/room/room.dart index 10e0bda..38747ba 100644 --- a/lib/src/ui/builder/room/room.dart +++ b/lib/src/ui/builder/room/room.dart @@ -20,8 +20,7 @@ import '../../../context/room_context.dart'; import 'media_device.dart'; class LivekitRoom extends StatelessWidget { - const LivekitRoom( - {super.key, required this.roomContext, required this.builder}); + const LivekitRoom({super.key, required this.roomContext, required this.builder}); final RoomContext roomContext; final Widget Function(BuildContext context, RoomContext roomCtx) builder; @@ -32,8 +31,7 @@ class LivekitRoom extends StatelessWidget { create: (_) => roomContext, child: Consumer( builder: (context, roomCtx, child) => MediaDeviceContextBuilder( - builder: (context, roomCtx, mediaDeviceCtx) => - builder(context, roomCtx), + builder: (context, roomCtx, mediaDeviceCtx) => builder(context, roomCtx), ), ), ); diff --git a/lib/src/ui/builder/room/room_active_recording_indicator.dart b/lib/src/ui/builder/room/room_active_recording_indicator.dart index 737c222..c89c965 100644 --- a/lib/src/ui/builder/room/room_active_recording_indicator.dart +++ b/lib/src/ui/builder/room/room_active_recording_indicator.dart @@ -31,8 +31,7 @@ class RoomActiveRecording extends StatelessWidget { @override Widget build(BuildContext context) { return Consumer(builder: (context, roomCtx, child) { - Debug.log( - '====> RoomActiveRecording for ${roomCtx.activeRecording}'); + Debug.log('====> RoomActiveRecording for ${roomCtx.activeRecording}'); return Selector( selector: (context, activeRecording) => roomCtx.activeRecording, builder: (context, activeRecording, child) { diff --git a/lib/src/ui/builder/room/room_connection_state.dart b/lib/src/ui/builder/room/room_connection_state.dart index d7a0e24..53ad981 100644 --- a/lib/src/ui/builder/room/room_connection_state.dart +++ b/lib/src/ui/builder/room/room_connection_state.dart @@ -26,14 +26,12 @@ class RoomConnectionState extends StatelessWidget { required this.builder, }); - final Widget Function(BuildContext context, ConnectionState connectionState) - builder; + final Widget Function(BuildContext context, ConnectionState connectionState) builder; @override Widget build(BuildContext context) { return Consumer(builder: (context, roomCtx, child) { - Debug.log( - '====> RoomConnectionState for ${roomCtx.connectionState}'); + Debug.log('====> RoomConnectionState for ${roomCtx.connectionState}'); return Selector( selector: (context, connectionState) => roomCtx.connectionState, builder: (context, connectionState, child) { diff --git a/lib/src/ui/builder/room/room_participants.dart b/lib/src/ui/builder/room/room_participants.dart index 64989fc..827b241 100644 --- a/lib/src/ui/builder/room/room_participants.dart +++ b/lib/src/ui/builder/room/room_participants.dart @@ -26,8 +26,7 @@ class RoomParticipants extends StatelessWidget { required this.builder, }); - final Widget Function(BuildContext context, List participants) - builder; + final Widget Function(BuildContext context, List participants) builder; @override Widget build(BuildContext context) { diff --git a/lib/src/ui/builder/room/screenshare_toggle.dart b/lib/src/ui/builder/room/screenshare_toggle.dart index 6fae876..caaabdb 100644 --- a/lib/src/ui/builder/room/screenshare_toggle.dart +++ b/lib/src/ui/builder/room/screenshare_toggle.dart @@ -25,8 +25,8 @@ class ScreenShareToggle extends StatelessWidget { required this.builder, }); - final Widget Function(BuildContext context, RoomContext roomCtx, - MediaDeviceContext deviceCtx, bool screenShareEnabled) builder; + final Widget Function( + BuildContext context, RoomContext roomCtx, MediaDeviceContext deviceCtx, bool screenShareEnabled) builder; @override Widget build(BuildContext context) { @@ -35,8 +35,7 @@ class ScreenShareToggle extends StatelessWidget { return Consumer( builder: (context, deviceCtx, child) { return Selector( - selector: (context, screenShareEnabled) => - deviceCtx.isScreenShareEnabled, + selector: (context, screenShareEnabled) => deviceCtx.isScreenShareEnabled, builder: (context, screenShareEnabled, child) => builder( context, roomCtx, diff --git a/lib/src/ui/builder/room/speaker_switch.dart b/lib/src/ui/builder/room/speaker_switch.dart index 587496b..0a47dfa 100644 --- a/lib/src/ui/builder/room/speaker_switch.dart +++ b/lib/src/ui/builder/room/speaker_switch.dart @@ -25,8 +25,8 @@ class SpeakerSwitch extends StatelessWidget { required this.builder, }); - final Widget Function(BuildContext context, RoomContext roomCtx, - MediaDeviceContext deviceCtx, bool? isSpeakerOn) builder; + final Widget Function(BuildContext context, RoomContext roomCtx, MediaDeviceContext deviceCtx, bool? isSpeakerOn) + builder; @override Widget build(BuildContext context) { diff --git a/lib/src/ui/builder/track/connection_quality_indicator.dart b/lib/src/ui/builder/track/connection_quality_indicator.dart index e304627..9afecf9 100644 --- a/lib/src/ui/builder/track/connection_quality_indicator.dart +++ b/lib/src/ui/builder/track/connection_quality_indicator.dart @@ -12,20 +12,15 @@ class ConnectionQualityIndicator extends StatelessWidget { required this.builder, }); - final Widget Function( - BuildContext context, ConnectionQuality connectionQuality) builder; + final Widget Function(BuildContext context, ConnectionQuality connectionQuality) builder; @override Widget build(BuildContext context) { - return Consumer( - builder: (context, participantContext, child) { - Debug.log( - '====> ConnectionQualityIndicator for ${participantContext.name}'); + return Consumer(builder: (context, participantContext, child) { + Debug.log('====> ConnectionQualityIndicator for ${participantContext.name}'); return Selector( - selector: (context, connectionQuality) => - participantContext.connectionQuality, - builder: (context, connectionQuality, child) => - builder(context, connectionQuality), + selector: (context, connectionQuality) => participantContext.connectionQuality, + builder: (context, connectionQuality, child) => builder(context, connectionQuality), ); }); } diff --git a/lib/src/ui/builder/track/e2e_encryption_indicator.dart b/lib/src/ui/builder/track/e2e_encryption_indicator.dart index 70fb300..2bc9822 100644 --- a/lib/src/ui/builder/track/e2e_encryption_indicator.dart +++ b/lib/src/ui/builder/track/e2e_encryption_indicator.dart @@ -15,10 +15,8 @@ class E2EEncryptionIndicator extends StatelessWidget { @override Widget build(BuildContext context) { - return Consumer( - builder: (context, participantContext, child) { - Debug.log( - '====> E2EEncryptionIndicator for ${participantContext.name}'); + return Consumer(builder: (context, participantContext, child) { + Debug.log('====> E2EEncryptionIndicator for ${participantContext.name}'); return Selector( selector: (context, isEncrypted) => participantContext.isEncrypted, builder: (context, isEncrypted, child) => builder(context, isEncrypted), diff --git a/lib/src/ui/builder/track/is_speaking_indicator.dart b/lib/src/ui/builder/track/is_speaking_indicator.dart index 8629ce2..f6da119 100644 --- a/lib/src/ui/builder/track/is_speaking_indicator.dart +++ b/lib/src/ui/builder/track/is_speaking_indicator.dart @@ -24,8 +24,7 @@ class IsSpeakingIndicator extends StatelessWidget { Debug.log('===> IsSpeakingIndicator for ${participantContext.name}'); return Selector( selector: (context, isSpeaking) => participantContext.isSpeaking, - builder: (context, isSpeaking, child) => - builder(context, showSpeakingIndicator ? isSpeaking : null), + builder: (context, isSpeaking, child) => builder(context, showSpeakingIndicator ? isSpeaking : null), ); } } diff --git a/lib/src/ui/layout/carousel_layout.dart b/lib/src/ui/layout/carousel_layout.dart index c22c8d8..8699602 100644 --- a/lib/src/ui/layout/carousel_layout.dart +++ b/lib/src/ui/layout/carousel_layout.dart @@ -35,10 +35,8 @@ class CarouselLayoutBuilder implements ParticipantLayoutBuilder { /// Move focused tracks to the pinned list for (var sid in pinnedTracks) { - var widget = children - .where((element) => element.trackIdentifier.identifier == sid) - .map((e) => e.widget) - .firstOrNull; + var widget = + children.where((element) => element.trackIdentifier.identifier == sid).map((e) => e.widget).firstOrNull; if (widget != null) { pinnedWidgets.add(widget); } diff --git a/lib/src/ui/layout/grid_layout.dart b/lib/src/ui/layout/grid_layout.dart index d5132a1..d48b9c9 100644 --- a/lib/src/ui/layout/grid_layout.dart +++ b/lib/src/ui/layout/grid_layout.dart @@ -30,10 +30,7 @@ class GridLayoutBuilder implements ParticipantLayoutBuilder { var deviceScreenType = getDeviceType(MediaQuery.of(context).size); var orientation = MediaQuery.of(context).orientation; return GridView.count( - crossAxisCount: deviceScreenType == DeviceScreenType.mobile && - orientation == Orientation.portrait - ? 2 - : 4, + crossAxisCount: deviceScreenType == DeviceScreenType.mobile && orientation == Orientation.portrait ? 2 : 4, childAspectRatio: 1.5, children: children.map((e) => e.widget).toList(), ); diff --git a/lib/src/ui/layout/sorting.dart b/lib/src/ui/layout/sorting.dart index 80d9236..76a3aba 100644 --- a/lib/src/ui/layout/sorting.dart +++ b/lib/src/ui/layout/sorting.dart @@ -56,8 +56,7 @@ List defaultSorting(List trackWidgets) { } // joinedAt - return participantA.joinedAt.millisecondsSinceEpoch - - participantB.joinedAt.millisecondsSinceEpoch; + return participantA.joinedAt.millisecondsSinceEpoch - participantB.joinedAt.millisecondsSinceEpoch; }); return trackWidgetsSorted; diff --git a/lib/src/ui/prejoin/prejoin.dart b/lib/src/ui/prejoin/prejoin.dart index 9bbe73f..59af79f 100644 --- a/lib/src/ui/prejoin/prejoin.dart +++ b/lib/src/ui/prejoin/prejoin.dart @@ -27,8 +27,7 @@ import '../widgets/room/microphone_select_button.dart'; import 'text_input.dart'; class Prejoin extends StatelessWidget { - Prejoin( - {super.key, required this.token, required this.url, this.onJoinPressed}); + Prejoin({super.key, required this.token, required this.url, this.onJoinPressed}); final void Function(RoomContext roomCtx, String url, String token)? onJoinPressed; @@ -63,8 +62,7 @@ class Prejoin extends StatelessWidget { @override Widget build(BuildContext context) { return Consumer( - builder: (context, roomCtx, child) => !roomCtx.connected && - !roomCtx.connecting + builder: (context, roomCtx, child) => !roomCtx.connected && !roomCtx.connecting ? Center( child: SizedBox( width: 480, @@ -77,8 +75,7 @@ class Prejoin extends StatelessWidget { Container( padding: const EdgeInsets.all(8.0), child: CameraPreview( - builder: (context, videoTrack) => - CameraPreviewWidget(track: videoTrack), + builder: (context, videoTrack) => CameraPreviewWidget(track: videoTrack), ), ), SizedBox( @@ -86,8 +83,7 @@ class Prejoin extends StatelessWidget { child: Container( padding: const EdgeInsets.all(8.0), child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceEvenly, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ MicrophoneSelectButton(), CameraSelectButton( @@ -124,8 +120,7 @@ class Prejoin extends StatelessWidget { child: Container( padding: const EdgeInsets.all(8.0), child: JoinButton( - builder: (context, roomCtx, connected) => - JoinButtonWidget( + builder: (context, roomCtx, connected) => JoinButtonWidget( roomCtx: roomCtx, connected: connected, onPressed: () => _handleJoinPressed(roomCtx), diff --git a/lib/src/ui/prejoin/text_input.dart b/lib/src/ui/prejoin/text_input.dart index 081139d..a4d41ce 100644 --- a/lib/src/ui/prejoin/text_input.dart +++ b/lib/src/ui/prejoin/text_input.dart @@ -15,11 +15,7 @@ import 'package:flutter/material.dart'; class TextInput extends StatelessWidget { - TextInput( - {super.key, - required this.onTextChanged, - required this.hintText, - String? text}) + TextInput({super.key, required this.onTextChanged, required this.hintText, String? text}) : _textController = TextEditingController(text: text ?? ''); final TextEditingController _textController; @@ -45,8 +41,7 @@ class TextInput extends StatelessWidget { decoration: InputDecoration( hintText: hintText, hintMaxLines: 1, - contentPadding: - const EdgeInsets.symmetric(horizontal: 8.0, vertical: 10), + contentPadding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 10), hintStyle: const TextStyle( fontSize: 16, color: Colors.grey, diff --git a/lib/src/ui/widgets/camera_preview.dart b/lib/src/ui/widgets/camera_preview.dart index f528fd3..2b16208 100644 --- a/lib/src/ui/widgets/camera_preview.dart +++ b/lib/src/ui/widgets/camera_preview.dart @@ -42,9 +42,7 @@ class CameraPreviewWidget extends StatelessWidget { builder: (context, constraints) => Icon( Icons.videocam_off_outlined, color: iconColor, - size: - math.min(constraints.maxHeight, constraints.maxWidth) * - 0.33, + size: math.min(constraints.maxHeight, constraints.maxWidth) * 0.33, ), ), ), diff --git a/lib/src/ui/widgets/participant/connection_quality_indicator.dart b/lib/src/ui/widgets/participant/connection_quality_indicator.dart index 37702bf..734bb67 100644 --- a/lib/src/ui/widgets/participant/connection_quality_indicator.dart +++ b/lib/src/ui/widgets/participant/connection_quality_indicator.dart @@ -26,9 +26,7 @@ class ConnectionQualityIndicatorWidget extends StatelessWidget { return Padding( padding: const EdgeInsets.only(left: 5), child: Icon( - connectionQuality == ConnectionQuality.poor - ? Icons.wifi_off_outlined - : Icons.wifi, + connectionQuality == ConnectionQuality.poor ? Icons.wifi_off_outlined : Icons.wifi, color: { ConnectionQuality.excellent: Colors.green, ConnectionQuality.good: Colors.orange, diff --git a/lib/src/ui/widgets/participant/participant_status_bar.dart b/lib/src/ui/widgets/participant/participant_status_bar.dart index bd1afa6..edaf042 100644 --- a/lib/src/ui/widgets/participant/participant_status_bar.dart +++ b/lib/src/ui/widgets/participant/participant_status_bar.dart @@ -43,67 +43,63 @@ class ParticipantStatusBar extends StatelessWidget { Widget build(BuildContext context) { return Consumer( builder: (context, participantContext, child) { - Debug.log( - '===> ParticipantStatusBar for ${participantContext.name}'); + Debug.log('===> ParticipantStatusBar for ${participantContext.name}'); var trackCtx = Provider.of(context); var isScreenShare = trackCtx?.isScreenShare ?? false; return Container( padding: const EdgeInsets.symmetric(horizontal: 6), color: Colors.black.withValues(alpha: 0.6), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - if (showMuteStatus && !isScreenShare) - ParticipantMutedIndicator( - builder: (context, isMuted) => isMuted - ? const Icon( - Icons.mic_off, + child: + Row(mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.center, children: [ + if (showMuteStatus && !isScreenShare) + ParticipantMutedIndicator( + builder: (context, isMuted) => isMuted + ? const Icon( + Icons.mic_off, + color: Colors.white54, + size: 20, + ) + : const SizedBox(), + ), + if (isScreenShare) + const Icon( + Icons.screen_share, + color: Colors.white54, + size: 20, + ), + if (showName) + ParticipantName( + builder: (context, name) => name != null + ? Flexible( + child: Text( + isScreenShare ? '$name\'s screen' : name, + style: const TextStyle( color: Colors.white54, - size: 20, - ) - : const SizedBox(), - ), - if (isScreenShare) - const Icon( - Icons.screen_share, + fontSize: 16, + ), + overflow: TextOverflow.ellipsis, + ), + ) + : Container(), + ), + if (showConnectionQuality) + ConnectionQualityIndicator( + builder: (context, connectionQuality) => ConnectionQualityIndicatorWidget( + connectionQuality: connectionQuality, + ), + ), + if (showE2EEStatus) + E2EEncryptionIndicator( + builder: (context, isEncrypted) => Padding( + padding: const EdgeInsets.only(left: 5), + child: Icon( + isEncrypted ? Icons.lock : Icons.lock_open, color: Colors.white54, size: 20, ), - if (showName) - ParticipantName( - builder: (context, name) => name != null - ? Flexible( - child: Text( - isScreenShare ? '$name\'s screen' : name, - style: const TextStyle( - color: Colors.white54, - fontSize: 16, - ), - overflow: TextOverflow.ellipsis, - ), - ) - : Container(), - ), - if (showConnectionQuality) - ConnectionQualityIndicator( - builder: (context, connectionQuality) => - ConnectionQualityIndicatorWidget( - connectionQuality: connectionQuality, - ), - ), - if (showE2EEStatus) - E2EEncryptionIndicator( - builder: (context, isEncrypted) => Padding( - padding: const EdgeInsets.only(left: 5), - child: Icon( - isEncrypted ? Icons.lock : Icons.lock_open, - color: Colors.white54, - size: 20, - ), - ), - ), - ]), + ), + ), + ]), ); }, ); diff --git a/lib/src/ui/widgets/participant/participant_tile_widget.dart b/lib/src/ui/widgets/participant/participant_tile_widget.dart index 156f3a5..cd1bffe 100644 --- a/lib/src/ui/widgets/participant/participant_tile_widget.dart +++ b/lib/src/ui/widgets/participant/participant_tile_widget.dart @@ -33,8 +33,7 @@ class ParticipantTileWidget extends StatelessWidget { @override Widget build(BuildContext context) { var trackCtx = Provider.of(context); - Debug.log( - '> ParticipantTile for track ${trackCtx?.sid}@${trackCtx?.participant.identity}'); + Debug.log('> ParticipantTile for track ${trackCtx?.sid}@${trackCtx?.participant.identity}'); return Stack( children: [ diff --git a/lib/src/ui/widgets/room/camera_select_button.dart b/lib/src/ui/widgets/room/camera_select_button.dart index 8916b81..0e72155 100644 --- a/lib/src/ui/widgets/room/camera_select_button.dart +++ b/lib/src/ui/widgets/room/camera_select_button.dart @@ -58,8 +58,7 @@ class CameraSelectButton extends StatelessWidget { selectedDeviceId: deviceCtx.selectedVideoInputDeviceId, deviceIsOpened: deviceCtx.cameraOpened, onSelect: (device) => deviceCtx.selectVideoInput(device), - onToggle: (enabled) => - enabled ? deviceCtx.enableCamera() : deviceCtx.disableCamera(), + onToggle: (enabled) => enabled ? deviceCtx.enableCamera() : deviceCtx.disableCamera(), showTitleWidget: showTitleWidget, ), ); diff --git a/lib/src/ui/widgets/room/camera_switch_button.dart b/lib/src/ui/widgets/room/camera_switch_button.dart index 38b7999..0db5818 100644 --- a/lib/src/ui/widgets/room/camera_switch_button.dart +++ b/lib/src/ui/widgets/room/camera_switch_button.dart @@ -38,25 +38,21 @@ class CameraSwitchButton extends StatelessWidget { Widget build(BuildContext context) { return ElevatedButton( style: ButtonStyle( - backgroundColor: - WidgetStateProperty.all(backgroundColor.withValues(alpha: 0.9)), + backgroundColor: WidgetStateProperty.all(backgroundColor.withValues(alpha: 0.9)), foregroundColor: WidgetStateProperty.all(foregroundColor), overlayColor: WidgetStateProperty.all(overlayColor), - shape: WidgetStateProperty.all(const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(20.0)))), + shape: WidgetStateProperty.all( + const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20.0)))), padding: WidgetStateProperty.all( const EdgeInsets.all(12), ), ), - onPressed: () => onToggle?.call(currentPosition == CameraPosition.front - ? CameraPosition.back - : CameraPosition.front), + onPressed: () => + onToggle?.call(currentPosition == CameraPosition.front ? CameraPosition.back : CameraPosition.front), child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(currentPosition == CameraPosition.back - ? Icons.video_camera_back - : Icons.video_camera_front), + Icon(currentPosition == CameraPosition.back ? Icons.video_camera_back : Icons.video_camera_front), ], ), ); diff --git a/lib/src/ui/widgets/room/chat_toggle.dart b/lib/src/ui/widgets/room/chat_toggle.dart index 2269a05..ef15e95 100644 --- a/lib/src/ui/widgets/room/chat_toggle.dart +++ b/lib/src/ui/widgets/room/chat_toggle.dart @@ -45,12 +45,9 @@ class ChatToggleWidget extends StatelessWidget { var deviceScreenType = getDeviceType(MediaQuery.of(context).size); return ElevatedButton( style: ButtonStyle( - backgroundColor: WidgetStateProperty.all(isChatOpen - ? selectedColor - : backgroundColor.withValues(alpha: 0.9)), + backgroundColor: WidgetStateProperty.all(isChatOpen ? selectedColor : backgroundColor.withValues(alpha: 0.9)), foregroundColor: WidgetStateProperty.all(foregroundColor), - overlayColor: WidgetStateProperty.all( - isChatOpen ? selectedOverlayColor : backgroundColor), + overlayColor: WidgetStateProperty.all(isChatOpen ? selectedOverlayColor : backgroundColor), shape: WidgetStateProperty.all( const RoundedRectangleBorder( borderRadius: BorderRadius.all( diff --git a/lib/src/ui/widgets/room/chat_widget.dart b/lib/src/ui/widgets/room/chat_widget.dart index 4ee8e89..717395d 100644 --- a/lib/src/ui/widgets/room/chat_widget.dart +++ b/lib/src/ui/widgets/room/chat_widget.dart @@ -39,14 +39,12 @@ class ChatWidget extends StatelessWidget { String lastPartcipantId = ''; for (ChatMessage msg in messages) { if (DateTime.fromMillisecondsSinceEpoch(msg.timestamp) - .difference( - DateTime.fromMillisecondsSinceEpoch(lastTimestamp)) + .difference(DateTime.fromMillisecondsSinceEpoch(lastTimestamp)) .inMinutes > 1 || lastPartcipantId != msg.participant?.identity) { msgWidgets.add(CustomDateNameChip( - name: msg.participant?.name ?? 'Unknown', - date: DateTime.fromMillisecondsSinceEpoch(msg.timestamp))); + name: msg.participant?.name ?? 'Unknown', date: DateTime.fromMillisecondsSinceEpoch(msg.timestamp))); } msgWidgets.add(BubbleNormal( text: msg.message, @@ -83,10 +81,7 @@ class ChatWidget extends StatelessWidget { child: Center( child: Text( 'Messages', - style: TextStyle( - color: Colors.white, - fontSize: 20.0, - fontWeight: FontWeight.bold), + style: TextStyle(color: Colors.white, fontSize: 20.0, fontWeight: FontWeight.bold), ), ), ), diff --git a/lib/src/ui/widgets/room/control_bar.dart b/lib/src/ui/widgets/room/control_bar.dart index 21c96dc..60de07a 100644 --- a/lib/src/ui/widgets/room/control_bar.dart +++ b/lib/src/ui/widgets/room/control_bar.dart @@ -94,14 +94,11 @@ class ControlBar extends StatelessWidget { selectedDeviceId: deviceCtx.selectedAudioInputDeviceId, deviceIsOpened: deviceCtx.microphoneOpened, onSelect: (device) => deviceCtx.selectAudioInput(device), - onToggle: (enabled) => enabled - ? deviceCtx.enableMicrophone() - : deviceCtx.disableMicrophone(), + onToggle: (enabled) => enabled ? deviceCtx.enableMicrophone() : deviceCtx.disableMicrophone(), showTitleWidget: showTitleWidget, ), ), - if (audioOutput && - (lkPlatformIsDesktop() || lkPlatformIs(PlatformType.web))) + if (audioOutput && (lkPlatformIsDesktop() || lkPlatformIs(PlatformType.web))) MediaDeviceSelectButton( builder: (context, roomCtx, deviceCtx) => MediaDeviceSelectWidget( titleWidget: Text( @@ -142,32 +139,25 @@ class ControlBar extends StatelessWidget { selectedDeviceId: deviceCtx.selectedVideoInputDeviceId, deviceIsOpened: deviceCtx.cameraOpened, onSelect: (device) => deviceCtx.selectVideoInput(device), - onToggle: (enabled) => enabled - ? deviceCtx.enableCamera() - : deviceCtx.disableCamera(), + onToggle: (enabled) => enabled ? deviceCtx.enableCamera() : deviceCtx.disableCamera(), showTitleWidget: showTitleWidget, ), ), if (isMobile && microphone) SpeakerSwitch( - builder: (context, roomCtx, deviceCtx, isSpeakerOn) => - SpeakerSwitchButton( + builder: (context, roomCtx, deviceCtx, isSpeakerOn) => SpeakerSwitchButton( isSpeakerOn: isSpeakerOn ?? false, - onToggle: (speakerOn) => - deviceCtx.setSpeakerphoneOn(speakerOn), + onToggle: (speakerOn) => deviceCtx.setSpeakerphoneOn(speakerOn), )), if (isMobile && camera) CameraSwitch( - builder: (context, roomCtx, deviceCtx, position) => - CameraSwitchButton( + builder: (context, roomCtx, deviceCtx, position) => CameraSwitchButton( currentPosition: position, - onToggle: (newPosition) => - deviceCtx.switchCameraPosition(newPosition), + onToggle: (newPosition) => deviceCtx.switchCameraPosition(newPosition), )), if (screenShare) ScreenShareToggle( - builder: (context, roomCtx, deviceCtx, screenShareEnabled) => - ScreenShareToggleWidget( + builder: (context, roomCtx, deviceCtx, screenShareEnabled) => ScreenShareToggleWidget( roomCtx: roomCtx, deviceCtx: deviceCtx, screenShareEnabled: screenShareEnabled, diff --git a/lib/src/ui/widgets/room/disconnect_button.dart b/lib/src/ui/widgets/room/disconnect_button.dart index 06ffdf3..c24d9de 100644 --- a/lib/src/ui/widgets/room/disconnect_button.dart +++ b/lib/src/ui/widgets/room/disconnect_button.dart @@ -52,11 +52,9 @@ class DisconnectButtonWidget extends StatelessWidget { var deviceScreenType = getDeviceType(MediaQuery.of(context).size); return ElevatedButton( style: ButtonStyle( - backgroundColor: WidgetStateProperty.all( - connected ? selectedColor : backgroundColor.withValues(alpha: 0.9)), + backgroundColor: WidgetStateProperty.all(connected ? selectedColor : backgroundColor.withValues(alpha: 0.9)), foregroundColor: WidgetStateProperty.all(foregroundColor), - overlayColor: WidgetStateProperty.all( - connected ? selectedOverlayColor : overlayColor), + overlayColor: WidgetStateProperty.all(connected ? selectedOverlayColor : overlayColor), shape: WidgetStateProperty.all( const RoundedRectangleBorder( borderRadius: BorderRadius.all( diff --git a/lib/src/ui/widgets/room/join_button.dart b/lib/src/ui/widgets/room/join_button.dart index ed9dfec..cf8683f 100644 --- a/lib/src/ui/widgets/room/join_button.dart +++ b/lib/src/ui/widgets/room/join_button.dart @@ -49,11 +49,9 @@ class JoinButtonWidget extends StatelessWidget { ? roomCtx.connect() : null, style: ButtonStyle( - backgroundColor: WidgetStateProperty.all( - connected ? disabledColor : backgroundColor), + backgroundColor: WidgetStateProperty.all(connected ? disabledColor : backgroundColor), foregroundColor: WidgetStateProperty.all(foregroundColor), - overlayColor: WidgetStateProperty.all( - connected ? overlayColor : disabledOverlayColor), + overlayColor: WidgetStateProperty.all(connected ? overlayColor : disabledOverlayColor), shape: WidgetStateProperty.all( const RoundedRectangleBorder( borderRadius: BorderRadius.all( diff --git a/lib/src/ui/widgets/room/media_device_select_button.dart b/lib/src/ui/widgets/room/media_device_select_button.dart index 98079ea..a086fce 100644 --- a/lib/src/ui/widgets/room/media_device_select_button.dart +++ b/lib/src/ui/widgets/room/media_device_select_button.dart @@ -61,17 +61,13 @@ class MediaDeviceSelectWidget extends StatelessWidget { return Row(mainAxisSize: MainAxisSize.min, children: [ ElevatedButton( style: ButtonStyle( - backgroundColor: WidgetStateProperty.all(deviceIsOpened - ? selectedColor - : backgroundColor.withValues(alpha: 0.9)), + backgroundColor: + WidgetStateProperty.all(deviceIsOpened ? selectedColor : backgroundColor.withValues(alpha: 0.9)), foregroundColor: WidgetStateProperty.all(foregroundColor), - overlayColor: WidgetStateProperty.all( - deviceIsOpened ? selectedOverlayColor : backgroundColor), + overlayColor: WidgetStateProperty.all(deviceIsOpened ? selectedOverlayColor : backgroundColor), shadowColor: WidgetStateProperty.all(Colors.transparent), shape: WidgetStateProperty.all(const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(20.0), - bottomLeft: Radius.circular(20.0)))), + borderRadius: BorderRadius.only(topLeft: Radius.circular(20.0), bottomLeft: Radius.circular(20.0)))), padding: WidgetStateProperty.all( (lkPlatformIsMobile() || lkPlatformIsWebMobile()) ? const EdgeInsets.all(12) @@ -82,13 +78,9 @@ class MediaDeviceSelectWidget extends StatelessWidget { child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(deviceIsOpened || defaultSelectable ? iconOn : iconOff, - color: iconColor), + Icon(deviceIsOpened || defaultSelectable ? iconOn : iconOff, color: iconColor), const SizedBox(width: 2), - if (titleWidget != null && - (deviceScreenType != DeviceScreenType.mobile || - showTitleWidget)) - titleWidget!, + if (titleWidget != null && (deviceScreenType != DeviceScreenType.mobile || showTitleWidget)) titleWidget!, ], ), ), @@ -100,19 +92,14 @@ class MediaDeviceSelectWidget extends StatelessWidget { Icons.arrow_drop_down, color: iconColor, ), - offset: Offset( - 0, ((deviceList.isNotEmpty ? deviceList.length : 1) * -55.0)), + offset: Offset(0, ((deviceList.isNotEmpty ? deviceList.length : 1) * -55.0)), style: ButtonStyle( - backgroundColor: WidgetStateProperty.all(deviceIsOpened - ? selectedColor - : backgroundColor.withValues(alpha: 0.9)), + backgroundColor: + WidgetStateProperty.all(deviceIsOpened ? selectedColor : backgroundColor.withValues(alpha: 0.9)), foregroundColor: WidgetStateProperty.all(foregroundColor), - overlayColor: WidgetStateProperty.all( - deviceIsOpened ? selectedOverlayColor : backgroundColor), + overlayColor: WidgetStateProperty.all(deviceIsOpened ? selectedOverlayColor : backgroundColor), shape: WidgetStateProperty.all(const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topRight: Radius.circular(20.0), - bottomRight: Radius.circular(20.0)))), + borderRadius: BorderRadius.only(topRight: Radius.circular(20.0), bottomRight: Radius.circular(20.0)))), ), enabled: deviceIsOpened || defaultSelectable, itemBuilder: (BuildContext context) { @@ -127,9 +114,7 @@ class MediaDeviceSelectWidget extends StatelessWidget { leading: (device.deviceId == selectedDeviceId) ? Icon( Icons.check_box_outlined, - color: (device.deviceId == selectedDeviceId) - ? selectedColor - : backgroundColor, + color: (device.deviceId == selectedDeviceId) ? selectedColor : backgroundColor, ) : Icon( Icons.check_box_outline_blank, diff --git a/lib/src/ui/widgets/room/microphone_select_button.dart b/lib/src/ui/widgets/room/microphone_select_button.dart index f8458a6..cba27ec 100644 --- a/lib/src/ui/widgets/room/microphone_select_button.dart +++ b/lib/src/ui/widgets/room/microphone_select_button.dart @@ -58,9 +58,7 @@ class MicrophoneSelectButton extends StatelessWidget { selectedDeviceId: deviceCtx.selectedAudioInputDeviceId, deviceIsOpened: deviceCtx.microphoneOpened, onSelect: (device) => deviceCtx.selectAudioInput(device), - onToggle: (enabled) => enabled - ? deviceCtx.enableMicrophone() - : deviceCtx.disableMicrophone(), + onToggle: (enabled) => enabled ? deviceCtx.enableMicrophone() : deviceCtx.disableMicrophone(), showTitleWidget: showtitleWidget, ), ); diff --git a/lib/src/ui/widgets/room/screenshare_toggle.dart b/lib/src/ui/widgets/room/screenshare_toggle.dart index 18b4809..8dcdbbc 100644 --- a/lib/src/ui/widgets/room/screenshare_toggle.dart +++ b/lib/src/ui/widgets/room/screenshare_toggle.dart @@ -50,12 +50,10 @@ class ScreenShareToggleWidget extends StatelessWidget { var deviceScreenType = getDeviceType(MediaQuery.of(context).size); return ElevatedButton( style: ButtonStyle( - backgroundColor: WidgetStateProperty.all(screenShareEnabled - ? selectedColor - : backgroundColor.withValues(alpha: 0.9)), + backgroundColor: + WidgetStateProperty.all(screenShareEnabled ? selectedColor : backgroundColor.withValues(alpha: 0.9)), foregroundColor: WidgetStateProperty.all(foregroundColor), - overlayColor: WidgetStateProperty.all( - screenShareEnabled ? selectedOverlayColor : overlayColor), + overlayColor: WidgetStateProperty.all(screenShareEnabled ? selectedOverlayColor : overlayColor), shape: WidgetStateProperty.all( const RoundedRectangleBorder( borderRadius: BorderRadius.all( @@ -69,15 +67,11 @@ class ScreenShareToggleWidget extends StatelessWidget { : const EdgeInsets.fromLTRB(12, 20, 12, 20), ), ), - onPressed: () => screenShareEnabled - ? deviceCtx.disableScreenShare() - : deviceCtx.enableScreenShare(context), + onPressed: () => screenShareEnabled ? deviceCtx.disableScreenShare() : deviceCtx.enableScreenShare(context), child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(screenShareEnabled - ? Icons.stop_screen_share_outlined - : Icons.screen_share_outlined), + Icon(screenShareEnabled ? Icons.stop_screen_share_outlined : Icons.screen_share_outlined), const SizedBox(width: 2), if (deviceScreenType != DeviceScreenType.mobile || showLabel) Text( diff --git a/lib/src/ui/widgets/room/speaker_switch_button.dart b/lib/src/ui/widgets/room/speaker_switch_button.dart index d891969..311aac8 100644 --- a/lib/src/ui/widgets/room/speaker_switch_button.dart +++ b/lib/src/ui/widgets/room/speaker_switch_button.dart @@ -36,12 +36,11 @@ class SpeakerSwitchButton extends StatelessWidget { Widget build(BuildContext context) { return ElevatedButton( style: ButtonStyle( - backgroundColor: - WidgetStateProperty.all(backgroundColor.withValues(alpha: 0.9)), + backgroundColor: WidgetStateProperty.all(backgroundColor.withValues(alpha: 0.9)), foregroundColor: WidgetStateProperty.all(foregroundColor), overlayColor: WidgetStateProperty.all(overlayColor), - shape: WidgetStateProperty.all(const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(20.0)))), + shape: WidgetStateProperty.all( + const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20.0)))), padding: WidgetStateProperty.all(const EdgeInsets.all(12)), ), onPressed: () => onToggle?.call(!isSpeakerOn), diff --git a/lib/src/ui/widgets/theme.dart b/lib/src/ui/widgets/theme.dart index 6dc7cfa..39c803b 100644 --- a/lib/src/ui/widgets/theme.dart +++ b/lib/src/ui/widgets/theme.dart @@ -48,14 +48,12 @@ class LiveKitTheme { ), elevatedButtonTheme: ElevatedButtonThemeData( style: ButtonStyle( - textStyle: - WidgetStateProperty.all(GoogleFonts.montserrat( + textStyle: WidgetStateProperty.all(GoogleFonts.montserrat( fontSize: 15, )), - padding: WidgetStateProperty.all( - const EdgeInsets.symmetric(vertical: 20, horizontal: 25)), - shape: WidgetStateProperty.all( - RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), + padding: WidgetStateProperty.all(const EdgeInsets.symmetric(vertical: 20, horizontal: 25)), + shape: + WidgetStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), foregroundColor: WidgetStateProperty.all(Colors.white), // backgroundColor: WidgetStateProperty.all(accentColor), backgroundColor: WidgetStateProperty.resolveWith((states) { diff --git a/lib/src/ui/widgets/toast.dart b/lib/src/ui/widgets/toast.dart index 4e4e95f..7071122 100644 --- a/lib/src/ui/widgets/toast.dart +++ b/lib/src/ui/widgets/toast.dart @@ -39,8 +39,7 @@ class ToastWidget extends StatefulWidget { ToastState createState() => ToastState(); } -class ToastState extends State - with SingleTickerProviderStateMixin { +class ToastState extends State with SingleTickerProviderStateMixin { AnimationController? _animationController; late Animation _fadeAnimation; Timer? _timer; @@ -71,8 +70,7 @@ class ToastState extends State vsync: this, duration: widget.fadeDuration, ); - _fadeAnimation = - CurvedAnimation(parent: _animationController!, curve: Curves.easeIn); + _fadeAnimation = CurvedAnimation(parent: _animationController!, curve: Curves.easeIn); super.initState(); } @@ -93,8 +91,7 @@ class ToastState extends State @override Widget build(BuildContext context) { return Consumer( - builder: (context, roomCtx, child) => - Selector( + builder: (context, roomCtx, child) => Selector( selector: (context, connectionState) => roomCtx.connectionState, builder: (context, connectionState, child) { if (connectionState != _connectionState) { diff --git a/lib/src/ui/widgets/track/focus_toggle.dart b/lib/src/ui/widgets/track/focus_toggle.dart index d64565e..cc596ff 100644 --- a/lib/src/ui/widgets/track/focus_toggle.dart +++ b/lib/src/ui/widgets/track/focus_toggle.dart @@ -37,8 +37,7 @@ class FocusToggle extends StatelessWidget { if (trackCtx == null) { return const SizedBox(); } - var shouldShowBackToGridView = - roomCtx.pinnedTracks.contains(sid) && sid == roomCtx.pinnedTracks.first; + var shouldShowBackToGridView = roomCtx.pinnedTracks.contains(sid) && sid == roomCtx.pinnedTracks.first; if (shouldShowBackToGridView && !showBackToGridView) { return const SizedBox(); @@ -47,8 +46,7 @@ class FocusToggle extends StatelessWidget { return Padding( padding: const EdgeInsets.all(2), child: IconButton( - icon: Icon( - shouldShowBackToGridView ? Icons.grid_view : Icons.open_in_full), + icon: Icon(shouldShowBackToGridView ? Icons.grid_view : Icons.open_in_full), color: Colors.white70, onPressed: () { if (sid == null) { diff --git a/lib/src/ui/widgets/track/track_stats_widget.dart b/lib/src/ui/widgets/track/track_stats_widget.dart index 815f46c..a58c34b 100644 --- a/lib/src/ui/widgets/track/track_stats_widget.dart +++ b/lib/src/ui/widgets/track/track_stats_widget.dart @@ -16,11 +16,9 @@ class TrackStatsWidget extends StatelessWidget { } return Consumer( - builder: (context, trackCtx, child) => - Selector>( + builder: (context, trackCtx, child) => Selector>( selector: (context, trackCtx) => trackCtx.stats, - builder: - (BuildContext context, Map stats, Widget? child) { + builder: (BuildContext context, Map stats, Widget? child) { return Center( child: Stack( children: [ @@ -33,18 +31,15 @@ class TrackStatsWidget extends StatelessWidget { ), child: Column(children: [ Text('${trackCtx.isVideo ? 'video' : 'audio'} stats', - style: const TextStyle( - fontWeight: FontWeight.bold, fontSize: 16)), - ...stats.entries - .map((e) => Text('${e.key}: ${e.value}')), + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), + ...stats.entries.map((e) => Text('${e.key}: ${e.value}')), ]), ) : const SizedBox(), Container( padding: const EdgeInsets.all(2), child: IconButton( - icon: Icon( - trackCtx.showStatistics ? Icons.close : Icons.info), + icon: Icon(trackCtx.showStatistics ? Icons.close : Icons.info), color: Colors.white70, onPressed: () { // show stats From 3e016b91f27a800df0c545a7129e2ebcf026c488 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:42:58 +0800 Subject: [PATCH 06/24] Fix imports --- lib/src/context/room_context.dart | 2 +- lib/src/context/transcription_context.dart | 3 ++- lib/src/types/transcription.dart | 1 + lib/src/ui/builder/participant/participant_selector.dart | 2 +- lib/src/ui/builder/room/transcription.dart | 1 + lib/src/ui/widgets/participant/transcription_widget.dart | 1 + lib/src/ui/widgets/room/camera_select_button.dart | 2 +- lib/src/ui/widgets/room/control_bar.dart | 4 ++-- lib/src/ui/widgets/track/audio_visualizer_widget.dart | 3 ++- lib/src/ui/widgets/track/video_track_widget.dart | 1 + 10 files changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/src/context/room_context.dart b/lib/src/context/room_context.dart index 7097564..75e8dd7 100644 --- a/lib/src/context/room_context.dart +++ b/lib/src/context/room_context.dart @@ -19,7 +19,7 @@ import 'package:flutter/material.dart' hide ConnectionState; import 'package:livekit_client/livekit_client.dart'; import 'package:provider/provider.dart'; -import 'package:livekit_components/src/context/transcription_context.dart'; +import '../context/transcription_context.dart'; import '../debug/logger.dart'; import 'chat_context.dart'; diff --git a/lib/src/context/transcription_context.dart b/lib/src/context/transcription_context.dart index 98f30ae..6b8e6f0 100644 --- a/lib/src/context/transcription_context.dart +++ b/lib/src/context/transcription_context.dart @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; + +import 'package:collection/collection.dart'; import 'package:livekit_client/livekit_client.dart'; import '../types/transcription.dart'; diff --git a/lib/src/types/transcription.dart b/lib/src/types/transcription.dart index 9e5ff25..fd1c69e 100644 --- a/lib/src/types/transcription.dart +++ b/lib/src/types/transcription.dart @@ -1,4 +1,5 @@ import 'package:flutter/foundation.dart'; + import 'package:livekit_client/livekit_client.dart' as sdk; @immutable diff --git a/lib/src/ui/builder/participant/participant_selector.dart b/lib/src/ui/builder/participant/participant_selector.dart index 781811b..ea5916f 100644 --- a/lib/src/ui/builder/participant/participant_selector.dart +++ b/lib/src/ui/builder/participant/participant_selector.dart @@ -4,9 +4,9 @@ import 'package:collection/collection.dart'; import 'package:livekit_client/livekit_client.dart'; import 'package:provider/provider.dart'; -import 'package:livekit_components/src/types/track_identifier.dart'; import '../../../context/room_context.dart'; import '../../../debug/logger.dart'; +import '../../../types/track_identifier.dart'; import 'participant_track.dart'; class ParticipantSelector extends StatelessWidget { diff --git a/lib/src/ui/builder/room/transcription.dart b/lib/src/ui/builder/room/transcription.dart index 648cabc..92e0081 100644 --- a/lib/src/ui/builder/room/transcription.dart +++ b/lib/src/ui/builder/room/transcription.dart @@ -13,6 +13,7 @@ // limitations under the License. import 'package:flutter/material.dart'; + import 'package:provider/provider.dart'; import '../../../context/room_context.dart'; diff --git a/lib/src/ui/widgets/participant/transcription_widget.dart b/lib/src/ui/widgets/participant/transcription_widget.dart index baa0457..4d71856 100644 --- a/lib/src/ui/widgets/participant/transcription_widget.dart +++ b/lib/src/ui/widgets/participant/transcription_widget.dart @@ -13,6 +13,7 @@ // limitations under the License. import 'package:flutter/material.dart'; + import 'package:livekit_client/livekit_client.dart'; import '../../../types/transcription.dart'; diff --git a/lib/src/ui/widgets/room/camera_select_button.dart b/lib/src/ui/widgets/room/camera_select_button.dart index 0e72155..8537215 100644 --- a/lib/src/ui/widgets/room/camera_select_button.dart +++ b/lib/src/ui/widgets/room/camera_select_button.dart @@ -14,8 +14,8 @@ import 'package:flutter/material.dart'; -import 'package:livekit_components/src/ui/widgets/theme.dart'; import '../../builder/room/media_device_select_button.dart'; +import '../../widgets/theme.dart'; import 'media_device_select_button.dart'; class CameraSelectButton extends StatelessWidget { diff --git a/lib/src/ui/widgets/room/control_bar.dart b/lib/src/ui/widgets/room/control_bar.dart index 60de07a..a8abdb3 100644 --- a/lib/src/ui/widgets/room/control_bar.dart +++ b/lib/src/ui/widgets/room/control_bar.dart @@ -16,13 +16,13 @@ import 'package:flutter/material.dart'; import 'package:livekit_client/livekit_client.dart'; -import 'package:livekit_components/src/ui/builder/room/camera_switch.dart'; -import 'package:livekit_components/src/ui/widgets/room/speaker_switch_button.dart'; +import '../../builder/room/camera_switch.dart'; import '../../builder/room/chat_toggle.dart'; import '../../builder/room/disconnect_button.dart'; import '../../builder/room/media_device_select_button.dart'; import '../../builder/room/screenshare_toggle.dart'; import '../../builder/room/speaker_switch.dart'; +import '../../widgets/room/speaker_switch_button.dart'; import '../theme.dart'; import 'camera_switch_button.dart'; import 'chat_toggle.dart'; diff --git a/lib/src/ui/widgets/track/audio_visualizer_widget.dart b/lib/src/ui/widgets/track/audio_visualizer_widget.dart index fb1234c..e77f64b 100644 --- a/lib/src/ui/widgets/track/audio_visualizer_widget.dart +++ b/lib/src/ui/widgets/track/audio_visualizer_widget.dart @@ -1,7 +1,8 @@ import 'dart:math' show max; -import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; + +import 'package:collection/collection.dart'; import 'package:livekit_client/livekit_client.dart' as sdk; import 'package:provider/provider.dart'; diff --git a/lib/src/ui/widgets/track/video_track_widget.dart b/lib/src/ui/widgets/track/video_track_widget.dart index 8b9203e..5652019 100644 --- a/lib/src/ui/widgets/track/video_track_widget.dart +++ b/lib/src/ui/widgets/track/video_track_widget.dart @@ -13,6 +13,7 @@ // limitations under the License. import 'package:flutter/material.dart'; + import 'package:livekit_client/livekit_client.dart' as sdk; import 'package:provider/provider.dart'; From 6b214da336c426a53d7c647c0ff7fde90fe5fa15 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:49:05 +0800 Subject: [PATCH 07/24] Misc fixes --- analysis_options.yaml | 2 +- dart_dependency_validator.yaml | 8 ++++ example/analysis_options.yaml | 30 ------------- example/ios/Flutter/AppFrameworkInfo.plist | 2 +- example/ios/Runner.xcodeproj/project.pbxproj | 6 +-- example/pubspec.yaml | 46 +------------------- maintenance.sh | 3 ++ pubspec.yaml | 8 ++-- 8 files changed, 21 insertions(+), 84 deletions(-) create mode 100644 dart_dependency_validator.yaml create mode 100755 maintenance.sh diff --git a/analysis_options.yaml b/analysis_options.yaml index ad88315..4146ad3 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,4 +1,4 @@ -include: package:lints/recommended.yaml +include: package:flutter_lints/flutter.yaml analyzer: language: diff --git a/dart_dependency_validator.yaml b/dart_dependency_validator.yaml new file mode 100644 index 0000000..73c22fe --- /dev/null +++ b/dart_dependency_validator.yaml @@ -0,0 +1,8 @@ +# dart_dependency_validator.yaml + +# Set true if you allow pinned packages in your project. +allow_pins: true + +# Exclude one or more paths from being scanned. Supports glob syntax. +exclude: + - "./example/**" diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml index 822aed2..f9b3034 100644 --- a/example/analysis_options.yaml +++ b/example/analysis_options.yaml @@ -1,31 +1 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -analyzer: - errors: - must_be_immutable: ignore include: package:flutter_lints/flutter.yaml - -linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at https://dart.dev/lints. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. - rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 7c56964..1dc6cf7 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 12.0 + 13.0 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 7f735ce..2720e0d 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -455,7 +455,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -584,7 +584,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -635,7 +635,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 6031ef6..f425df1 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -21,12 +21,6 @@ version: 1.0.0+1 environment: sdk: ^3.5.2 -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. dependencies: flutter: sdk: flutter @@ -42,19 +36,12 @@ dependencies: provider: ^6.1.2 fluttertoast: ^8.2.12 responsive_builder: ^0.7.1 - http: ^1.2.2 shared_preferences: ^2.5.3 dev_dependencies: flutter_test: sdk: flutter - - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. - flutter_lints: ^4.0.0 + flutter_lints: ^6.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -65,34 +52,3 @@ flutter: # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/to/resolution-aware-images - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/to/asset-from-package - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/to/font-from-package diff --git a/maintenance.sh b/maintenance.sh new file mode 100755 index 0000000..3d31215 --- /dev/null +++ b/maintenance.sh @@ -0,0 +1,3 @@ +dart pub run import_sorter:main --no-comments +dart format . -l 120 +dart pub global run dependency_validator diff --git a/pubspec.yaml b/pubspec.yaml index 019e92b..3e36d96 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,15 +8,15 @@ environment: flutter: ">=1.17.0" dependencies: - chat_bubbles: ^1.6.0 - collection: ^1.19.0 flutter: sdk: flutter + flutter_webrtc: 1.2.0 + livekit_client: ^2.4.9 + chat_bubbles: ^1.6.0 + collection: ^1.19.0 flutter_background: ^1.3.0+1 google_fonts: ^6.2.1 - http: ^1.2.2 intl: ^0.20.2 - livekit_client: ^2.4.9 provider: ^6.1.2 responsive_builder: ^0.7.1 From c89330fd0f56258a9a03c30e1e7da47bda3f50fb Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:50:21 +0800 Subject: [PATCH 08/24] Update workflow --- .github/workflows/build.yaml | 28 ++++++++++++++-------------- .github/workflows/publish.yaml | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index b028ee8..90b4f0b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -16,9 +16,9 @@ name: Build on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] jobs: dart-format-and-analyze-check: @@ -29,15 +29,15 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-java@v1 with: - java-version: '17.x' + java-version: "17.x" - uses: actions/checkout@v2 - uses: subosito/flutter-action@v2 with: - channel: 'stable' + channel: "stable" - name: Install project dependencies run: flutter pub get - name: Dart Format Check - run: dart format lib/ test/ --set-exit-if-changed + run: dart format lib/ test/ --set-exit-if-changed -l 120 - name: Import Sorter Check run: dart run import_sorter:main --no-comments --exit-if-changed - name: Dart Analyze Check @@ -51,11 +51,11 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-java@v1 with: - java-version: '17.x' + java-version: "17.x" - uses: actions/checkout@v2 - uses: subosito/flutter-action@v2 with: - channel: 'stable' + channel: "stable" - name: Install project dependencies run: flutter pub get - name: Build for Android @@ -70,7 +70,7 @@ jobs: - uses: actions/checkout@v2 - uses: subosito/flutter-action@v2 with: - channel: 'stable' + channel: "stable" - name: Install project dependencies run: flutter pub get - name: Build for iOS @@ -85,7 +85,7 @@ jobs: - uses: actions/checkout@v2 - uses: subosito/flutter-action@v1 with: - channel: 'stable' + channel: "stable" - name: Install project dependencies run: flutter pub get - name: Build for Windows @@ -100,7 +100,7 @@ jobs: - uses: actions/checkout@v2 - uses: subosito/flutter-action@v1 with: - channel: 'stable' + channel: "stable" - name: Install project dependencies run: flutter pub get - name: Build for macOS @@ -115,11 +115,11 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-java@v1 with: - java-version: '12.x' + java-version: "12.x" - uses: actions/checkout@v2 - uses: subosito/flutter-action@v2 with: - channel: 'stable' + channel: "stable" - name: Install project dependencies run: flutter pub get - name: Run apt update @@ -138,11 +138,11 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-java@v1 with: - java-version: '12.x' + java-version: "12.x" - uses: actions/checkout@v2 - uses: subosito/flutter-action@v2 with: - channel: 'stable' + channel: "stable" - name: Install project dependencies run: flutter pub get - name: Build for Web diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 0c95301..2373429 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -17,7 +17,7 @@ name: Publish to pub.dev on: push: tags: - - 'v[0-9]+.[0-9]+.[0-9]+*' + - "v[0-9]+.[0-9]+.[0-9]+*" jobs: publish: From 1c8392e725255dc5c04b3e57c03662354fa8a229 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Wed, 1 Oct 2025 17:53:26 +0800 Subject: [PATCH 09/24] v1.2.3 --- CHANGELOG.md | 4 ++++ pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93db6c2..df91cbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.2.3 (2025-10-01) + +* Update WebRTC ver & code maintenance. (#41) + ## 1.2.2+hotfix.1 (2025-06-30) * Fixed: TranscriptionContext clean up (#36) diff --git a/pubspec.yaml b/pubspec.yaml index 3e36d96..6f40cda 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: livekit_components description: "Out-of-the-box flutter widgets for livekit client." -version: 1.2.2+hotfix.1 +version: 1.2.3 homepage: https://github.com/livekit/components-flutter environment: From 8bdd6faf3aa59eb06ebbb052a84bfad9431ee6ea Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 2 Dec 2025 22:22:35 +0900 Subject: [PATCH 10/24] components 1 --- lib/livekit_components.dart | 2 + lib/src/context/session_context.dart | 45 ++++++++ lib/src/ui/session/chat_scroll_view.dart | 139 +++++++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 lib/src/context/session_context.dart create mode 100644 lib/src/ui/session/chat_scroll_view.dart diff --git a/lib/livekit_components.dart b/lib/livekit_components.dart index 61f4f3e..f8dee0c 100644 --- a/lib/livekit_components.dart +++ b/lib/livekit_components.dart @@ -15,6 +15,7 @@ export 'src/context/chat_context.dart'; export 'src/context/media_device_context.dart'; export 'src/context/participant_context.dart'; +export 'src/context/session_context.dart'; export 'src/context/room_context.dart'; export 'src/context/track_reference_context.dart'; export 'src/debug/logger.dart'; @@ -51,6 +52,7 @@ export 'src/ui/layout/carousel_layout.dart'; export 'src/ui/layout/grid_layout.dart'; export 'src/ui/layout/layouts.dart'; export 'src/ui/prejoin/prejoin.dart'; +export 'src/ui/session/chat_scroll_view.dart'; export 'src/ui/widgets/camera_preview.dart'; export 'src/ui/widgets/participant/connection_quality_indicator.dart'; export 'src/ui/widgets/participant/is_speaking_indicator.dart'; diff --git a/lib/src/context/session_context.dart b/lib/src/context/session_context.dart new file mode 100644 index 0000000..355125d --- /dev/null +++ b/lib/src/context/session_context.dart @@ -0,0 +1,45 @@ +// Copyright 2025 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:flutter/widgets.dart'; + +import 'package:livekit_client/livekit_client.dart'; + +/// Provides a [Session] to descendant widgets, mirroring the Swift +/// `@EnvironmentObject` pattern used by LiveKit components. +class SessionScope extends InheritedNotifier { + const SessionScope({ + super.key, + required Session session, + required super.child, + }) : super(notifier: session); + + /// Returns the nearest [Session] in the widget tree or `null` if none exists. + static Session? maybeOf(BuildContext context) { + return context.dependOnInheritedWidgetOfExactType()?.notifier; + } + + /// Returns the nearest [Session] in the widget tree. + /// Throws a [FlutterError] if no session is found. + static Session of(BuildContext context) { + final session = maybeOf(context); + if (session == null) { + throw FlutterError( + 'SessionScope.of() called with no Session in the context. ' + 'Add a SessionScope above this widget or pass a Session directly.', + ); + } + return session; + } +} diff --git a/lib/src/ui/session/chat_scroll_view.dart b/lib/src/ui/session/chat_scroll_view.dart new file mode 100644 index 0000000..8e2acbb --- /dev/null +++ b/lib/src/ui/session/chat_scroll_view.dart @@ -0,0 +1,139 @@ +// Copyright 2025 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:flutter/material.dart'; + +import 'package:livekit_client/livekit_client.dart'; + +import '../../context/session_context.dart'; + +/// A scrollable list that renders [Session.messages] and auto-scrolls as new +/// messages arrive (newest at the bottom). +/// +/// Provide a [Session] via [session] or a surrounding [SessionScope]. The +/// [messageBuilder] is responsible for rendering each [ReceivedMessage]. +class ChatScrollView extends StatefulWidget { + const ChatScrollView({ + super.key, + required this.messageBuilder, + this.session, + this.autoScroll = true, + this.scrollController, + this.padding, + this.physics, + }); + + /// Optional session instance. If omitted, [SessionScope.of] is used. + final Session? session; + + /// Builder for each message. + final Widget Function(BuildContext context, ReceivedMessage message) messageBuilder; + + /// Whether the list should automatically scroll to the latest message when + /// the message count changes. + final bool autoScroll; + + /// Optional scroll controller. If not provided, an internal controller is + /// created and disposed automatically. + final ScrollController? scrollController; + + /// Optional padding applied to the list. + final EdgeInsetsGeometry? padding; + + /// Optional scroll physics. + final ScrollPhysics? physics; + + @override + State createState() => _ChatScrollViewState(); +} + +class _ChatScrollViewState extends State { + ScrollController? _internalController; + int _lastMessageCount = 0; + + ScrollController get _controller => widget.scrollController ?? _internalController!; + + @override + void initState() { + super.initState(); + _internalController = widget.scrollController ?? ScrollController(); + } + + @override + void didUpdateWidget(ChatScrollView oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.scrollController != widget.scrollController) { + _internalController?.dispose(); + _internalController = widget.scrollController ?? ScrollController(); + } + } + + @override + void dispose() { + if (widget.scrollController == null) { + _internalController?.dispose(); + } + super.dispose(); + } + + Session _resolveSession(BuildContext context) { + return widget.session ?? SessionScope.of(context); + } + + void _autoScrollIfNeeded(List messages) { + if (!widget.autoScroll) { + _lastMessageCount = messages.length; + return; + } + if (messages.length == _lastMessageCount) { + return; + } + _lastMessageCount = messages.length; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!_controller.hasClients) { + return; + } + _controller.animateTo( + 0, + duration: const Duration(milliseconds: 200), + curve: Curves.easeOut, + ); + }); + } + + @override + Widget build(BuildContext context) { + final session = _resolveSession(context); + + return AnimatedBuilder( + animation: session, + builder: (context, _) { + final messages = session.messages; + _autoScrollIfNeeded(messages); + + return ListView.builder( + reverse: true, + controller: _controller, + padding: widget.padding, + physics: widget.physics, + itemCount: messages.length, + itemBuilder: (context, index) { + final message = messages[messages.length - 1 - index]; + return widget.messageBuilder(context, message); + }, + ); + }, + ); + } +} From cea216e1a2eeefe57efd6d719c59f3594456b68a Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 2 Dec 2025 22:22:47 +0900 Subject: [PATCH 11/24] Update README.md --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index 37820c5..f0a2e0b 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,28 @@ class MyApp extends StatelessWidget { You can find a complete example in the [example](./example) folder. +### Session UI (Agents) + +Use the agent `Session` from `livekit_client` with `SessionScope` to make it +available to widgets like `ChatScrollView`: + +```dart +final session = Session.withAgent( + 'my-agent', + tokenSource: EndpointTokenSource(Uri.parse('https://your-token-endpoint')), +); + +SessionScope( + session: session, + child: ChatScrollView( + messageBuilder: (context, message) => ListTile( + title: Text(message.content.text), + subtitle: Text(message.timestamp.toLocal().toIso8601String()), + ), + ), +); +``` +
From 640e2daaabec7e7568eb68626939d903db7ee4ac Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 2 Dec 2025 23:01:33 +0900 Subject: [PATCH 12/24] bump mac target --- example/macos/Podfile | 2 +- example/macos/Runner.xcodeproj/project.pbxproj | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/example/macos/Podfile b/example/macos/Podfile index c795730..b52666a 100644 --- a/example/macos/Podfile +++ b/example/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.14' +platform :osx, '10.15' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index fe44e6d..48a8af9 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -557,7 +557,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -639,7 +639,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -689,7 +689,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; From 7a5ebc2e3c5ac99b835d84b13ecd445b7674ef99 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 2 Dec 2025 23:14:50 +0900 Subject: [PATCH 13/24] update docs --- README.md | 10 ++++++++++ lib/src/context/session_context.dart | 9 +++++++-- lib/src/ui/session/chat_scroll_view.dart | 9 +++++---- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f0a2e0b..af43465 100644 --- a/README.md +++ b/README.md @@ -113,14 +113,19 @@ Use the agent `Session` from `livekit_client` with `SessionScope` to make it available to widgets like `ChatScrollView`: ```dart +import 'package:livekit_client/livekit_client.dart'; +import 'package:livekit_components/livekit_components.dart'; + final session = Session.withAgent( 'my-agent', tokenSource: EndpointTokenSource(Uri.parse('https://your-token-endpoint')), + options: const SessionOptions(preConnectAudio: true), ); SessionScope( session: session, child: ChatScrollView( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), messageBuilder: (context, message) => ListTile( title: Text(message.content.text), subtitle: Text(message.timestamp.toLocal().toIso8601String()), @@ -129,6 +134,11 @@ SessionScope( ); ``` +- `ChatScrollView` auto-scrolls to the newest message (bottom). Pass a + `ScrollController` if you need manual control. +- You can also pass `session:` directly to `ChatScrollView` instead of relying + on `SessionScope`. +
LiveKit Ecosystem
diff --git a/lib/src/context/session_context.dart b/lib/src/context/session_context.dart index 355125d..0185120 100644 --- a/lib/src/context/session_context.dart +++ b/lib/src/context/session_context.dart @@ -16,8 +16,13 @@ import 'package:flutter/widgets.dart'; import 'package:livekit_client/livekit_client.dart'; -/// Provides a [Session] to descendant widgets, mirroring the Swift -/// `@EnvironmentObject` pattern used by LiveKit components. +/// Provides a [Session] to descendant widgets. +/// +/// Use this to make a single `Session` visible to session-aware widgets (for +/// example, `ChatScrollView`) without passing it through every constructor. +/// Because it inherits from [InheritedNotifier], it will rebuild dependents +/// when the session notifies listeners, but you can safely use [maybeOf] if +/// you are in an optional context. class SessionScope extends InheritedNotifier { const SessionScope({ super.key, diff --git a/lib/src/ui/session/chat_scroll_view.dart b/lib/src/ui/session/chat_scroll_view.dart index 8e2acbb..d9fc5ba 100644 --- a/lib/src/ui/session/chat_scroll_view.dart +++ b/lib/src/ui/session/chat_scroll_view.dart @@ -18,11 +18,12 @@ import 'package:livekit_client/livekit_client.dart'; import '../../context/session_context.dart'; -/// A scrollable list that renders [Session.messages] and auto-scrolls as new -/// messages arrive (newest at the bottom). +/// A scrollable list that renders [Session.messages] with newest messages at +/// the bottom and auto-scrolls when new messages arrive. /// -/// Provide a [Session] via [session] or a surrounding [SessionScope]. The -/// [messageBuilder] is responsible for rendering each [ReceivedMessage]. +/// Provide a [Session] via [session] or a surrounding [SessionScope]. Use +/// [messageBuilder] to render each [ReceivedMessage]; the builder runs in +/// reverse order so index `0` corresponds to the latest message. class ChatScrollView extends StatefulWidget { const ChatScrollView({ super.key, From 8197896c342974c33ccbb22253355b229ca696d1 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Wed, 3 Dec 2025 00:31:00 +0900 Subject: [PATCH 14/24] ensure messages sorted by timestamp --- lib/src/ui/session/chat_scroll_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/ui/session/chat_scroll_view.dart b/lib/src/ui/session/chat_scroll_view.dart index d9fc5ba..b1990a7 100644 --- a/lib/src/ui/session/chat_scroll_view.dart +++ b/lib/src/ui/session/chat_scroll_view.dart @@ -120,7 +120,7 @@ class _ChatScrollViewState extends State { return AnimatedBuilder( animation: session, builder: (context, _) { - final messages = session.messages; + final messages = [...session.messages]..sort((a, b) => a.timestamp.compareTo(b.timestamp)); _autoScrollIfNeeded(messages); return ListView.builder( From 9339ed851365681c62a6ad921ac4ff1789390058 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Sun, 7 Dec 2025 19:42:37 +0900 Subject: [PATCH 15/24] fix changelog date for v1.2.3 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df91cbf..c74fa27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 1.2.3 (2025-10-01) +## 1.2.3 (2025-12-07) * Update WebRTC ver & code maintenance. (#41) From 0d7b8cb71b48297fbc5aa74208195a4541d74bf5 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Sun, 7 Dec 2025 19:56:31 +0900 Subject: [PATCH 16/24] fix merge --- analysis_options.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 399c370..355d33b 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -18,8 +18,8 @@ linter: prefer_single_quotes: true prefer_final_locals: true unnecessary_brace_in_string_interps: false - depend_on_referenced_packages: false avoid_print: true + depend_on_referenced_packages: false # Enforce this for correct async logic unawaited_futures: true From 0c89a5d185c66d5a189e95caf9604b5a5c5590b1 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Sun, 7 Dec 2025 20:02:09 +0900 Subject: [PATCH 17/24] Rename to SessionContext --- README.md | 6 +++--- lib/src/context/session_context.dart | 10 +++++----- lib/src/ui/session/chat_scroll_view.dart | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index af43465..2537f6f 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ You can find a complete example in the [example](./example) folder. ### Session UI (Agents) -Use the agent `Session` from `livekit_client` with `SessionScope` to make it +Use the agent `Session` from `livekit_client` with `SessionContext` to make it available to widgets like `ChatScrollView`: ```dart @@ -122,7 +122,7 @@ final session = Session.withAgent( options: const SessionOptions(preConnectAudio: true), ); -SessionScope( +SessionContext( session: session, child: ChatScrollView( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), @@ -137,7 +137,7 @@ SessionScope( - `ChatScrollView` auto-scrolls to the newest message (bottom). Pass a `ScrollController` if you need manual control. - You can also pass `session:` directly to `ChatScrollView` instead of relying - on `SessionScope`. + on `SessionContext`.
LiveKit Ecosystem
diff --git a/lib/src/context/session_context.dart b/lib/src/context/session_context.dart index 0185120..bb4b8a2 100644 --- a/lib/src/context/session_context.dart +++ b/lib/src/context/session_context.dart @@ -23,8 +23,8 @@ import 'package:livekit_client/livekit_client.dart'; /// Because it inherits from [InheritedNotifier], it will rebuild dependents /// when the session notifies listeners, but you can safely use [maybeOf] if /// you are in an optional context. -class SessionScope extends InheritedNotifier { - const SessionScope({ +class SessionContext extends InheritedNotifier { + const SessionContext({ super.key, required Session session, required super.child, @@ -32,7 +32,7 @@ class SessionScope extends InheritedNotifier { /// Returns the nearest [Session] in the widget tree or `null` if none exists. static Session? maybeOf(BuildContext context) { - return context.dependOnInheritedWidgetOfExactType()?.notifier; + return context.dependOnInheritedWidgetOfExactType()?.notifier; } /// Returns the nearest [Session] in the widget tree. @@ -41,8 +41,8 @@ class SessionScope extends InheritedNotifier { final session = maybeOf(context); if (session == null) { throw FlutterError( - 'SessionScope.of() called with no Session in the context. ' - 'Add a SessionScope above this widget or pass a Session directly.', + 'SessionContext.of() called with no Session in the context. ' + 'Add a SessionContext above this widget or pass a Session directly.', ); } return session; diff --git a/lib/src/ui/session/chat_scroll_view.dart b/lib/src/ui/session/chat_scroll_view.dart index b1990a7..a980ceb 100644 --- a/lib/src/ui/session/chat_scroll_view.dart +++ b/lib/src/ui/session/chat_scroll_view.dart @@ -21,7 +21,7 @@ import '../../context/session_context.dart'; /// A scrollable list that renders [Session.messages] with newest messages at /// the bottom and auto-scrolls when new messages arrive. /// -/// Provide a [Session] via [session] or a surrounding [SessionScope]. Use +/// Provide a [Session] via [session] or a surrounding [SessionContext]. Use /// [messageBuilder] to render each [ReceivedMessage]; the builder runs in /// reverse order so index `0` corresponds to the latest message. class ChatScrollView extends StatefulWidget { @@ -35,7 +35,7 @@ class ChatScrollView extends StatefulWidget { this.physics, }); - /// Optional session instance. If omitted, [SessionScope.of] is used. + /// Optional session instance. If omitted, [SessionContext.of] is used. final Session? session; /// Builder for each message. @@ -89,7 +89,7 @@ class _ChatScrollViewState extends State { } Session _resolveSession(BuildContext context) { - return widget.session ?? SessionScope.of(context); + return widget.session ?? SessionContext.of(context); } void _autoScrollIfNeeded(List messages) { From 4daad7642eb068ea474cf8d6a5a2d36abe0e5c3d Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 9 Dec 2025 19:10:21 +0900 Subject: [PATCH 18/24] Use SDK v2.6.0 --- lib/src/ui/session/chat_scroll_view.dart | 6 ++++-- pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/src/ui/session/chat_scroll_view.dart b/lib/src/ui/session/chat_scroll_view.dart index a980ceb..b59d989 100644 --- a/lib/src/ui/session/chat_scroll_view.dart +++ b/lib/src/ui/session/chat_scroll_view.dart @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:livekit_client/livekit_client.dart'; @@ -105,11 +107,11 @@ class _ChatScrollViewState extends State { if (!_controller.hasClients) { return; } - _controller.animateTo( + unawaited(_controller.animateTo( 0, duration: const Duration(milliseconds: 200), curve: Curves.easeOut, - ); + )); }); } diff --git a/pubspec.yaml b/pubspec.yaml index 3873a7b..d35d302 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flutter: sdk: flutter flutter_webrtc: 1.2.1 - livekit_client: ^2.4.9 + livekit_client: ^2.6.0 chat_bubbles: ^1.6.0 collection: ^1.19.0 flutter_background: ^1.3.0+1 From 84a9515cee4dfac23110ad6fe3bce07c32bfc2bc Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 9 Dec 2025 19:22:52 +0900 Subject: [PATCH 19/24] Update analysis_options.yaml --- analysis_options.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 355d33b..b13dcec 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -19,7 +19,7 @@ linter: prefer_final_locals: true unnecessary_brace_in_string_interps: false avoid_print: true - depend_on_referenced_packages: false + depend_on_referenced_packages: true # Enforce this for correct async logic unawaited_futures: true From bd8455d91bb7d6de10bd5db9c19d0859495ef293 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 9 Dec 2025 19:36:43 +0900 Subject: [PATCH 20/24] check mounted --- lib/src/ui/session/chat_scroll_view.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/src/ui/session/chat_scroll_view.dart b/lib/src/ui/session/chat_scroll_view.dart index b59d989..5245c1a 100644 --- a/lib/src/ui/session/chat_scroll_view.dart +++ b/lib/src/ui/session/chat_scroll_view.dart @@ -104,6 +104,9 @@ class _ChatScrollViewState extends State { } _lastMessageCount = messages.length; WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) { + return; + } if (!_controller.hasClients) { return; } From 5780752d599c5109e5eefd2942fbae0664c73d53 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 9 Dec 2025 19:41:38 +0900 Subject: [PATCH 21/24] add dispose to doc --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 2537f6f..fd75c58 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,13 @@ SessionContext( ), ), ); + +// Dispose the session when the widget is disposed: +@override +void dispose() { + session.dispose(); + super.dispose(); +} ``` - `ChatScrollView` auto-scrolls to the newest message (bottom). Pass a From c16b2250929f87f281276ef705740c827e8c5b9d Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 9 Dec 2025 19:52:36 +0900 Subject: [PATCH 22/24] Update README.md --- README.md | 64 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index fd75c58..1ef739d 100644 --- a/README.md +++ b/README.md @@ -116,28 +116,48 @@ available to widgets like `ChatScrollView`: import 'package:livekit_client/livekit_client.dart'; import 'package:livekit_components/livekit_components.dart'; -final session = Session.withAgent( - 'my-agent', - tokenSource: EndpointTokenSource(Uri.parse('https://your-token-endpoint')), - options: const SessionOptions(preConnectAudio: true), -); - -SessionContext( - session: session, - child: ChatScrollView( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), - messageBuilder: (context, message) => ListTile( - title: Text(message.content.text), - subtitle: Text(message.timestamp.toLocal().toIso8601String()), - ), - ), -); - -// Dispose the session when the widget is disposed: -@override -void dispose() { - session.dispose(); - super.dispose(); +class AgentChatView extends StatefulWidget { + const AgentChatView({super.key}); + + @override + State createState() => _AgentChatViewState(); +} + +class _AgentChatViewState extends State { + late final Session _session; + + @override + void initState() { + super.initState(); + _session = Session.withAgent( + 'my-agent', + tokenSource: EndpointTokenSource( + url: Uri.parse('https://your-token-endpoint'), + ), + options: const SessionOptions(preConnectAudio: true), + ); + unawaited(_session.start()); // start connecting the agent session + } + + @override + void dispose() { + _session.dispose(); // ends the session and cleans up listeners + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SessionContext( + session: _session, + child: ChatScrollView( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), + messageBuilder: (context, message) => ListTile( + title: Text(message.content.text), + subtitle: Text(message.timestamp.toLocal().toIso8601String()), + ), + ), + ); + } } ``` From ab749c0abfe56029dc92a80cdeaaed8cbe02b616 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 9 Dec 2025 20:20:00 +0900 Subject: [PATCH 23/24] Create session-api --- .changes/session-api | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changes/session-api diff --git a/.changes/session-api b/.changes/session-api new file mode 100644 index 0000000..8cc6f07 --- /dev/null +++ b/.changes/session-api @@ -0,0 +1 @@ +minor type="added" "Session API" From b087099c342a2ba2e702852c4371deecd276e550 Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Tue, 9 Dec 2025 20:33:31 +0900 Subject: [PATCH 24/24] Update session-api --- .changes/session-api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changes/session-api b/.changes/session-api index 8cc6f07..89e824e 100644 --- a/.changes/session-api +++ b/.changes/session-api @@ -1 +1 @@ -minor type="added" "Session API" +minor type="added" "Session components"