diff --git a/lib/src/context/room_context.dart b/lib/src/context/room_context.dart index 8cbbb32..c589743 100644 --- a/lib/src/context/room_context.dart +++ b/lib/src/context/room_context.dart @@ -225,27 +225,27 @@ class RoomContext extends ChangeNotifier with ChatContextMixin { } void pinningTrack(String sid) { - _pinnedTrackSid.remove(sid); - _pinnedTrackSid.insert(0, sid); + _pinnedTracks.remove(sid); + _pinnedTracks.insert(0, sid); Debug.event('Pinning track: $sid'); notifyListeners(); } void unpinningTrack(String sid) { - _pinnedTrackSid.remove(sid); + _pinnedTracks.remove(sid); Debug.event('Unpinning track: $sid'); notifyListeners(); } void clearPinnedTracks() { - _pinnedTrackSid.clear(); + _pinnedTracks.clear(); notifyListeners(); } - final List _pinnedTrackSid = []; - List get pinnedTracks => _pinnedTrackSid; + final List _pinnedTracks = []; + List get pinnedTracks => _pinnedTracks; - bool get isPinnedTracksEmpty => _pinnedTrackSid.isEmpty; + bool get isPinnedTracksEmpty => _pinnedTracks.isEmpty; LocalVideoTrack? _localVideoTrack; diff --git a/lib/src/types/track_identifier.dart b/lib/src/types/track_identifier.dart new file mode 100644 index 0000000..a63cde0 --- /dev/null +++ b/lib/src/types/track_identifier.dart @@ -0,0 +1,9 @@ +import 'package:livekit_client/livekit_client.dart'; + +class TrackIdentifier { + TrackIdentifier(this.participant, [this.track]); + final Participant participant; + final TrackPublication? track; + + String? get identifier => track?.sid ?? participant.sid; +} diff --git a/lib/src/ui/builder/participant/participant_loop.dart b/lib/src/ui/builder/participant/participant_loop.dart index 191d9e8..c760d90 100644 --- a/lib/src/ui/builder/participant/participant_loop.dart +++ b/lib/src/ui/builder/participant/participant_loop.dart @@ -5,6 +5,7 @@ import 'package:provider/provider.dart'; import '../../../context/room_context.dart'; import '../../../debug/logger.dart'; +import '../../../types/track_identifier.dart'; import '../../layout/grid_layout.dart'; import '../../layout/layouts.dart'; import 'participant_track.dart'; @@ -20,16 +21,16 @@ class ParticipantLoop extends StatelessWidget { }); final WidgetBuilder participantBuilder; - final Map Function( - Map tracks)? sorting; + final List> Function( + List> tracks)? sorting; final ParticipantLayoutBuilder layoutBuilder; final bool showAudioTracks; final bool showVideoTracks; - Map buildTracksMap( + List> buildTracksMap( bool audio, bool video, List participants) { - final Map trackMap = {}; + final List> trackMap = []; int index = 0; for (Participant participant in participants) { Debug.log('=> participant ${participant.identity}, index: $index'); @@ -42,7 +43,7 @@ class ParticipantLoop extends StatelessWidget { if (track.kind == TrackType.VIDEO && !video) { continue; } - trackMap[participant] = track; + trackMap.add(MapEntry(TrackIdentifier(participant, track), track)); Debug.log( '=> ${track.source.toString()} track ${track.sid} for ${participant.identity}'); @@ -51,7 +52,7 @@ class ParticipantLoop extends StatelessWidget { if (!audio && !tracks.any((t) => t.kind == TrackType.VIDEO) || !video && tracks.any((t) => t.kind == TrackType.AUDIO) || tracks.isEmpty) { - trackMap[participant] = null; + trackMap.add(MapEntry(TrackIdentifier(participant), null)); Debug.log('=> no tracks for ${participant.identity}'); } @@ -65,13 +66,12 @@ class ParticipantLoop extends StatelessWidget { return Consumer( builder: (context, roomCtx, child) { Debug.log('> ParticipantLoop for ${roomCtx.roomName}'); + return Selector>( selector: (context, participants) => roomCtx.participants, shouldRebuild: (previous, next) => previous.length != next.length, builder: (context, participants, child) { - var roomCtx = Provider.of(context); - - Map trackWidgets = {}; + List trackWidgets = []; var trackMap = buildTracksMap( showAudioTracks, showVideoTracks, participants); @@ -80,43 +80,38 @@ class ParticipantLoop extends StatelessWidget { trackMap = sorting!(trackMap); } - for (var item in trackMap.entries) { - var participant = item.key; + for (var item in trackMap) { + var identifier = item.key; var track = item.value; if (track == null) { - trackWidgets[participant.identity] = ParticipantTrack( - participant: participant, - builder: (context) => participantBuilder(context), + trackWidgets.add( + TrackWidget( + identifier, + ParticipantTrack( + participant: identifier.participant, + builder: (context) => participantBuilder(context), + ), + ), ); } else { - trackWidgets[track.sid] = ParticipantTrack( - participant: participant, - track: track, - builder: (context) => participantBuilder(context), + trackWidgets.add( + TrackWidget( + identifier, + ParticipantTrack( + participant: identifier.participant, + track: track, + builder: (context) => participantBuilder(context), + ), + ), ); } } - - var children = trackWidgets.values.toList(); - List pinned = []; - - /// Move focused tracks to the pinned list - var focused = roomCtx.pinnedTracks - .map((e) { - if (trackWidgets.containsKey(e)) { - return trackWidgets[e]!; - } - return null; - }) - .whereType() - .toList(); - - if (focused.isNotEmpty) { - children.removeWhere((e) => focused.contains(e)); - pinned.addAll(focused); - } - - return layoutBuilder.build(context, children, pinned); + return Selector>( + selector: (context, pinnedTracks) => roomCtx.pinnedTracks, + builder: (context, pinnedTracks, child) { + return layoutBuilder.build( + context, trackWidgets, pinnedTracks); + }); }); }, ); diff --git a/lib/src/ui/layout/garousel_ayout.dart b/lib/src/ui/layout/garousel_ayout.dart index daa0e48..43a6ff4 100644 --- a/lib/src/ui/layout/garousel_ayout.dart +++ b/lib/src/ui/layout/garousel_ayout.dart @@ -1,3 +1,4 @@ +import 'dart:collection'; import 'dart:math' as math; import 'package:flutter/widgets.dart'; @@ -11,20 +12,39 @@ class CarouselLayoutBuilder implements ParticipantLayoutBuilder { @override Widget build( - BuildContext context, List children, List? pinned) { - Widget? pinnedWidget; + BuildContext context, + List children, + List pinnedTracks, + ) { + List pinnedWidgets = []; List otherWidgets = []; - if (pinned != null) { - pinnedWidget = pinned.firstOrNull; - if (children.length > 1) { - otherWidgets = pinned.skip(1).toList(); - otherWidgets.addAll(children); + /// 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; + if (widget != null) { + pinnedWidgets.add(widget); } - } else { - pinnedWidget = children.firstOrNull; - if (children.length > 1) { - otherWidgets = children.skip(1).toList(); + } + + Widget? singlePinnedWidget = pinnedWidgets.firstOrNull; + if (pinnedWidgets.length > 1) { + otherWidgets.addAll(pinnedWidgets.skip(1).toList()); + } + + for (var child in children) { + if (!pinnedTracks.contains(child.trackIdentifier.identifier)) { + otherWidgets.add(child.widget); + } + } + + if (pinnedWidgets.isEmpty) { + singlePinnedWidget = otherWidgets.firstOrNull; + if (otherWidgets.length > 1) { + otherWidgets.removeAt(0); } } @@ -49,7 +69,7 @@ class CarouselLayoutBuilder implements ParticipantLayoutBuilder { ), ), Expanded( - child: pinnedWidget ?? Container(), + child: singlePinnedWidget ?? Container(), ), ], ); @@ -70,7 +90,7 @@ class CarouselLayoutBuilder implements ParticipantLayoutBuilder { ), ), Expanded( - child: pinnedWidget ?? Container(), + child: singlePinnedWidget ?? Container(), ), ], ); diff --git a/lib/src/ui/layout/grid_layout.dart b/lib/src/ui/layout/grid_layout.dart index 8e3edec..4142ae4 100644 --- a/lib/src/ui/layout/grid_layout.dart +++ b/lib/src/ui/layout/grid_layout.dart @@ -9,7 +9,10 @@ class GridLayoutBuilder implements ParticipantLayoutBuilder { @override Widget build( - BuildContext context, List children, List? pinned) { + BuildContext context, + List children, + List pinnedTracks, + ) { var deviceScreenType = getDeviceType(MediaQuery.of(context).size); var orientation = MediaQuery.of(context).orientation; return GridView.count( @@ -18,10 +21,7 @@ class GridLayoutBuilder implements ParticipantLayoutBuilder { ? 2 : 4, childAspectRatio: 1.5, - children: [ - if (pinned != null) ...pinned, - ...children, - ], + children: children.map((e) => e.widget).toList(), ); } } diff --git a/lib/src/ui/layout/layouts.dart b/lib/src/ui/layout/layouts.dart index 1b65767..acc5326 100644 --- a/lib/src/ui/layout/layouts.dart +++ b/lib/src/ui/layout/layouts.dart @@ -1,16 +1,17 @@ import 'package:flutter/widgets.dart'; -class ParticipantIdentity { - const ParticipantIdentity({ - required this.identity, - this.sid, - }); +import '../../types/track_identifier.dart'; - final String identity; - final String? sid; +class TrackWidget { + TrackWidget(this.trackIdentifier, this.widget); + final TrackIdentifier trackIdentifier; + final Widget widget; } abstract class ParticipantLayoutBuilder { Widget build( - BuildContext context, List children, List? pinned); + BuildContext context, + List children, + List pinnedTracks, + ); } diff --git a/lib/src/ui/widgets/track/track_stats_widget.dart b/lib/src/ui/widgets/track/track_stats_widget.dart index c6ac3c6..ec5b25b 100644 --- a/lib/src/ui/widgets/track/track_stats_widget.dart +++ b/lib/src/ui/widgets/track/track_stats_widget.dart @@ -11,9 +11,8 @@ class TrackStatsWidget extends StatelessWidget { @override Widget build(BuildContext context) { var trackCtx = Provider.of(context); - var roomCtx = Provider.of(context); - if (trackCtx == null || !roomCtx.pinnedTracks.contains(trackCtx.sid)) { + if (trackCtx == null) { return const SizedBox(); }