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: diff --git a/analysis_options.yaml b/analysis_options.yaml index 1923cb6..f9517a1 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,18 +1,24 @@ -include: package:lints/recommended.yaml +include: package:flutter_lints/flutter.yaml analyzer: errors: constant_identifier_names: ignore - must_be_immutable: ignore no_wildcard_variable_uses: ignore use_super_parameters: ignore + avoid_print: ignore linter: rules: - # - # Additional recommended rules - # + # Preference of the SDK prefer_single_quotes: true + prefer_final_locals: true unnecessary_brace_in_string_interps: false + avoid_print: true + + # Enforce this for correct async logic unawaited_futures: true - depend_on_referenced_packages: false + discarded_futures: true + +formatter: + page_width: 120 + trailing_commas: preserve 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..f9517a1 100644 --- a/example/analysis_options.yaml +++ b/example/analysis_options.yaml @@ -1,31 +1,24 @@ -# 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`. +include: package:flutter_lints/flutter.yaml -# 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 + constant_identifier_names: ignore + no_wildcard_variable_uses: ignore + use_super_parameters: ignore + avoid_print: ignore 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 + # Preference of the SDK + prefer_single_quotes: true + prefer_final_locals: true + unnecessary_brace_in_string_interps: false + avoid_print: true + + # Enforce this for correct async logic + unawaited_futures: true + discarded_futures: true -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options +formatter: + page_width: 120 + trailing_commas: preserve diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 7bb2df6..afa1e8e 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip diff --git a/example/android/settings.gradle b/example/android/settings.gradle index b9e43bd..72e8d42 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -18,8 +18,8 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.1.0" apply false - id "org.jetbrains.kotlin.android" version "1.8.22" apply false + id "com.android.application" version "8.7.2" apply false + id "org.jetbrains.kotlin.android" version "2.1.0" apply false } include ":app" 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/lib/main.dart b/example/lib/main.dart index 46e4108..26f892d 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -115,11 +115,10 @@ class _MyHomePageState extends State { }, ), builder: (context, roomCtx) { - var deviceScreenType = getDeviceType(MediaQuery.of(context).size); + final 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/example/pubspec.yaml b/example/pubspec.yaml index 6031ef6..cd7f8b4 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -19,14 +19,8 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ^3.5.2 + sdk: ">=3.6.0 <4.0.0" -# 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/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/chat_context.dart b/lib/src/context/chat_context.dart index f6ad1d6..c4a7f1c 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 { @@ -87,7 +84,7 @@ mixin ChatContextMixin on ChangeNotifier { } } - void sendMessage(String message) { + Future sendMessage(String message) async { final msg = ChatMessage( message: message, timestamp: DateTime.now().millisecondsSinceEpoch, @@ -96,8 +93,10 @@ mixin ChatContextMixin on ChangeNotifier { participant: _localParticipant, ); addMessage(msg); - _localParticipant?.publishData(const Utf8Encoder().convert(msg.toJson()), - topic: 'lk-chat-topic'); + await _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..97196bb 100644 --- a/lib/src/context/media_device_context.dart +++ b/lib/src/context/media_device_context.dart @@ -35,7 +35,7 @@ class MediaDeviceContext extends ChangeNotifier { required RoomContext roomCtx, }) : _roomCtx = roomCtx, _room = roomCtx.room { - loadDevices(); + unawaited(loadDevices()); } final RoomContext _roomCtx; final Room? _room; @@ -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(); @@ -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( @@ -199,7 +199,7 @@ class MediaDeviceContext extends ChangeNotifier { return; } Debug.log('DesktopCapturerSource: ${source.id}'); - var track = await LocalVideoTrack.createScreenShareTrack( + final track = await LocalVideoTrack.createScreenShareTrack( ScreenShareCaptureOptions( sourceId: source.id, maxFrameRate: 15.0, @@ -213,7 +213,7 @@ class MediaDeviceContext extends ChangeNotifier { } if (lkPlatformIs(PlatformType.android)) { // Android specific - bool hasCapturePermission = await Helper.requestCapturePermission(); + final hasCapturePermission = await Helper.requestCapturePermission(); if (!hasCapturePermission) { return; } @@ -245,7 +245,7 @@ class MediaDeviceContext extends ChangeNotifier { await requestBackgroundPermission(); } if (lkPlatformIs(PlatformType.iOS)) { - var track = await LocalVideoTrack.createScreenShareTrack( + final track = await LocalVideoTrack.createScreenShareTrack( const ScreenShareCaptureOptions( useiOSBroadcastExtension: true, maxFrameRate: 15.0, @@ -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; } @@ -317,6 +317,6 @@ class MediaDeviceContext extends ChangeNotifier { @override void dispose() { super.dispose(); - _deviceChangeSub?.cancel(); + unawaited(_deviceChangeSub?.cancel()); } } diff --git a/lib/src/context/participant_context.dart b/lib/src/context/participant_context.dart index 12a2b44..7cb28df 100644 --- a/lib/src/context/participant_context.dart +++ b/lib/src/context/participant_context.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'; @@ -28,12 +30,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 +41,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 +65,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 +82,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(); } }) @@ -125,21 +117,23 @@ class ParticipantContext extends ChangeNotifier { @override void dispose() { super.dispose(); - _listener.dispose(); + unawaited(_disposeListener()); createCount--; Debug.log('Participant::dispose count $createCount'); } + Future _disposeListener() async { + await _listener.dispose(); + } + 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 +146,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 e98ece3..b611347 100644 --- a/lib/src/context/room_context.dart +++ b/lib/src/context/room_context.dart @@ -19,12 +19,11 @@ 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'; -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) { @@ -153,7 +145,11 @@ class RoomContext extends ChangeNotifier if (connect && url != null && token != null) { _url = url; _token = token; - this.connect(url: url, token: token); + unawaited(this.connect(url: url, token: token).catchError((error, stack) { + Debug.event('RoomContext: auto connect failed: $error'); + // Preserve original behavior by rethrowing after logging. + throw error; + })); } } @@ -198,11 +194,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; @@ -320,8 +316,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..1cc0656 100644 --- a/lib/src/context/track_reference_context.dart +++ b/lib/src/context/track_reference_context.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'; @@ -39,29 +41,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(); } }); @@ -70,10 +68,10 @@ class TrackReferenceContext extends ChangeNotifier { @override void dispose() { super.dispose(); - _listener.cancelAll(); - _listener.dispose(); + unawaited(_listener.cancelAll()); + unawaited(_disposeListener()); if (_statsListener != null) { - _statsListener!.dispose(); + unawaited(_disposeStatsListener()); } } @@ -113,7 +111,7 @@ class TrackReferenceContext extends ChangeNotifier { } } else { if (_statsListener != null) { - _statsListener!.dispose(); + unawaited(_disposeStatsListener()); } _stats = {}; } @@ -127,30 +125,27 @@ class TrackReferenceContext extends ChangeNotifier { void _setUpListener(Track track) { if (_statsListener != null) { - _statsListener!.dispose(); + unawaited(_disposeStatsListener()); } _statsListener = track.createListener(); if (track is LocalVideoTrack) { _statsListener?.on((event) { - Map stats = {}; + final stats = {}; stats['tx'] = 'total sent ${event.currentBitrate.toInt()} kpbs'; event.stats.forEach((key, value) { 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']; + final 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; @@ -158,11 +153,10 @@ class TrackReferenceContext extends ChangeNotifier { }); } else if (track is RemoteVideoTrack) { _statsListener?.on((event) { - Map stats = {}; + final 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'] = @@ -180,11 +174,10 @@ class TrackReferenceContext extends ChangeNotifier { }); } else if (track is LocalAudioTrack) { _statsListener?.on((event) { - Map stats = {}; + final 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; @@ -192,12 +185,11 @@ class TrackReferenceContext extends ChangeNotifier { }); } else if (track is RemoteAudioTrack) { _statsListener?.on((event) { - Map stats = {}; + final stats = {}; 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'; @@ -211,4 +203,12 @@ class TrackReferenceContext extends ChangeNotifier { }); } } + + Future _disposeStatsListener() async { + await _statsListener?.dispose(); + } + + Future _disposeListener() async { + await _listener.dispose(); + } } diff --git a/lib/src/context/transcription_context.dart b/lib/src/context/transcription_context.dart index 98f30ae..5907670 100644 --- a/lib/src/context/transcription_context.dart +++ b/lib/src/context/transcription_context.dart @@ -12,8 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:collection/collection.dart'; +import 'dart:async'; + import 'package:flutter/material.dart'; + +import 'package:collection/collection.dart'; import 'package:livekit_client/livekit_client.dart'; import '../types/transcription.dart'; @@ -27,8 +30,11 @@ mixin TranscriptionContextMixin on ChangeNotifier { CancelListenFunc? _cancelListener; void transcriptionContextCleanUp() { - _cancelListener?.call(); + final cancel = _cancelListener; _cancelListener = null; + if (cancel != null) { + unawaited(cancel()); + } _transcriptionMap.clear(); } 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/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/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..22ba54c 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({ @@ -54,7 +53,7 @@ class ParticipantLoop extends StatelessWidget { for (Participant participant in participants) { Debug.log('=> participant ${participant.identity}, index: $index'); index++; - var tracks = participant.trackPublications.values; + final tracks = participant.trackPublications.values; for (var track in tracks) { if (track.kind == TrackType.AUDIO && !audio) { continue; @@ -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,12 +90,11 @@ class ParticipantLoop extends StatelessWidget { builder: (context, participants, child) { List trackWidgets = []; - var trackMap = buildTracksMap( - showAudioTracks, showVideoTracks, participants); + final trackMap = buildTracksMap(showAudioTracks, showVideoTracks, participants); for (var item in trackMap) { - var identifier = item.key; - var track = item.value; + final identifier = item.key; + final track = item.value; if (track != null) { trackWidgets.add( TrackWidget( @@ -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..73846e8 100644 --- a/lib/src/ui/builder/participant/participant_name.dart +++ b/lib/src/ui/builder/participant/participant_name.dart @@ -30,15 +30,14 @@ 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; + final trackCtx = Provider.of(context); + final isScreenShare = trackCtx?.isScreenShare ?? false; return Selector( selector: (context, name) => participantContext.name, builder: (context, name, child) { - var str = isScreenShare ? '$name\'s screen' : name; + final str = isScreenShare ? '$name\'s screen' : name; return builder(context, str); }, ); 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..ee37922 100644 --- a/lib/src/ui/builder/participant/participant_selector.dart +++ b/lib/src/ui/builder/participant/participant_selector.dart @@ -4,15 +4,14 @@ 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 { 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, @@ -26,7 +25,7 @@ class ParticipantSelector extends StatelessWidget { for (Participant participant in participants) { Debug.log('=> participant ${participant.identity}, index: $index'); index++; - var tracks = participant.trackPublications.values; + final tracks = participant.trackPublications.values; for (var track in tracks) { if (track.kind == TrackType.AUDIO && !audio) { continue; @@ -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) || @@ -60,9 +58,8 @@ class ParticipantSelector extends StatelessWidget { selector: (context, participants) => roomCtx.participants, 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; + final trackMap = buildTracksMap(true, true, participants); + final 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 1eac20b..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 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 d924cc9..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 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 bde1836..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 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 5db5c5a..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 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..3464f2b 100644 --- a/lib/src/ui/builder/room/room_connection_state.dart +++ b/lib/src/ui/builder/room/room_connection_state.dart @@ -22,18 +22,16 @@ import '../../../debug/logger.dart'; class RoomConnectionState extends StatelessWidget { const RoomConnectionState({ - Key? key, + super.key, 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 2b77fa7..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 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 8a10ea5..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 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/room/transcription.dart b/lib/src/ui/builder/room/transcription.dart index 8a07dcb..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'; @@ -27,7 +28,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/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..4265731 100644 --- a/lib/src/ui/builder/track/is_speaking_indicator.dart +++ b/lib/src/ui/builder/track/is_speaking_indicator.dart @@ -16,16 +16,15 @@ class IsSpeakingIndicator extends StatelessWidget { @override Widget build(BuildContext context) { - var participantContext = Provider.of(context); - var trackCtx = Provider.of(context); + final participantContext = Provider.of(context); + final trackCtx = Provider.of(context); /// Show speaking indicator only if the participant is not sharing screen - var showSpeakingIndicator = !(trackCtx?.isScreenShare ?? true); + final showSpeakingIndicator = !(trackCtx?.isScreenShare ?? true); 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..74ecd94 100644 --- a/lib/src/ui/layout/carousel_layout.dart +++ b/lib/src/ui/layout/carousel_layout.dart @@ -30,15 +30,13 @@ class CarouselLayoutBuilder implements ParticipantLayoutBuilder { List children, List pinnedTracks, ) { - List pinnedWidgets = []; - List otherWidgets = []; + final pinnedWidgets = []; + final otherWidgets = []; /// 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; + final widget = + children.where((element) => element.trackIdentifier.identifier == sid).map((e) => e.widget).firstOrNull; if (widget != null) { pinnedWidgets.add(widget); } @@ -62,9 +60,9 @@ class CarouselLayoutBuilder implements ParticipantLayoutBuilder { } } - var deviceScreenType = getDeviceType(MediaQuery.of(context).size); - var orientation = MediaQuery.of(context).orientation; - var isMobile = deviceScreenType == DeviceScreenType.mobile; + final deviceScreenType = getDeviceType(MediaQuery.of(context).size); + final orientation = MediaQuery.of(context).orientation; + final isMobile = deviceScreenType == DeviceScreenType.mobile; if (isMobile && orientation == Orientation.portrait) { return Column( mainAxisAlignment: MainAxisAlignment.start, diff --git a/lib/src/ui/layout/grid_layout.dart b/lib/src/ui/layout/grid_layout.dart index d5132a1..286229f 100644 --- a/lib/src/ui/layout/grid_layout.dart +++ b/lib/src/ui/layout/grid_layout.dart @@ -27,13 +27,10 @@ class GridLayoutBuilder implements ParticipantLayoutBuilder { List children, List pinnedTracks, ) { - var deviceScreenType = getDeviceType(MediaQuery.of(context).size); - var orientation = MediaQuery.of(context).orientation; + final deviceScreenType = getDeviceType(MediaQuery.of(context).size); + final 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..8b3e0a0 100644 --- a/lib/src/ui/layout/sorting.dart +++ b/lib/src/ui/layout/sorting.dart @@ -17,7 +17,7 @@ import 'package:livekit_client/livekit_client.dart'; import 'layouts.dart'; List defaultSorting(List trackWidgets) { - List trackWidgetsSorted = []; + final trackWidgetsSorted = []; /// sort screen shares and local participant first for (var element in trackWidgets) { @@ -32,8 +32,8 @@ List defaultSorting(List trackWidgets) { // sort speakers for the grid trackWidgetsSorted.sort((a, b) { // loudest speaker first - var participantA = a.trackIdentifier.participant; - var participantB = b.trackIdentifier.participant; + final participantA = a.trackIdentifier.participant; + final participantB = b.trackIdentifier.participant; if (participantA.isSpeaking && participantB.isSpeaking) { if (participantA.audioLevel > participantB.audioLevel) { return -1; @@ -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 daa2109..9c50cfd 100644 --- a/lib/src/ui/prejoin/prejoin.dart +++ b/lib/src/ui/prejoin/prejoin.dart @@ -26,26 +26,58 @@ import '../widgets/room/join_button.dart'; 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}); +class Prejoin extends StatefulWidget { + const 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; + final String token; - String url; + final String url; - void onTextTokenChanged(String token) async { - this.token = token; + @override + State createState() => _PrejoinState(); +} + +class _PrejoinState extends State { + late String _token; + + late String _url; + + @override + void initState() { + super.initState(); + _token = widget.token; + _url = widget.url; + } + + @override + void didUpdateWidget(covariant Prejoin oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.token != widget.token) { + _token = widget.token; + } + if (oldWidget.url != widget.url) { + _url = widget.url; + } + } + + void onTextTokenChanged(String token) { + setState(() { + _token = token; + }); } - void onTextUrlChanged(String url) async { - this.url = url; + void onTextUrlChanged(String url) { + setState(() { + _url = url; + }); } - void _handleJoinPressed(RoomContext roomCtx) async { - if (onJoinPressed == null) { + Future _handleJoinPressed(RoomContext roomCtx) async { + final token = _token; + final url = _url; + if (widget.onJoinPressed == null) { Debug.event('Joining room: $url'); try { await roomCtx.connect( @@ -57,14 +89,13 @@ class Prejoin extends StatelessWidget { } return; } - onJoinPressed?.call(roomCtx, url, token); + widget.onJoinPressed?.call(roomCtx, url, token); } @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 +108,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 +116,7 @@ class Prejoin extends StatelessWidget { child: Container( padding: const EdgeInsets.all(8.0), child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceEvenly, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ MicrophoneSelectButton(), CameraSelectButton( @@ -103,7 +132,7 @@ class Prejoin extends StatelessWidget { child: TextInput( onTextChanged: onTextUrlChanged, hintText: 'Enter Livekit Server URL', - text: url, + text: _url, ), ), ), @@ -114,7 +143,7 @@ class Prejoin extends StatelessWidget { child: TextInput( onTextChanged: onTextTokenChanged, hintText: 'Enter Token', - text: token, + text: _token, ), ), ), @@ -124,8 +153,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 36403ca..a4d41ce 100644 --- a/lib/src/ui/prejoin/text_input.dart +++ b/lib/src/ui/prejoin/text_input.dart @@ -15,16 +15,12 @@ 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; - final Function(String) onTextChanged; + final void Function(String) onTextChanged; final String hintText; @@ -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..93e9824 100644 --- a/lib/src/ui/widgets/participant/connection_quality_indicator.dart +++ b/lib/src/ui/widgets/participant/connection_quality_indicator.dart @@ -19,16 +19,14 @@ import 'package:livekit_client/livekit_client.dart'; class ConnectionQualityIndicatorWidget extends StatelessWidget { final ConnectionQuality connectionQuality; - ConnectionQualityIndicatorWidget({required this.connectionQuality}); + const ConnectionQualityIndicatorWidget({super.key, required this.connectionQuality}); @override Widget build(BuildContext context) { 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..a27f9b0 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}'); - var trackCtx = Provider.of(context); - var isScreenShare = trackCtx?.isScreenShare ?? false; + Debug.log('===> ParticipantStatusBar for ${participantContext.name}'); + final trackCtx = Provider.of(context); + final 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..c81e794 100644 --- a/lib/src/ui/widgets/participant/participant_tile_widget.dart +++ b/lib/src/ui/widgets/participant/participant_tile_widget.dart @@ -32,9 +32,8 @@ class ParticipantTileWidget extends StatelessWidget { @override Widget build(BuildContext context) { - var trackCtx = Provider.of(context); - Debug.log( - '> ParticipantTile for track ${trackCtx?.sid}@${trackCtx?.participant.identity}'); + final trackCtx = Provider.of(context); + Debug.log('> ParticipantTile for track ${trackCtx?.sid}@${trackCtx?.participant.identity}'); return Stack( children: [ diff --git a/lib/src/ui/widgets/participant/transcription_widget.dart b/lib/src/ui/widgets/participant/transcription_widget.dart index baa0457..bddf53e 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'; @@ -20,7 +21,7 @@ import '../../../types/transcription.dart'; class TranscriptionMessageBubble extends StatelessWidget { final TranscriptionForParticipant transcriptionForParticipant; - TranscriptionMessageBubble({required this.transcriptionForParticipant}); + const TranscriptionMessageBubble({super.key, required this.transcriptionForParticipant}); @override Widget build(BuildContext context) { @@ -51,7 +52,7 @@ class TranscriptionMessageBubble extends StatelessWidget { } class TranscriptionWidget extends StatelessWidget { - TranscriptionWidget({ + const TranscriptionWidget({ super.key, required this.transcriptions, this.padding, diff --git a/lib/src/ui/widgets/room/camera_select_button.dart b/lib/src/ui/widgets/room/camera_select_button.dart index 8916b81..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 { @@ -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 139433a..0db5818 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; @@ -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 bc9c26d..0db78f5 100644 --- a/lib/src/ui/widgets/room/chat_toggle.dart +++ b/lib/src/ui/widgets/room/chat_toggle.dart @@ -20,7 +20,8 @@ import 'package:responsive_builder/responsive_builder.dart'; import '../theme.dart'; class ChatToggleWidget extends StatelessWidget { - ChatToggleWidget({ + const ChatToggleWidget({ + super.key, required this.isChatOpen, required this.toggleChat, this.showLabel = true, @@ -32,7 +33,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; @@ -42,15 +43,12 @@ class ChatToggleWidget extends StatelessWidget { @override Widget build(BuildContext context) { - var deviceScreenType = getDeviceType(MediaQuery.of(context).size); + final 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 06600ef..19b2cc3 100644 --- a/lib/src/ui/widgets/room/chat_widget.dart +++ b/lib/src/ui/widgets/room/chat_widget.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:chat_bubbles/chat_bubbles.dart'; @@ -29,24 +31,22 @@ 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) { - List msgWidgets = []; + final msgWidgets = []; int lastTimestamp = 0; 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, @@ -62,8 +62,8 @@ class ChatWidget extends StatelessWidget { } void scrollToBottom() { - _scrollController.animateTo(_scrollController.position.maxScrollExtent, - duration: const Duration(milliseconds: 300), curve: Curves.easeOut); + unawaited(_scrollController.animateTo(_scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 300), curve: Curves.easeOut)); } @override @@ -83,10 +83,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..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'; @@ -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 6d9c524..9388b0e 100644 --- a/lib/src/ui/widgets/room/disconnect_button.dart +++ b/lib/src/ui/widgets/room/disconnect_button.dart @@ -45,18 +45,16 @@ class DisconnectButtonWidget extends StatelessWidget { final Color selectedColor; final Color selectedOverlayColor; - final Function()? onPressed; + final void Function()? onPressed; @override Widget build(BuildContext context) { - var deviceScreenType = getDeviceType(MediaQuery.of(context).size); + final 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( @@ -70,8 +68,13 @@ class DisconnectButtonWidget extends StatelessWidget { : const EdgeInsets.fromLTRB(12, 20, 12, 20), ), ), - onPressed: () => - onPressed?.call() ?? connected ? roomCtx.disconnect() : null, + onPressed: () async { + if (onPressed != null) { + onPressed!(); + } else if (connected) { + await 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..0d69dfb 100644 --- a/lib/src/ui/widgets/room/join_button.dart +++ b/lib/src/ui/widgets/room/join_button.dart @@ -18,7 +18,7 @@ import '../../../context/room_context.dart'; import '../theme.dart'; class JoinButtonWidget extends StatelessWidget { - JoinButtonWidget({ + const JoinButtonWidget({ Key? key, required this.roomCtx, required this.connected, @@ -30,9 +30,9 @@ class JoinButtonWidget extends StatelessWidget { this.disabledOverlayColor = LKColors.lkLightBlue, }) : super(key: key); - RoomContext roomCtx; - bool connected; - final Function()? onPressed; + final RoomContext roomCtx; + final bool connected; + final void Function()? onPressed; final Color backgroundColor; final Color foregroundColor; @@ -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 ca6baaa..1b2a815 100644 --- a/lib/src/ui/widgets/room/media_device_select_button.dart +++ b/lib/src/ui/widgets/room/media_device_select_button.dart @@ -50,28 +50,24 @@ 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; @override Widget build(BuildContext context) { - var deviceScreenType = getDeviceType(MediaQuery.of(context).size); + final deviceScreenType = getDeviceType(MediaQuery.of(context).size); 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..cc7dc00 100644 --- a/lib/src/ui/widgets/room/screenshare_toggle.dart +++ b/lib/src/ui/widgets/room/screenshare_toggle.dart @@ -47,15 +47,13 @@ class ScreenShareToggleWidget extends StatelessWidget { @override Widget build(BuildContext context) { - var deviceScreenType = getDeviceType(MediaQuery.of(context).size); + final 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 a35be40..311aac8 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; @@ -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 2629f4d..0ad51e3 100644 --- a/lib/src/ui/widgets/toast.dart +++ b/lib/src/ui/widgets/toast.dart @@ -39,20 +39,19 @@ class ToastWidget extends StatefulWidget { ToastState createState() => ToastState(); } -class ToastState extends State - with SingleTickerProviderStateMixin { +class ToastState extends State with SingleTickerProviderStateMixin { AnimationController? _animationController; - late Animation _fadeAnimation; + late Animation _fadeAnimation; Timer? _timer; ConnectionState _connectionState = ConnectionState.disconnected; - showIt() { - _animationController!.forward(); + void showIt() { + unawaited(_animationController?.forward()); } - hideIt() { - _animationController!.reverse(); + void hideIt() { + unawaited(_animationController?.reverse()); _timer?.cancel(); _timer = null; } @@ -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) { @@ -107,7 +104,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, diff --git a/lib/src/ui/widgets/track/audio_visualizer_widget.dart b/lib/src/ui/widgets/track/audio_visualizer_widget.dart index fb1234c..f1fe80a 100644 --- a/lib/src/ui/widgets/track/audio_visualizer_widget.dart +++ b/lib/src/ui/widgets/track/audio_visualizer_widget.dart @@ -1,7 +1,9 @@ +import 'dart:async'; 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'; @@ -133,8 +135,7 @@ class _SoundWaveformWidgetState extends State with SingleTi if (didUpdateParams) { // Re-attach listeners - _detachListeners(); - _attachListeners(); + unawaited(_detachListeners().then((_) => _attachListeners())); } } @@ -198,20 +199,21 @@ class _SoundWaveformWidgetState extends State with SingleTi _controller = AnimationController( duration: Duration(milliseconds: widget.options.durationInMilliseconds), vsync: this, - )..repeat(reverse: true); + ); + unawaited(_controller.repeat(reverse: true)); _pulseAnimation = CurvedAnimation( parent: _controller, curve: Curves.easeInOut, ); - _attachListeners(); + unawaited(_attachListeners()); } @override void dispose() { _controller.dispose(); - _detachListeners(); + unawaited(_detachListeners()); super.dispose(); } diff --git a/lib/src/ui/widgets/track/focus_toggle.dart b/lib/src/ui/widgets/track/focus_toggle.dart index d64565e..868409e 100644 --- a/lib/src/ui/widgets/track/focus_toggle.dart +++ b/lib/src/ui/widgets/track/focus_toggle.dart @@ -31,14 +31,13 @@ class FocusToggle extends StatelessWidget { @override Widget build(BuildContext context) { final roomCtx = context.read(); - var trackCtx = Provider.of(context); + final trackCtx = Provider.of(context); final String? sid = trackCtx?.sid; Debug.log('===> FocusButton for $sid'); if (trackCtx == null) { return const SizedBox(); } - var shouldShowBackToGridView = - roomCtx.pinnedTracks.contains(sid) && sid == roomCtx.pinnedTracks.first; + final 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/no_track_widget.dart b/lib/src/ui/widgets/track/no_track_widget.dart index 79ff023..4ae83ee 100644 --- a/lib/src/ui/widgets/track/no_track_widget.dart +++ b/lib/src/ui/widgets/track/no_track_widget.dart @@ -34,7 +34,7 @@ class NoTrackWidget extends StatelessWidget { @override Widget build(BuildContext context) { - var trackCtx = Provider.of(context); + final trackCtx = Provider.of(context); final String? sid = trackCtx?.sid; Debug.log('===> NoTrackWidget for $sid'); diff --git a/lib/src/ui/widgets/track/track_stats_widget.dart b/lib/src/ui/widgets/track/track_stats_widget.dart index 815f46c..3d6732a 100644 --- a/lib/src/ui/widgets/track/track_stats_widget.dart +++ b/lib/src/ui/widgets/track/track_stats_widget.dart @@ -9,18 +9,16 @@ class TrackStatsWidget extends StatelessWidget { @override Widget build(BuildContext context) { - var trackCtx = Provider.of(context); + final trackCtx = Provider.of(context); if (trackCtx == null) { return const SizedBox(); } 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 diff --git a/lib/src/ui/widgets/track/video_track_widget.dart b/lib/src/ui/widgets/track/video_track_widget.dart index 8b9203e..00469d2 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'; @@ -37,7 +38,7 @@ class VideoTrackWidget extends StatelessWidget { @override Widget build(BuildContext context) { - var trackCtx = Provider.of(context); + final trackCtx = Provider.of(context); final String? sid = trackCtx?.sid; Debug.log('===> VideoTrackWidget for $sid'); 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 d72c2ce..3e36d96 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,27 +4,26 @@ 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: - 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 - flutter_webrtc: ^1.0.0 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 dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^4.0.0 + flutter_lints: ^6.0.0 import_sorter: ^4.6.0 topics: @@ -33,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