mirror of
https://github.com/vector-im/element-call.git
synced 2026-02-11 04:27:03 +00:00
Allow user to switch layouts while phone is in landscape
This fixes a regression on the development branch: the layout switcher would not respond to input while the window mode is 'flat' (i.e. while a mobile phone is in landscape orientation). See https://github.com/element-hq/element-call/pull/3605#discussion_r2586226422 for more context. I was having a little trouble interpreting the emergent behavior of the layout switching code, so I refactored it in the process into a form that I think is a more direct description of the behavior we want (while not making it as terse as my original implementation).
This commit is contained in:
@@ -946,11 +946,12 @@ export function createCallViewModel$(
|
||||
),
|
||||
);
|
||||
|
||||
const hasRemoteScreenShares$: Observable<boolean> = spotlight$.pipe(
|
||||
map((spotlight) =>
|
||||
spotlight.some((vm) => !vm.local && vm instanceof ScreenShareViewModel),
|
||||
const hasRemoteScreenShares$ = scope.behavior<boolean>(
|
||||
spotlight$.pipe(
|
||||
map((spotlight) =>
|
||||
spotlight.some((vm) => !vm.local && vm instanceof ScreenShareViewModel),
|
||||
),
|
||||
),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
|
||||
const pipEnabled$ = scope.behavior(setPipEnabled$, false);
|
||||
|
||||
@@ -107,3 +107,26 @@ test("auto-switches to spotlight when in flat window mode", () =>
|
||||
windowMode: " nNf",
|
||||
expectedGridMode: "g-s",
|
||||
}));
|
||||
|
||||
test("allows switching modes manually when in flat window mode", () =>
|
||||
testLayoutSwitch({
|
||||
// Window becomes flat, then user switches to grid and back.
|
||||
// Finally the window returns to a normal shape.
|
||||
windowMode: " nf--n",
|
||||
userSelection: " --gs",
|
||||
expectedGridMode: "gsgsg",
|
||||
}));
|
||||
|
||||
test("stays in spotlight while there are screen shares even when window mode changes", () =>
|
||||
testLayoutSwitch({
|
||||
windowMode: " nfn",
|
||||
hasScreenShares: " y",
|
||||
expectedGridMode: "s",
|
||||
}));
|
||||
|
||||
test("ignores end of screen share until window mode returns to normal", () =>
|
||||
testLayoutSwitch({
|
||||
windowMode: " nf-n",
|
||||
hasScreenShares: " y-n",
|
||||
expectedGridMode: "s--g",
|
||||
}));
|
||||
|
||||
@@ -6,122 +6,85 @@ Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
map,
|
||||
type Observable,
|
||||
scan,
|
||||
Subject,
|
||||
startWith,
|
||||
skipWhile,
|
||||
switchMap,
|
||||
} 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 { constant, 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.
|
||||
* The actual layout mode might switch automatically to spotlight 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.
|
||||
* @param windowMode$ - The current window mode.
|
||||
* @param hasRemoteScreenShares$ - A behavior indicating if there are remote screen shares active.
|
||||
*/
|
||||
export function createLayoutModeSwitch(
|
||||
scope: ObservableScope,
|
||||
windowMode$: Behavior<WindowMode>,
|
||||
hasRemoteScreenShares$: Observable<boolean>,
|
||||
hasRemoteScreenShares$: Behavior<boolean>,
|
||||
): {
|
||||
gridMode$: Behavior<GridMode>;
|
||||
setGridMode: (value: GridMode) => void;
|
||||
} {
|
||||
const gridModeUserSelection$ = new BehaviorSubject<GridMode>("grid");
|
||||
|
||||
const userSelection$ = new Subject<GridMode>();
|
||||
// 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);
|
||||
};
|
||||
const setGridMode = (value: GridMode): void => userSelection$.next(value);
|
||||
|
||||
/**
|
||||
* The natural grid mode - the mode that the grid would prefer to be in,
|
||||
* not accounting for the user's manual selections.
|
||||
*/
|
||||
const naturalGridMode$ = scope.behavior<GridMode>(
|
||||
combineLatest(
|
||||
[hasRemoteScreenShares$, windowMode$],
|
||||
(hasRemoteScreenShares, windowMode) =>
|
||||
// When there are screen shares or the window is flat (as with a phone
|
||||
// in landscape orientation), spotlight is a better experience.
|
||||
// We want screen shares to be big and readable, and we want flipping
|
||||
// your phone into landscape to be a quick way of maximising the
|
||||
// spotlight tile.
|
||||
hasRemoteScreenShares || windowMode === "flat" ? "spotlight" : "grid",
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* 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<GridMode>(
|
||||
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;
|
||||
/** Remember if the change was user driven or not */
|
||||
hasAutoSwitched: boolean;
|
||||
/** To know if it is new screen share or an already handled */
|
||||
hasScreenShares: boolean;
|
||||
}
|
||||
>(
|
||||
(prev, [userSelection, hasScreenShares, windowMode]) => {
|
||||
const isFlatMode = windowMode === "flat";
|
||||
const gridMode$ = scope.behavior<GridMode>(
|
||||
// Whenever the user makes a selection, we enter a new mode of behavior:
|
||||
userSelection$.pipe(
|
||||
map((selection) => {
|
||||
if (selection === "grid")
|
||||
// The user has selected grid mode. Start by respecting their choice,
|
||||
// but then follow the natural mode again as soon as it matches.
|
||||
return naturalGridMode$.pipe(
|
||||
skipWhile((naturalMode) => naturalMode !== selection),
|
||||
startWith(selection),
|
||||
);
|
||||
|
||||
// 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: prev.hasAutoSwitched,
|
||||
hasScreenShares,
|
||||
};
|
||||
}
|
||||
|
||||
// User explicitly chose spotlight.
|
||||
// Respect that choice.
|
||||
if (userSelection === "spotlight") {
|
||||
return {
|
||||
mode: "spotlight",
|
||||
hasAutoSwitched: prev.hasAutoSwitched,
|
||||
hasScreenShares,
|
||||
};
|
||||
}
|
||||
|
||||
// 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.
|
||||
const isNewShare = hasScreenShares && !prev.hasScreenShares;
|
||||
if (isNewShare && !prev.hasAutoSwitched) {
|
||||
return {
|
||||
mode: "spotlight",
|
||||
hasAutoSwitched: true,
|
||||
hasScreenShares: 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,
|
||||
hasScreenShares,
|
||||
};
|
||||
},
|
||||
// initial value
|
||||
{ mode: "grid", hasAutoSwitched: false, hasScreenShares: false },
|
||||
),
|
||||
map(({ mode }) => mode),
|
||||
),
|
||||
"grid",
|
||||
);
|
||||
// The user has selected spotlight mode. If this matches the natural
|
||||
// mode, then follow the natural mode going forward.
|
||||
return selection === naturalGridMode$.value
|
||||
? naturalGridMode$
|
||||
: constant(selection);
|
||||
}),
|
||||
// Initially the mode of behavior is to just follow the natural grid mode.
|
||||
startWith(naturalGridMode$),
|
||||
// Switch between each mode of behavior.
|
||||
switchMap((mode$) => mode$),
|
||||
),
|
||||
);
|
||||
|
||||
return {
|
||||
gridMode$,
|
||||
|
||||
Reference in New Issue
Block a user