Only show layout switch when it has an effect on the layout

This commit is contained in:
Robin
2026-07-27 18:19:01 +02:00
parent a443a55bec
commit 5a96d1769e
2 changed files with 62 additions and 33 deletions

View File

@@ -536,11 +536,15 @@ describe.each([
withTestScheduler(({ behavior, expectObservable }) => { withTestScheduler(({ behavior, expectObservable }) => {
// Starts as a one-on-one call, then Alice shares her screen, then Bob // Starts as a one-on-one call, then Alice shares her screen, then Bob
// joins, and finally Alice stops sharing her screen // joins, and finally Alice stops sharing her screen
const participantInputMarbles = " a--b"; const participantInputMarbles = " a--b";
const aliceSharingInputMarbles = "ny-n"; const aliceSharingInputMarbles = " ny-n";
// Starts in one-on-one mobile layout, then goes to spotlight layout for // Starts in one-on-one mobile layout, then goes to spotlight layout for
// the screen sharing and group call cases // the screen sharing and group call cases
const expectedLayoutMarbles = " ab-c"; const expectedLayoutMarbles = " ab-c";
// Whether the layout switch is visible. It should be hidden while in
// one-on-one layout.
const expectedLayoutSwitchMarbles = "ny--";
withCallViewModel( withCallViewModel(
{ {
remoteParticipants$: behavior(participantInputMarbles, { remoteParticipants$: behavior(participantInputMarbles, {
@@ -579,6 +583,9 @@ describe.each([
}, },
}, },
); );
expectObservable(
vm.layoutSwitchVm$.pipe(map((vm) => vm !== null)),
).toBe(expectedLayoutSwitchMarbles, yesNo);
}, },
); );
}); });

View File

@@ -1108,41 +1108,45 @@ export function createCallViewModel$(
), ),
); );
const oneOnOneLayoutMedia$: Observable<{ const oneOnOneLayoutMedia$: Behavior<{
local: LocalUserMediaViewModel; local: LocalUserMediaViewModel;
remote: UserMediaViewModel | RingingMediaViewModel; remote: UserMediaViewModel | RingingMediaViewModel;
} | null> = combineLatest([userMedia$, screenShares$]).pipe( } | null> = scope.behavior(
switchMap(([userMedia, screenShares]) => { combineLatest([userMedia$, screenShares$]).pipe(
// One-on-one layout only supports 2 user media, no screen shares switchMap(([userMedia, screenShares]) => {
if (userMedia.length <= 2 && screenShares.length === 0) { // One-on-one layout only supports 2 user media, no screen shares
const local = userMedia.find( if (userMedia.length <= 2 && screenShares.length === 0) {
(vm): vm is WrappedUserMediaViewModel & LocalUserMediaViewModel => const local = userMedia.find(
vm.type === "user" && vm.local, (vm): vm is WrappedUserMediaViewModel & LocalUserMediaViewModel =>
); vm.type === "user" && vm.local,
if (local !== undefined) {
const remote = userMedia.find(
(vm): vm is WrappedUserMediaViewModel & RemoteUserMediaViewModel =>
vm.type === "user" && !vm.local,
); );
if (remote !== undefined) return of({ local, remote }); if (local !== undefined) {
const remote = userMedia.find(
// If there's no other user media in the call (could still happen in (
// this branch due to the duplicate tiles option), we could possibly vm,
// show ringing media instead ): vm is WrappedUserMediaViewModel & RemoteUserMediaViewModel =>
if (userMedia.length === 1) vm.type === "user" && !vm.local,
return ringingMedia$.pipe(
map(
(ringingMedia) =>
ringingMedia && { local, remote: ringingMedia },
),
); );
}
}
return of(null); if (remote !== undefined) return of({ local, remote });
}),
// If there's no other user media in the call (could still happen in
// this branch due to the duplicate tiles option), we could possibly
// show ringing media instead
if (userMedia.length === 1)
return ringingMedia$.pipe(
map(
(ringingMedia) =>
ringingMedia && { local, remote: ringingMedia },
),
);
}
}
return of(null);
}),
),
); );
const oneOnOneDesktopLayoutMedia$: Observable<OneOnOneDesktopLayoutMedia | null> = const oneOnOneDesktopLayoutMedia$: Observable<OneOnOneDesktopLayoutMedia | null> =
@@ -1358,6 +1362,22 @@ export function createCallViewModel$(
layoutMedia$.pipe(map(({ edgeToEdge }) => edgeToEdge)), layoutMedia$.pipe(map(({ edgeToEdge }) => edgeToEdge)),
); );
// Only show the layout switch in cases where it has an effect on the layout
const showLayoutSwitch$ = windowMode$.pipe(
switchMap((windowMode) => {
switch (windowMode) {
case "normal":
return of(true);
case "flat":
return oneOnOneLayoutMedia$.pipe(
map((oneOnOne) => oneOnOne === null),
);
default:
return of(false);
}
}),
);
const screenTap$ = new Subject<void>(); const screenTap$ = new Subject<void>();
const controlsTap$ = new Subject<void>(); const controlsTap$ = new Subject<void>();
const screenHover$ = new Subject<void>(); const screenHover$ = new Subject<void>();
@@ -1775,7 +1795,9 @@ export function createCallViewModel$(
spotlightExpanded$: spotlightExpanded$, spotlightExpanded$: spotlightExpanded$,
toggleSpotlightExpanded$: toggleSpotlightExpanded$, toggleSpotlightExpanded$: toggleSpotlightExpanded$,
layoutSwitchVm$: constant(layoutSwitchVm), layoutSwitchVm$: scope.behavior(
showLayoutSwitch$.pipe(map((show) => (show ? layoutSwitchVm : null))),
),
layout$: layout$, layout$: layout$,
localMatrixLivekitMember$, localMatrixLivekitMember$,
remoteMatrixLivekitMembers$: scope.behavior( remoteMatrixLivekitMembers$: scope.behavior(