From 60bc6f1e933fd837ba4173174cf3046185d725b9 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 2 Dec 2025 11:00:32 +0100 Subject: [PATCH 1/6] refactor: Extract layout mode switch + test --- src/state/CallViewModel/CallViewModel.ts | 51 +------ src/state/CallViewModel/Layout.switch.test.ts | 134 ++++++++++++++++++ src/state/CallViewModel/LayoutSwitch.ts | 89 ++++++++++++ 3 files changed, 230 insertions(+), 44 deletions(-) create mode 100644 src/state/CallViewModel/Layout.switch.test.ts create mode 100644 src/state/CallViewModel/LayoutSwitch.ts diff --git a/src/state/CallViewModel/CallViewModel.ts b/src/state/CallViewModel/CallViewModel.ts index 3c15958a..8fb1084c 100644 --- a/src/state/CallViewModel/CallViewModel.ts +++ b/src/state/CallViewModel/CallViewModel.ts @@ -15,7 +15,6 @@ import { } from "livekit-client"; import { type Room as MatrixRoom } from "matrix-js-sdk"; import { - BehaviorSubject, combineLatest, distinctUntilChanged, filter, @@ -126,6 +125,7 @@ import { } from "./remoteMembers/MatrixMemberMetadata.ts"; import { Publisher } from "./localMember/Publisher.ts"; import { type Connection } from "./remoteMembers/Connection.ts"; +import { createLayoutModeSwitch } from "./LayoutSwitch.ts"; const logger = rootLogger.getChild("[CallViewModel]"); //TODO @@ -343,6 +343,7 @@ export interface CallViewModel { // DISCUSSION own membership manager ALSO this probably can be simplifis reconnecting$: Behavior; } + /** * A view model providing all the application logic needed to show the in-call * UI (may eventually be expanded to cover the lobby and feedback screens in the @@ -980,49 +981,11 @@ export function createCallViewModel$( spotlightExpandedToggle$.pipe(accumulate(false, (expanded) => !expanded)), ); - const gridModeUserSelection$ = new BehaviorSubject("grid"); - - // Callback to set the grid mode desired by the user. - // Notice that this is only a preference, the actual grid mode can be overridden - // if there is a remote screen share active. - const setGridMode = (value: GridMode): void => { - gridModeUserSelection$.next(value); - }; - /** - * The layout mode of the media tile grid. - */ - const gridMode$ = - // If the user hasn't selected spotlight and somebody starts screen sharing, - // automatically switch to spotlight mode and reset when screen sharing ends - scope.behavior( - gridModeUserSelection$.pipe( - switchMap((userSelection): Observable => { - if (userSelection === "spotlight") { - // If already in spotlight mode, stay there - return of("spotlight"); - } else { - // Otherwise, check if there is a remote screen share active - // as this could force us into spotlight mode. - return combineLatest([hasRemoteScreenShares$, windowMode$]).pipe( - map(([hasScreenShares, windowMode]): GridMode => { - const isFlatMode = windowMode === "flat"; - if (hasScreenShares || isFlatMode) { - logger.debug( - `Forcing spotlight mode, hasScreenShares=${hasScreenShares} windowMode=${windowMode}`, - ); - // override to spotlight mode - return "spotlight"; - } else { - // respect user choice - return "grid"; - } - }), - ); - } - }), - ), - "grid", - ); + const { setGridMode, gridMode$ } = createLayoutModeSwitch( + scope, + windowMode$, + hasRemoteScreenShares$, + ); const gridLayoutMedia$: Observable = combineLatest( [grid$, spotlight$], diff --git a/src/state/CallViewModel/Layout.switch.test.ts b/src/state/CallViewModel/Layout.switch.test.ts new file mode 100644 index 00000000..f4d36b9e --- /dev/null +++ b/src/state/CallViewModel/Layout.switch.test.ts @@ -0,0 +1,134 @@ +/* +Copyright 2025 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +import { afterEach, beforeEach, describe, expect, test } from "vitest"; +import { firstValueFrom, of } from "rxjs"; + +import { createLayoutModeSwitch } from "./LayoutSwitch"; +import { ObservableScope } from "../ObservableScope"; +import { constant } from "../Behavior"; +import { withTestScheduler } from "../../utils/test"; + +let scope: ObservableScope; +beforeEach(() => { + scope = new ObservableScope(); +}); +afterEach(() => { + scope.end(); +}); + +describe("Default mode", () => { + test("Should be in grid layout by default", async () => { + const { gridMode$ } = createLayoutModeSwitch( + scope, + constant("normal"), + of(false), + ); + + const mode = await firstValueFrom(gridMode$); + expect(mode).toBe("grid"); + }); + + test("Should switch to spotlight mode when window mode is flat", async () => { + const { gridMode$ } = createLayoutModeSwitch( + scope, + constant("flat"), + of(false), + ); + + const mode = await firstValueFrom(gridMode$); + expect(mode).toBe("spotlight"); + }); +}); + +test("Should allow switching modes manually", () => { + withTestScheduler(({ cold, behavior, expectObservable, schedule }): void => { + const { gridMode$, setGridMode } = createLayoutModeSwitch( + scope, + behavior("n", { n: "normal" }), + cold("f", { f: false, t: true }), + ); + + schedule("--sgs", { + s: () => setGridMode("spotlight"), + g: () => setGridMode("grid"), + }); + + expectObservable(gridMode$).toBe("g-sgs", { + g: "grid", + s: "spotlight", + }); + }); +}); + +test("Should switch to spotlight mode when there is a remote screen share", () => { + withTestScheduler(({ cold, behavior, expectObservable }): void => { + const shareMarble = "f--t"; + const gridsMarble = "g--s"; + const { gridMode$ } = createLayoutModeSwitch( + scope, + behavior("n", { n: "normal" }), + cold(shareMarble, { f: false, t: true }), + ); + + expectObservable(gridMode$).toBe(gridsMarble, { + g: "grid", + s: "spotlight", + }); + }); +}); + +test.fails("Can manually force grid when there is a screenshare", () => { + withTestScheduler(({ cold, behavior, expectObservable, schedule }): void => { + const { gridMode$, setGridMode } = createLayoutModeSwitch( + scope, + behavior("n", { n: "normal" }), + cold("-ft", { f: false, t: true }), + ); + + schedule("---g", { + g: () => setGridMode("grid"), + }); + + expectObservable(gridMode$).toBe("ggsg", { + g: "grid", + s: "spotlight", + }); + }); +}); + +test("Should switch back to grid mode when the remote screen share ends", () => { + withTestScheduler(({ cold, behavior, expectObservable }): void => { + const shareMarble = "f--t--f-"; + const gridsMarble = "g--s--g-"; + const { gridMode$ } = createLayoutModeSwitch( + scope, + behavior("n", { n: "normal" }), + cold(shareMarble, { f: false, t: true }), + ); + + expectObservable(gridMode$).toBe(gridsMarble, { + g: "grid", + s: "spotlight", + }); + }); +}); + +test("Should auto-switch to spotlight when in flat window mode", () => { + withTestScheduler(({ cold, behavior, expectObservable }): void => { + const { gridMode$ } = createLayoutModeSwitch( + scope, + behavior("naf", { n: "normal", a: "narrow", f: "flat" }), + cold("f", { f: false, t: true }), + ); + + expectObservable(gridMode$).toBe("g-s-", { + g: "grid", + s: "spotlight", + }); + }); +}); diff --git a/src/state/CallViewModel/LayoutSwitch.ts b/src/state/CallViewModel/LayoutSwitch.ts new file mode 100644 index 00000000..b406392d --- /dev/null +++ b/src/state/CallViewModel/LayoutSwitch.ts @@ -0,0 +1,89 @@ +/* +Copyright 2025 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +import { + BehaviorSubject, + combineLatest, + map, + switchMap, + type Observable, + of, +} from "rxjs"; +import { logger } from "matrix-js-sdk/lib/logger"; + +import { type GridMode, type WindowMode } from "./CallViewModel.ts"; +import { type Behavior } from "../Behavior.ts"; +import { type ObservableScope } from "../ObservableScope.ts"; + +/** + * Creates a layout mode switch that allows switching between grid and spotlight modes. + * The actual layout mode can be overridden to spotlight mode if there is a remote screen share active + * or if the window mode is flat. + * + * @param scope - The observable scope to manage subscriptions. + * @param windowMode$ - The current window mode observable. + * @param hasRemoteScreenShares$ - An observable indicating if there are remote screen shares active. + */ +export function createLayoutModeSwitch( + scope: ObservableScope, + windowMode$: Behavior, + hasRemoteScreenShares$: Observable, +): { + gridMode$: Behavior; + setGridMode: (value: GridMode) => void; +} { + const gridModeUserSelection$ = new BehaviorSubject("grid"); + + // Callback to set the grid mode desired by the user. + // Notice that this is only a preference, the actual grid mode can be overridden + // if there is a remote screen share active. + const setGridMode = (value: GridMode): void => { + gridModeUserSelection$.next(value); + }; + /** + * The layout mode of the media tile grid. + */ + const gridMode$ = + // If the user hasn't selected spotlight and somebody starts screen sharing, + // automatically switch to spotlight mode and reset when screen sharing ends + scope.behavior( + gridModeUserSelection$.pipe( + switchMap((userSelection): Observable => { + if (userSelection === "spotlight") { + // If already in spotlight mode, stay there + return of("spotlight"); + } else { + // Otherwise, check if there is a remote screen share active + // as this could force us into spotlight mode. + return combineLatest([hasRemoteScreenShares$, windowMode$]).pipe( + map(([hasScreenShares, windowMode]): GridMode => { + // TODO: strange that we do that for flat mode but not for other modes? + // TODO: Why is this not handled in layoutMedia$ like other window modes? + const isFlatMode = windowMode === "flat"; + if (hasScreenShares || isFlatMode) { + logger.debug( + `Forcing spotlight mode, hasScreenShares=${hasScreenShares} windowMode=${windowMode}`, + ); + // override to spotlight mode + return "spotlight"; + } else { + // respect user choice + return "grid"; + } + }), + ); + } + }), + ), + "grid", + ); + + return { + gridMode$, + setGridMode, + }; +} From b5d3f3c72a2d31d2bded511bdcffb0adb64901bc Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 2 Dec 2025 11:18:50 +0100 Subject: [PATCH 2/6] fix: Allow switching back to grid when there is a screenshare --- src/state/CallViewModel/Layout.switch.test.ts | 25 +++++- src/state/CallViewModel/LayoutSwitch.ts | 85 ++++++++++++------- 2 files changed, 80 insertions(+), 30 deletions(-) diff --git a/src/state/CallViewModel/Layout.switch.test.ts b/src/state/CallViewModel/Layout.switch.test.ts index f4d36b9e..57df5563 100644 --- a/src/state/CallViewModel/Layout.switch.test.ts +++ b/src/state/CallViewModel/Layout.switch.test.ts @@ -82,7 +82,7 @@ test("Should switch to spotlight mode when there is a remote screen share", () = }); }); -test.fails("Can manually force grid when there is a screenshare", () => { +test("Can manually force grid when there is a screenshare", () => { withTestScheduler(({ cold, behavior, expectObservable, schedule }): void => { const { gridMode$, setGridMode } = createLayoutModeSwitch( scope, @@ -101,6 +101,29 @@ test.fails("Can manually force grid when there is a screenshare", () => { }); }); +test("Should not auto-switch after manually selected grid", () => { + withTestScheduler(({ cold, behavior, expectObservable, schedule }): void => { + const { gridMode$, setGridMode } = createLayoutModeSwitch( + scope, + behavior("n", { n: "normal" }), + cold("-ft-ft", { f: false, t: true }), + ); + + schedule("---g", { + g: () => setGridMode("grid"), + }); + + const expectation = "ggsg"; + // If we did not respect manual selection, the expectation would be: + // const expectation = "ggsg-s"; + + expectObservable(gridMode$).toBe(expectation, { + g: "grid", + s: "spotlight", + }); + }); +}); + test("Should switch back to grid mode when the remote screen share ends", () => { withTestScheduler(({ cold, behavior, expectObservable }): void => { const shareMarble = "f--t--f-"; diff --git a/src/state/CallViewModel/LayoutSwitch.ts b/src/state/CallViewModel/LayoutSwitch.ts index b406392d..c7a1e631 100644 --- a/src/state/CallViewModel/LayoutSwitch.ts +++ b/src/state/CallViewModel/LayoutSwitch.ts @@ -9,9 +9,8 @@ import { BehaviorSubject, combineLatest, map, - switchMap, type Observable, - of, + scan, } from "rxjs"; import { logger } from "matrix-js-sdk/lib/logger"; @@ -51,33 +50,61 @@ export function createLayoutModeSwitch( // If the user hasn't selected spotlight and somebody starts screen sharing, // automatically switch to spotlight mode and reset when screen sharing ends scope.behavior( - gridModeUserSelection$.pipe( - switchMap((userSelection): Observable => { - if (userSelection === "spotlight") { - // If already in spotlight mode, stay there - return of("spotlight"); - } else { - // Otherwise, check if there is a remote screen share active - // as this could force us into spotlight mode. - return combineLatest([hasRemoteScreenShares$, windowMode$]).pipe( - map(([hasScreenShares, windowMode]): GridMode => { - // TODO: strange that we do that for flat mode but not for other modes? - // TODO: Why is this not handled in layoutMedia$ like other window modes? - const isFlatMode = windowMode === "flat"; - if (hasScreenShares || isFlatMode) { - logger.debug( - `Forcing spotlight mode, hasScreenShares=${hasScreenShares} windowMode=${windowMode}`, - ); - // override to spotlight mode - return "spotlight"; - } else { - // respect user choice - return "grid"; - } - }), - ); - } - }), + combineLatest([ + gridModeUserSelection$, + hasRemoteScreenShares$, + windowMode$, + ]).pipe( + // Scan to keep track if we have auto-switched already or not. + // To allow the user to override the auto-switch by selecting grid mode again. + scan< + [GridMode, boolean, WindowMode], + { mode: GridMode; hasAutoSwitched: boolean } + >( + (acc, [userSelection, hasScreenShares, windowMode]) => { + const isFlatMode = windowMode === "flat"; + + // Always force spotlight in flat mode, grid layout is not supported + // in that mode. + // TODO: strange that we do that for flat mode but not for other modes? + // TODO: Why is this not handled in layoutMedia$ like other window modes? + if (isFlatMode) { + logger.debug(`Forcing spotlight mode, windowMode=${windowMode}`); + return { + mode: "spotlight", + hasAutoSwitched: acc.hasAutoSwitched, + }; + } + + // User explicitly chose spotlight. + // Respect that choice. + if (userSelection === "spotlight") { + return { + mode: "spotlight", + hasAutoSwitched: acc.hasAutoSwitched, + }; + } + + // User has chosen grid mode. If a screen share starts, we will + // auto-switch to spotlight mode for better experience. + // But we only do it once, if the user switches back to grid mode, + // we respect that choice until they explicitly change it again. + if (hasScreenShares && !acc.hasAutoSwitched) { + logger.debug( + `Auto-switching to spotlight mode, hasScreenShares=${hasScreenShares}`, + ); + return { mode: "spotlight", hasAutoSwitched: true }; + } + + // Respect user's grid choice + // XXX If we want to allow switching automatically again after we can + // return hasAutoSwitched: false here instead of keeping the previous value. + return { mode: "grid", hasAutoSwitched: acc.hasAutoSwitched }; + }, + // initial value + { mode: "grid", hasAutoSwitched: false }, + ), + map(({ mode }) => mode), ), "grid", ); From b85f36598c7c404f782f1a5dc091f3759cda1f57 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 3 Dec 2025 08:54:52 +0100 Subject: [PATCH 3/6] fix: mistake in file name --- .../CallViewModel/{Layout.switch.test.ts => LayoutSwitch.test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/state/CallViewModel/{Layout.switch.test.ts => LayoutSwitch.test.ts} (100%) diff --git a/src/state/CallViewModel/Layout.switch.test.ts b/src/state/CallViewModel/LayoutSwitch.test.ts similarity index 100% rename from src/state/CallViewModel/Layout.switch.test.ts rename to src/state/CallViewModel/LayoutSwitch.test.ts From a93ceeae4ba2402c7fc8ccc3d2c774ab66006210 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 3 Dec 2025 09:01:26 +0100 Subject: [PATCH 4/6] review: Keep previous behavior for now to always auto switch --- src/state/CallViewModel/LayoutSwitch.test.ts | 11 +++++++---- src/state/CallViewModel/LayoutSwitch.ts | 6 +++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/state/CallViewModel/LayoutSwitch.test.ts b/src/state/CallViewModel/LayoutSwitch.test.ts index 57df5563..d0034743 100644 --- a/src/state/CallViewModel/LayoutSwitch.test.ts +++ b/src/state/CallViewModel/LayoutSwitch.test.ts @@ -101,21 +101,24 @@ test("Can manually force grid when there is a screenshare", () => { }); }); -test("Should not auto-switch after manually selected grid", () => { +test("Should auto-switch after manually selected grid", () => { withTestScheduler(({ cold, behavior, expectObservable, schedule }): void => { const { gridMode$, setGridMode } = createLayoutModeSwitch( scope, behavior("n", { n: "normal" }), + // Two screenshares will happen in sequence cold("-ft-ft", { f: false, t: true }), ); + // There was a screen-share that forced spotlight, then + // the user manually switch back to grid schedule("---g", { g: () => setGridMode("grid"), }); - const expectation = "ggsg"; - // If we did not respect manual selection, the expectation would be: - // const expectation = "ggsg-s"; + // If we did want to respect manual selection, the expectation would be: + // const expectation = "ggsg"; + const expectation = "ggsg-s"; expectObservable(gridMode$).toBe(expectation, { g: "grid", diff --git a/src/state/CallViewModel/LayoutSwitch.ts b/src/state/CallViewModel/LayoutSwitch.ts index c7a1e631..cfb31d53 100644 --- a/src/state/CallViewModel/LayoutSwitch.ts +++ b/src/state/CallViewModel/LayoutSwitch.ts @@ -97,9 +97,9 @@ export function createLayoutModeSwitch( } // Respect user's grid choice - // XXX If we want to allow switching automatically again after we can - // return hasAutoSwitched: false here instead of keeping the previous value. - return { mode: "grid", hasAutoSwitched: acc.hasAutoSwitched }; + // XXX If we want to forbid switching automatically again after we can + // return hasAutoSwitched: acc.hasAutoSwitched here instead of setting to false. + return { mode: "grid", hasAutoSwitched: false }; }, // initial value { mode: "grid", hasAutoSwitched: false }, From fdc66a1d62bce8e38b0d89192eba0b70056e2f3c Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 3 Dec 2025 18:36:51 +0100 Subject: [PATCH 5/6] fix: existing screenshare switching twice --- src/state/CallViewModel/LayoutSwitch.test.ts | 25 ++++++++++++++++ src/state/CallViewModel/LayoutSwitch.ts | 30 ++++++++++++++------ 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/src/state/CallViewModel/LayoutSwitch.test.ts b/src/state/CallViewModel/LayoutSwitch.test.ts index d0034743..c1941eb8 100644 --- a/src/state/CallViewModel/LayoutSwitch.test.ts +++ b/src/state/CallViewModel/LayoutSwitch.test.ts @@ -144,6 +144,31 @@ test("Should switch back to grid mode when the remote screen share ends", () => }); }); +test("can switch manually to grid after screen share while manually in spotlight", () => { + withTestScheduler(({ cold, behavior, schedule, expectObservable }): void => { + // Initially, no one is sharing. Then the user manually switches to + // spotlight. After a screen share starts, the user manually switches to + // grid. + const shareMarbles = " f-t-"; + const setModeMarbles = "-s-g"; + const expectation = " gs-g"; + const { gridMode$, setGridMode } = createLayoutModeSwitch( + scope, + behavior("n", { n: "normal" }), + cold(shareMarbles, { f: false, t: true }), + ); + schedule(setModeMarbles, { + g: () => setGridMode("grid"), + s: () => setGridMode("spotlight"), + }); + + expectObservable(gridMode$).toBe(expectation, { + g: "grid", + s: "spotlight", + }); + }); +}); + test("Should auto-switch to spotlight when in flat window mode", () => { withTestScheduler(({ cold, behavior, expectObservable }): void => { const { gridMode$ } = createLayoutModeSwitch( diff --git a/src/state/CallViewModel/LayoutSwitch.ts b/src/state/CallViewModel/LayoutSwitch.ts index cfb31d53..65c6bcb1 100644 --- a/src/state/CallViewModel/LayoutSwitch.ts +++ b/src/state/CallViewModel/LayoutSwitch.ts @@ -59,7 +59,13 @@ export function createLayoutModeSwitch( // To allow the user to override the auto-switch by selecting grid mode again. scan< [GridMode, boolean, WindowMode], - { mode: GridMode; hasAutoSwitched: boolean } + { + mode: GridMode; + /** Remember if the change was user driven or not */ + hasAutoSwitched: boolean; + /** To know if it is new screen share or an already handled */ + prevShare: boolean; + } >( (acc, [userSelection, hasScreenShares, windowMode]) => { const isFlatMode = windowMode === "flat"; @@ -73,6 +79,7 @@ export function createLayoutModeSwitch( return { mode: "spotlight", hasAutoSwitched: acc.hasAutoSwitched, + prevShare: hasScreenShares, }; } @@ -82,6 +89,7 @@ export function createLayoutModeSwitch( return { mode: "spotlight", hasAutoSwitched: acc.hasAutoSwitched, + prevShare: hasScreenShares, }; } @@ -89,20 +97,26 @@ export function createLayoutModeSwitch( // auto-switch to spotlight mode for better experience. // But we only do it once, if the user switches back to grid mode, // we respect that choice until they explicitly change it again. - if (hasScreenShares && !acc.hasAutoSwitched) { - logger.debug( - `Auto-switching to spotlight mode, hasScreenShares=${hasScreenShares}`, - ); - return { mode: "spotlight", hasAutoSwitched: true }; + const isNewShare = hasScreenShares && !acc.prevShare; + if (isNewShare && !acc.hasAutoSwitched) { + return { + mode: "spotlight", + hasAutoSwitched: true, + prevShare: true, + }; } // Respect user's grid choice // XXX If we want to forbid switching automatically again after we can // return hasAutoSwitched: acc.hasAutoSwitched here instead of setting to false. - return { mode: "grid", hasAutoSwitched: false }; + return { + mode: "grid", + hasAutoSwitched: false, + prevShare: hasScreenShares, + }; }, // initial value - { mode: "grid", hasAutoSwitched: false }, + { mode: "grid", hasAutoSwitched: false, prevShare: false }, ), map(({ mode }) => mode), ), From be7407ea3d7fc060538f8c3e52b2aa2bb4ea25bf Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 4 Dec 2025 09:37:07 +0100 Subject: [PATCH 6/6] review: quick renaming --- src/state/CallViewModel/LayoutSwitch.test.ts | 17 +++++++++++++++ src/state/CallViewModel/LayoutSwitch.ts | 22 ++++++++++---------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/state/CallViewModel/LayoutSwitch.test.ts b/src/state/CallViewModel/LayoutSwitch.test.ts index c1941eb8..ae5a3896 100644 --- a/src/state/CallViewModel/LayoutSwitch.test.ts +++ b/src/state/CallViewModel/LayoutSwitch.test.ts @@ -144,6 +144,23 @@ test("Should switch back to grid mode when the remote screen share ends", () => }); }); +test("can auto-switch to spotlight again after first screen share ends", () => { + withTestScheduler(({ cold, behavior, expectObservable }): void => { + const shareMarble = "ftft"; + const gridsMarble = "gsgs"; + const { gridMode$ } = createLayoutModeSwitch( + scope, + behavior("n", { n: "normal" }), + cold(shareMarble, { f: false, t: true }), + ); + + expectObservable(gridMode$).toBe(gridsMarble, { + g: "grid", + s: "spotlight", + }); + }); +}); + test("can switch manually to grid after screen share while manually in spotlight", () => { withTestScheduler(({ cold, behavior, schedule, expectObservable }): void => { // Initially, no one is sharing. Then the user manually switches to diff --git a/src/state/CallViewModel/LayoutSwitch.ts b/src/state/CallViewModel/LayoutSwitch.ts index 65c6bcb1..3ad93204 100644 --- a/src/state/CallViewModel/LayoutSwitch.ts +++ b/src/state/CallViewModel/LayoutSwitch.ts @@ -64,10 +64,10 @@ export function createLayoutModeSwitch( /** Remember if the change was user driven or not */ hasAutoSwitched: boolean; /** To know if it is new screen share or an already handled */ - prevShare: boolean; + hasScreenShares: boolean; } >( - (acc, [userSelection, hasScreenShares, windowMode]) => { + (prev, [userSelection, hasScreenShares, windowMode]) => { const isFlatMode = windowMode === "flat"; // Always force spotlight in flat mode, grid layout is not supported @@ -78,8 +78,8 @@ export function createLayoutModeSwitch( logger.debug(`Forcing spotlight mode, windowMode=${windowMode}`); return { mode: "spotlight", - hasAutoSwitched: acc.hasAutoSwitched, - prevShare: hasScreenShares, + hasAutoSwitched: prev.hasAutoSwitched, + hasScreenShares, }; } @@ -88,8 +88,8 @@ export function createLayoutModeSwitch( if (userSelection === "spotlight") { return { mode: "spotlight", - hasAutoSwitched: acc.hasAutoSwitched, - prevShare: hasScreenShares, + hasAutoSwitched: prev.hasAutoSwitched, + hasScreenShares, }; } @@ -97,12 +97,12 @@ export function createLayoutModeSwitch( // auto-switch to spotlight mode for better experience. // But we only do it once, if the user switches back to grid mode, // we respect that choice until they explicitly change it again. - const isNewShare = hasScreenShares && !acc.prevShare; - if (isNewShare && !acc.hasAutoSwitched) { + const isNewShare = hasScreenShares && !prev.hasScreenShares; + if (isNewShare && !prev.hasAutoSwitched) { return { mode: "spotlight", hasAutoSwitched: true, - prevShare: true, + hasScreenShares: true, }; } @@ -112,11 +112,11 @@ export function createLayoutModeSwitch( return { mode: "grid", hasAutoSwitched: false, - prevShare: hasScreenShares, + hasScreenShares, }; }, // initial value - { mode: "grid", hasAutoSwitched: false, prevShare: false }, + { mode: "grid", hasAutoSwitched: false, hasScreenShares: false }, ), map(({ mode }) => mode), ),