Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,31 @@ abstract class ParticipantAction extends StreamExternalAction
class SetParticipantPinned extends ParticipantAction {
const SetParticipantPinned({
required this.sessionId,
required this.userId,
required this.pinned,
});

final String sessionId;
final String userId;
final bool pinned;

@override
List<Object?> get props => [sessionId, pinned];
List<Object?> get props => [sessionId, userId, pinned];
}

class UpdateViewportVisibility extends ParticipantAction {
const UpdateViewportVisibility({
required this.sessionId,
required this.userId,
required this.visibility,
});

final String sessionId;
final String userId;
final ViewportVisibility visibility;

@override
List<Object?> get props => [sessionId, visibility];
List<Object?> get props => [sessionId, userId, visibility];
}

class UpdateViewportVisibilities extends ParticipantAction {
Expand Down
2 changes: 2 additions & 0 deletions packages/stream_video/lib/src/call/call.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1522,10 +1522,12 @@ class Call {

Future<Result<None>> updateViewportVisibility({
required String sessionId,
required String userId,
required ViewportVisibility visibility,
}) async {
final action = UpdateViewportVisibility(
sessionId: sessionId,
userId: userId,
visibility: visibility,
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ mixin StateParticipantMixin on StateNotifier<CallState> {
) {
state = state.copyWith(
callParticipants: state.callParticipants.map((participant) {
if (participant.sessionId == action.sessionId) {
if (participant.sessionId == action.sessionId &&
participant.userId == action.userId) {
return participant.copyWith(isPinned: action.pinned);
}

Expand All @@ -30,7 +31,8 @@ mixin StateParticipantMixin on StateNotifier<CallState> {
) {
state = state.copyWith(
callParticipants: state.callParticipants.map((participant) {
if (participant.sessionId == action.sessionId) {
if (participant.sessionId == action.sessionId &&
participant.userId == action.userId) {
return participant.copyWith(
viewportVisibility: action.visibility,
);
Expand Down Expand Up @@ -70,9 +72,11 @@ mixin StateParticipantMixin on StateNotifier<CallState> {
() =>
'[participantUpdateSubscription] #${state.sessionId}; action: $action',
);

state = state.copyWith(
callParticipants: state.callParticipants.map((participant) {
final trackState = participant.publishedTracks[action.trackType];

if (participant.userId == action.userId &&
participant.sessionId == action.sessionId &&
trackState is RemoteTrackState) {
Expand All @@ -89,6 +93,7 @@ mixin StateParticipantMixin on StateNotifier<CallState> {
},
);
}

_logger.v(() => '[participantUpdateSubscription] pSame: $participant');
return participant;
}).toList(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ typedef Sort<T> = Comparator<T>;

/// Widget that renders all the [StreamCallParticipant], based on the number
/// of people in a call.
class StreamCallParticipants extends StatelessWidget {
class StreamCallParticipants extends StatefulWidget {
/// Creates a new instance of [StreamCallParticipant].
StreamCallParticipants({
super.key,
Expand Down Expand Up @@ -98,8 +98,32 @@ class StreamCallParticipants extends StatelessWidget {
}

@override
Widget build(BuildContext context) {
final participants = [...this.participants].where(filter).sorted(sort);
State<StreamCallParticipants> createState() => _StreamCallParticipantsState();
}

class _StreamCallParticipantsState extends State<StreamCallParticipants> {
List<CallParticipantState> _participants = [];
CallParticipantState? _screenShareParticipant;

@override
void initState() {
_recalculateParticipants();
super.initState();
}

@override
void didUpdateWidget(covariant StreamCallParticipants oldWidget) {
super.didUpdateWidget(oldWidget);

if (!const ListEquality<CallParticipantState>().equals(
widget.participants.toList(), oldWidget.participants.toList())) {
_recalculateParticipants();
}
}

void _recalculateParticipants() {
final participants = [...widget.participants].where(widget.filter).toList();
mergeSort(participants, compare: widget.sort);

final screenShareParticipant = participants.firstWhereOrNull(
(it) {
Expand All @@ -122,23 +146,33 @@ class StreamCallParticipants extends StatelessWidget {
},
);

if (screenShareParticipant != null) {
if (mounted) {
setState(() {
_participants = participants.toList();
_screenShareParticipant = screenShareParticipant;
});
}
}

@override
Widget build(BuildContext context) {
if (_screenShareParticipant != null) {
return ScreenShareCallParticipantsContent(
call: call,
participants: participants,
screenSharingParticipant: screenShareParticipant,
screenShareContentBuilder: screenShareContentBuilder,
screenShareParticipantBuilder: screenShareParticipantBuilder,
call: widget.call,
participants: _participants,
screenSharingParticipant: _screenShareParticipant!,
screenShareContentBuilder: widget.screenShareContentBuilder,
screenShareParticipantBuilder: widget.screenShareParticipantBuilder,
);
}

return RegularCallParticipantsContent(
call: call,
participants: participants,
layoutMode: layoutMode,
enableLocalVideo: enableLocalVideo,
callParticipantBuilder: callParticipantBuilder,
localVideoParticipantBuilder: localVideoParticipantBuilder,
call: widget.call,
participants: _participants,
layoutMode: widget.layoutMode,
enableLocalVideo: widget.enableLocalVideo,
callParticipantBuilder: widget.callParticipantBuilder,
localVideoParticipantBuilder: widget.localVideoParticipantBuilder,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,109 +94,6 @@ class MobileCallParticipantsGrid extends StatelessWidget {

@override
Widget build(BuildContext context) {
final participantsCount = participants.length;

if (participantsCount == 1) {
return Padding(
padding: padding,
child: itemBuilder(context, call, participants[0]),
);
}

if (participantsCount == 2) {
return Padding(
padding: padding,
child: Column(
children: [
Expanded(child: itemBuilder(context, call, participants[0])),
SizedBox(height: mainAxisSpacing),
Expanded(child: itemBuilder(context, call, participants[1])),
],
),
);
}

if (participantsCount == 3) {
return Padding(
padding: padding,
child: Column(
children: [
Expanded(child: itemBuilder(context, call, participants[0])),
SizedBox(height: mainAxisSpacing),
Expanded(
child: Row(
children: [
Expanded(child: itemBuilder(context, call, participants[1])),
SizedBox(width: crossAxisSpacing),
Expanded(child: itemBuilder(context, call, participants[2])),
],
),
),
],
),
);
}

if (participantsCount == 4) {
return Padding(
padding: padding,
child: Column(
children: [
Expanded(
child: Row(
children: [
Expanded(child: itemBuilder(context, call, participants[0])),
SizedBox(width: crossAxisSpacing),
Expanded(child: itemBuilder(context, call, participants[1])),
],
),
),
SizedBox(height: mainAxisSpacing),
Expanded(
child: Row(
children: [
Expanded(child: itemBuilder(context, call, participants[2])),
SizedBox(width: crossAxisSpacing),
Expanded(child: itemBuilder(context, call, participants[3])),
],
),
),
],
),
);
}

if (participantsCount == 5) {
return Padding(
padding: padding,
child: Row(
children: [
Expanded(
child: Column(
children: [
Expanded(child: itemBuilder(context, call, participants[0])),
SizedBox(height: mainAxisSpacing),
Expanded(child: itemBuilder(context, call, participants[1])),
],
),
),
SizedBox(width: crossAxisSpacing),
Expanded(
child: Column(
children: [
Expanded(child: itemBuilder(context, call, participants[2])),
SizedBox(height: mainAxisSpacing),
Expanded(child: itemBuilder(context, call, participants[3])),
SizedBox(height: mainAxisSpacing),
Expanded(child: itemBuilder(context, call, participants[4])),
],
),
),
],
),
);
}

const pageSize = 6;
final pages = participants.slices(pageSize);

Expand All @@ -220,21 +117,75 @@ class MobileCallParticipantsGrid extends StatelessWidget {
itemCount: pages.length,
scrollDirection: Axis.vertical,
itemBuilder: (context, index) {
final page = pages.elementAt(index);

return TileView(
padding: padding,
mainAxisSpacing: mainAxisSpacing,
crossAxisSpacing: crossAxisSpacing,
mainAxisCount: mainAxisCount,
crossAxisCount: crossAxisCount,
crossAxisAlignment: WrapAlignment.start,
mainAxisAlignment: WrapAlignment.start,
children: page.map(
(participant) {
return itemBuilder(context, call, participant);
},
),
final pageParticipants = pages.elementAt(index);
final pageParticipantsCount = pageParticipants.length;

Widget getParticipantTile(int index) {
if (index < pageParticipantsCount) {
return Expanded(
key: ValueKey(pageParticipants[index].sessionId),
child: itemBuilder(context, call, pageParticipants[index]),
);
}

return const Spacer();
}

if (index == 0) {
return Column(
children: [
if (pageParticipantsCount == 1) ...[
Expanded(
child: Padding(
padding: padding,
child: itemBuilder(context, call, pageParticipants[0]),
),
),
],
if (pageParticipantsCount == 2) ...[
getParticipantTile(0),
SizedBox(height: mainAxisSpacing),
getParticipantTile(1),
],
if (pageParticipantsCount >= 3) ...[
...pageParticipants.mapIndexed((index, element) {
if (index.isEven) {
return Expanded(
child: Row(
children: [
getParticipantTile(index),
SizedBox(width: crossAxisSpacing),
getParticipantTile(index + 1),
],
),
);
} else {
return SizedBox(height: mainAxisSpacing);
}
}),
],
],
);
}

return Column(
children: [
...List.generate(pageSize, (index) => index).map((index) {
if (index.isEven) {
return Expanded(
child: Row(
children: [
getParticipantTile(index),
SizedBox(width: crossAxisSpacing),
getParticipantTile(index + 1),
],
),
);
} else {
return SizedBox(height: mainAxisSpacing);
}
}),
],
);
},
);
Expand Down
Loading