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 }) => {
// Starts as a one-on-one call, then Alice shares her screen, then Bob
// joins, and finally Alice stops sharing her screen
const participantInputMarbles = " a--b";
const aliceSharingInputMarbles = "ny-n";
const participantInputMarbles = " a--b";
const aliceSharingInputMarbles = " ny-n";
// Starts in one-on-one mobile layout, then goes to spotlight layout for
// 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(
{
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;
remote: UserMediaViewModel | RingingMediaViewModel;
} | null> = combineLatest([userMedia$, screenShares$]).pipe(
switchMap(([userMedia, screenShares]) => {
// One-on-one layout only supports 2 user media, no screen shares
if (userMedia.length <= 2 && screenShares.length === 0) {
const local = userMedia.find(
(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,
} | null> = scope.behavior(
combineLatest([userMedia$, screenShares$]).pipe(
switchMap(([userMedia, screenShares]) => {
// One-on-one layout only supports 2 user media, no screen shares
if (userMedia.length <= 2 && screenShares.length === 0) {
const local = userMedia.find(
(vm): vm is WrappedUserMediaViewModel & LocalUserMediaViewModel =>
vm.type === "user" && vm.local,
);
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 },
),
if (local !== undefined) {
const remote = userMedia.find(
(
vm,
): vm is WrappedUserMediaViewModel & RemoteUserMediaViewModel =>
vm.type === "user" && !vm.local,
);
}
}
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> =
@@ -1358,6 +1362,22 @@ export function createCallViewModel$(
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 controlsTap$ = new Subject<void>();
const screenHover$ = new Subject<void>();
@@ -1775,7 +1795,9 @@ export function createCallViewModel$(
spotlightExpanded$: spotlightExpanded$,
toggleSpotlightExpanded$: toggleSpotlightExpanded$,
layoutSwitchVm$: constant(layoutSwitchVm),
layoutSwitchVm$: scope.behavior(
showLayoutSwitch$.pipe(map((show) => (show ? layoutSwitchVm : null))),
),
layout$: layout$,
localMatrixLivekitMember$,
remoteMatrixLivekitMembers$: scope.behavior(