Skip to content

Commit

Permalink
feat: Improve layout sorting.
Browse files Browse the repository at this point in the history
  • Loading branch information
cloudwebrtc committed Oct 26, 2024
1 parent 36b673d commit 8e5f081
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 74 deletions.
14 changes: 7 additions & 7 deletions lib/src/context/room_context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> _pinnedTrackSid = [];
List<String> get pinnedTracks => _pinnedTrackSid;
final List<String> _pinnedTracks = [];
List<String> get pinnedTracks => _pinnedTracks;

bool get isPinnedTracksEmpty => _pinnedTrackSid.isEmpty;
bool get isPinnedTracksEmpty => _pinnedTracks.isEmpty;

LocalVideoTrack? _localVideoTrack;

Expand Down
9 changes: 9 additions & 0 deletions lib/src/types/track_identifier.dart
Original file line number Diff line number Diff line change
@@ -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;
}
73 changes: 34 additions & 39 deletions lib/src/ui/builder/participant/participant_loop.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -20,16 +21,16 @@ class ParticipantLoop extends StatelessWidget {
});

final WidgetBuilder participantBuilder;
final Map<Participant, TrackPublication?> Function(
Map<Participant, TrackPublication?> tracks)? sorting;
final List<MapEntry<TrackIdentifier, TrackPublication?>> Function(
List<MapEntry<TrackIdentifier, TrackPublication?>> tracks)? sorting;
final ParticipantLayoutBuilder layoutBuilder;

final bool showAudioTracks;
final bool showVideoTracks;

Map<Participant, TrackPublication?> buildTracksMap(
List<MapEntry<TrackIdentifier, TrackPublication?>> buildTracksMap(
bool audio, bool video, List<Participant> participants) {
final Map<Participant, TrackPublication?> trackMap = {};
final List<MapEntry<TrackIdentifier, TrackPublication?>> trackMap = [];
int index = 0;
for (Participant participant in participants) {
Debug.log('=> participant ${participant.identity}, index: $index');
Expand All @@ -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}');
Expand All @@ -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}');
}
Expand All @@ -65,13 +66,12 @@ class ParticipantLoop extends StatelessWidget {
return Consumer<RoomContext>(
builder: (context, roomCtx, child) {
Debug.log('> ParticipantLoop for ${roomCtx.roomName}');

return Selector<RoomContext, List<Participant>>(
selector: (context, participants) => roomCtx.participants,
shouldRebuild: (previous, next) => previous.length != next.length,
builder: (context, participants, child) {
var roomCtx = Provider.of<RoomContext>(context);

Map<String, Widget> trackWidgets = {};
List<TrackWidget> trackWidgets = [];

var trackMap = buildTracksMap(
showAudioTracks, showVideoTracks, participants);
Expand All @@ -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<Widget> pinned = [];

/// Move focused tracks to the pinned list
var focused = roomCtx.pinnedTracks
.map((e) {
if (trackWidgets.containsKey(e)) {
return trackWidgets[e]!;
}
return null;
})
.whereType<Widget>()
.toList();

if (focused.isNotEmpty) {
children.removeWhere((e) => focused.contains(e));
pinned.addAll(focused);
}

return layoutBuilder.build(context, children, pinned);
return Selector<RoomContext, List<String>>(
selector: (context, pinnedTracks) => roomCtx.pinnedTracks,
builder: (context, pinnedTracks, child) {
return layoutBuilder.build(
context, trackWidgets, pinnedTracks);
});
});
},
);
Expand Down
46 changes: 33 additions & 13 deletions lib/src/ui/layout/garousel_ayout.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:collection';
import 'dart:math' as math;

import 'package:flutter/widgets.dart';
Expand All @@ -11,20 +12,39 @@ class CarouselLayoutBuilder implements ParticipantLayoutBuilder {

@override
Widget build(
BuildContext context, List<Widget> children, List<Widget>? pinned) {
Widget? pinnedWidget;
BuildContext context,
List<TrackWidget> children,
List<String> pinnedTracks,
) {
List<Widget> pinnedWidgets = [];
List<Widget> 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);
}
}

Expand All @@ -49,7 +69,7 @@ class CarouselLayoutBuilder implements ParticipantLayoutBuilder {
),
),
Expanded(
child: pinnedWidget ?? Container(),
child: singlePinnedWidget ?? Container(),
),
],
);
Expand All @@ -70,7 +90,7 @@ class CarouselLayoutBuilder implements ParticipantLayoutBuilder {
),
),
Expanded(
child: pinnedWidget ?? Container(),
child: singlePinnedWidget ?? Container(),
),
],
);
Expand Down
10 changes: 5 additions & 5 deletions lib/src/ui/layout/grid_layout.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ class GridLayoutBuilder implements ParticipantLayoutBuilder {

@override
Widget build(
BuildContext context, List<Widget> children, List<Widget>? pinned) {
BuildContext context,
List<TrackWidget> children,
List<String> pinnedTracks,
) {
var deviceScreenType = getDeviceType(MediaQuery.of(context).size);
var orientation = MediaQuery.of(context).orientation;
return GridView.count(
Expand All @@ -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(),
);
}
}
17 changes: 9 additions & 8 deletions lib/src/ui/layout/layouts.dart
Original file line number Diff line number Diff line change
@@ -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<Widget> children, List<Widget>? pinned);
BuildContext context,
List<TrackWidget> children,
List<String> pinnedTracks,
);
}
3 changes: 1 addition & 2 deletions lib/src/ui/widgets/track/track_stats_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ class TrackStatsWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
var trackCtx = Provider.of<TrackReferenceContext?>(context);
var roomCtx = Provider.of<RoomContext>(context);

if (trackCtx == null || !roomCtx.pinnedTracks.contains(trackCtx.sid)) {
if (trackCtx == null) {
return const SizedBox();
}

Expand Down

0 comments on commit 8e5f081

Please sign in to comment.