Skip to content

Commit e5a0dac

Browse files
bojidar-bgDileep Bandla
authored andcommitted
Remember whether sidebar is shown for calls when switching rooms (element-hq#30262)
* Remember whether sidebar is shown for calls when switching rooms Stores the sidebar state per-room in LegacyCallHandler, along with other details about calls. * Hide the Show/Hide Sidebar from the Picture-in-Picture preview The toggle sidebar button currently does nothing in PIP mode, since PIP mode never shows a sidebar (even when the call is made fullscreen from the PIP preview) * Add test for Show/Hide Sidebar feature * Add more tests for LegacyCallView and LegacyCallViewForRoom Also, fix issue where LegacyCallViewForRoom used roomId and not callId for checking for sidebar state
1 parent 04c7753 commit e5a0dac

File tree

7 files changed

+228
-16
lines changed

7 files changed

+228
-16
lines changed

src/LegacyCallHandler.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ export enum LegacyCallHandlerEvent {
112112
CallsChanged = "calls_changed",
113113
CallChangeRoom = "call_change_room",
114114
SilencedCallsChanged = "silenced_calls_changed",
115+
ShownSidebarsChanged = "shown_sidebars_changed",
115116
CallState = "call_state",
116117
ProtocolSupport = "protocol_support",
117118
}
@@ -120,6 +121,7 @@ type EventEmitterMap = {
120121
[LegacyCallHandlerEvent.CallsChanged]: (calls: Map<string, MatrixCall>) => void;
121122
[LegacyCallHandlerEvent.CallChangeRoom]: (call: MatrixCall) => void;
122123
[LegacyCallHandlerEvent.SilencedCallsChanged]: (calls: Set<string>) => void;
124+
[LegacyCallHandlerEvent.ShownSidebarsChanged]: (sidebarsShown: Map<string, boolean>) => void;
123125
[LegacyCallHandlerEvent.CallState]: (mappedRoomId: string | null, status: CallState) => void;
124126
[LegacyCallHandlerEvent.ProtocolSupport]: () => void;
125127
};
@@ -144,6 +146,8 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
144146

145147
private silencedCalls = new Set<string>(); // callIds
146148

149+
private shownSidebars = new Map<string, boolean>(); // callId (call) -> sidebar show
150+
147151
private backgroundAudio = new BackgroundAudio();
148152
private playingSources: Record<string, AudioBufferSourceNode> = {}; // Record them for stopping
149153

@@ -240,6 +244,15 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
240244
return false;
241245
}
242246

247+
public setCallSidebarShown(callId: string, sidebarShown: boolean): void {
248+
this.shownSidebars.set(callId, sidebarShown);
249+
this.emit(LegacyCallHandlerEvent.ShownSidebarsChanged, this.shownSidebars);
250+
}
251+
252+
public isCallSidebarShown(callId?: string): boolean {
253+
return !!callId && (this.shownSidebars.get(callId) ?? true);
254+
}
255+
243256
private async checkProtocols(maxTries: number): Promise<void> {
244257
try {
245258
const protocols = await MatrixClientPeg.safeGet().getThirdpartyProtocols();

src/components/structures/PipContainer.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ class PipContainerInner extends React.Component<IProps, IState> {
245245
secondaryCall={this.state.secondaryCall}
246246
pipMode={pipMode}
247247
onResize={onResize}
248+
sidebarShown={false}
248249
/>
249250
));
250251
}

src/components/views/voip/LegacyCallView.tsx

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ interface IProps {
5050
onMouseDownOnHeader?: (event: React.MouseEvent<Element, MouseEvent>) => void;
5151

5252
showApps?: boolean;
53+
54+
sidebarShown: boolean;
55+
56+
setSidebarShown?: (sidebarShown: boolean) => void;
5357
}
5458

5559
interface IState {
@@ -62,7 +66,6 @@ interface IState {
6266
primaryFeed?: CallFeed;
6367
secondaryFeed?: CallFeed;
6468
sidebarFeeds: Array<CallFeed>;
65-
sidebarShown: boolean;
6669
}
6770

6871
function getFullScreenElement(): Element | null {
@@ -97,7 +100,6 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
97100
primaryFeed: primary,
98101
secondaryFeed: secondary,
99102
sidebarFeeds: sidebar,
100-
sidebarShown: true,
101103
};
102104
}
103105

@@ -269,8 +271,9 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
269271
isScreensharing = await this.props.call.setScreensharingEnabled(true);
270272
}
271273

274+
this.props.setSidebarShown?.(true);
275+
272276
this.setState({
273-
sidebarShown: true,
274277
screensharing: isScreensharing,
275278
});
276279
};
@@ -320,12 +323,12 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
320323
};
321324

322325
private onToggleSidebar = (): void => {
323-
this.setState({ sidebarShown: !this.state.sidebarShown });
326+
this.props.setSidebarShown?.(!this.props.sidebarShown);
324327
};
325328

326329
private renderCallControls(): JSX.Element {
327-
const { call, pipMode } = this.props;
328-
const { callState, micMuted, vidMuted, screensharing, sidebarShown, secondaryFeed, sidebarFeeds } = this.state;
330+
const { call, pipMode, sidebarShown } = this.props;
331+
const { callState, micMuted, vidMuted, screensharing, secondaryFeed, sidebarFeeds } = this.state;
329332

330333
// If SDPStreamMetadata isn't supported don't show video mute button in voice calls
331334
const vidMuteButtonShown = call.opponentSupportsSDPStreamMetadata() || call.hasLocalUserMediaVideoTrack;
@@ -337,7 +340,8 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
337340
(call.opponentSupportsSDPStreamMetadata() || call.hasLocalUserMediaVideoTrack) &&
338341
call.state === CallState.Connected;
339342
// Show the sidebar button only if there is something to hide/show
340-
const sidebarButtonShown = (secondaryFeed && !secondaryFeed.isVideoMuted()) || sidebarFeeds.length > 0;
343+
const sidebarButtonShown =
344+
!pipMode && ((secondaryFeed && !secondaryFeed.isVideoMuted()) || sidebarFeeds.length > 0);
341345
// The dial pad & 'more' button actions are only relevant in a connected call
342346
const contextMenuButtonShown = callState === CallState.Connected;
343347
const dialpadButtonShown = callState === CallState.Connected && call.opponentSupportsDTMF();
@@ -372,15 +376,15 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
372376
}
373377

374378
private renderToast(): JSX.Element | null {
375-
const { call } = this.props;
379+
const { call, sidebarShown } = this.props;
376380
const someoneIsScreensharing = call.getFeeds().some((feed) => {
377381
return feed.purpose === SDPStreamMetadataPurpose.Screenshare;
378382
});
379383

380384
if (!someoneIsScreensharing) return null;
381385

382386
const isScreensharing = call.isScreensharing();
383-
const { primaryFeed, sidebarShown } = this.state;
387+
const { primaryFeed } = this.state;
384388
const sharerName = primaryFeed?.getMember()?.name;
385389
if (!sharerName) return null;
386390

@@ -393,8 +397,8 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
393397
}
394398

395399
private renderContent(): JSX.Element {
396-
const { pipMode, call, onResize } = this.props;
397-
const { isLocalOnHold, isRemoteOnHold, sidebarShown, primaryFeed, secondaryFeed, sidebarFeeds } = this.state;
400+
const { pipMode, call, onResize, sidebarShown } = this.props;
401+
const { isLocalOnHold, isRemoteOnHold, primaryFeed, secondaryFeed, sidebarFeeds } = this.state;
398402

399403
const callRoomId = LegacyCallHandler.instance.roomIdForCall(call);
400404
const callRoom = (callRoomId ? MatrixClientPeg.safeGet().getRoom(callRoomId) : undefined) ?? undefined;
@@ -537,8 +541,8 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
537541
}
538542

539543
public render(): React.ReactNode {
540-
const { call, secondaryCall, pipMode, showApps, onMouseDownOnHeader } = this.props;
541-
const { sidebarShown, sidebarFeeds } = this.state;
544+
const { call, secondaryCall, pipMode, showApps, onMouseDownOnHeader, sidebarShown } = this.props;
545+
const { sidebarFeeds } = this.state;
542546

543547
const client = MatrixClientPeg.safeGet();
544548
const callRoomId = LegacyCallHandler.instance.roomIdForCall(call);

src/components/views/voip/LegacyCallViewForRoom.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ interface IProps {
2525

2626
interface IState {
2727
call: MatrixCall | null;
28+
sidebarShown: boolean;
2829
}
2930

3031
/*
@@ -34,26 +35,34 @@ interface IState {
3435
export default class LegacyCallViewForRoom extends React.Component<IProps, IState> {
3536
public constructor(props: IProps) {
3637
super(props);
38+
const call = this.getCall();
3739
this.state = {
38-
call: this.getCall(),
40+
call,
41+
sidebarShown: !!call && LegacyCallHandler.instance.isCallSidebarShown(call.callId),
3942
};
4043
}
4144

4245
public componentDidMount(): void {
4346
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallState, this.updateCall);
4447
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallChangeRoom, this.updateCall);
48+
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.ShownSidebarsChanged, this.updateCall);
4549
}
4650

4751
public componentWillUnmount(): void {
4852
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.CallState, this.updateCall);
4953
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.CallChangeRoom, this.updateCall);
54+
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.ShownSidebarsChanged, this.updateCall);
5055
}
5156

5257
private updateCall = (): void => {
5358
const newCall = this.getCall();
5459
if (newCall !== this.state.call) {
5560
this.setState({ call: newCall });
5661
}
62+
const newSidebarShown = !!newCall && LegacyCallHandler.instance.isCallSidebarShown(newCall.callId);
63+
if (newSidebarShown !== this.state.sidebarShown) {
64+
this.setState({ sidebarShown: newSidebarShown });
65+
}
5766
};
5867

5968
private getCall(): MatrixCall | null {
@@ -75,6 +84,11 @@ export default class LegacyCallViewForRoom extends React.Component<IProps, IStat
7584
this.props.resizeNotifier.stopResizing();
7685
};
7786

87+
private setSidebarShown = (sidebarShown: boolean): void => {
88+
if (!this.state.call) return;
89+
LegacyCallHandler.instance.setCallSidebarShown(this.state.call.callId, sidebarShown);
90+
};
91+
7892
public render(): React.ReactNode {
7993
if (!this.state.call) return null;
8094

@@ -99,7 +113,13 @@ export default class LegacyCallViewForRoom extends React.Component<IProps, IStat
99113
className="mx_LegacyCallViewForRoom_ResizeWrapper"
100114
handleClasses={{ bottom: "mx_LegacyCallViewForRoom_ResizeHandle" }}
101115
>
102-
<LegacyCallView call={this.state.call} pipMode={false} showApps={this.props.showApps} />
116+
<LegacyCallView
117+
call={this.state.call}
118+
pipMode={false}
119+
showApps={this.props.showApps}
120+
sidebarShown={this.state.sidebarShown}
121+
setSidebarShown={this.setSidebarShown}
122+
/>
103123
</Resizable>
104124
</div>
105125
);

test/unit-tests/LegacyCallHandler-test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,4 +585,42 @@ describe("LegacyCallHandler without third party protocols", () => {
585585
expect(mockAudioBufferSourceNode.start).not.toHaveBeenCalled();
586586
});
587587
});
588+
589+
describe("sidebar state", () => {
590+
const roomId = "test-room-id";
591+
592+
it("should default to showing sidebar", () => {
593+
const call = new MatrixCall({
594+
client: MatrixClientPeg.safeGet(),
595+
roomId,
596+
});
597+
const cli = MatrixClientPeg.safeGet();
598+
cli.emit(CallEventHandlerEvent.Incoming, call);
599+
600+
expect(callHandler.isCallSidebarShown(call.callId)).toEqual(true);
601+
});
602+
603+
it("should remember sidebar state per call", () => {
604+
const call = new MatrixCall({
605+
client: MatrixClientPeg.safeGet(),
606+
roomId,
607+
});
608+
const cli = MatrixClientPeg.safeGet();
609+
cli.emit(CallEventHandlerEvent.Incoming, call);
610+
611+
expect(callHandler.isCallSidebarShown(call.callId)).toEqual(true);
612+
callHandler.setCallSidebarShown(call.callId, false);
613+
expect(callHandler.isCallSidebarShown(call.callId)).toEqual(false);
614+
615+
call.emit(CallEvent.Hangup, call);
616+
617+
const call2 = new MatrixCall({
618+
client: MatrixClientPeg.safeGet(),
619+
roomId,
620+
});
621+
cli.emit(CallEventHandlerEvent.Incoming, call2);
622+
623+
expect(callHandler.isCallSidebarShown(call2.callId)).toEqual(true);
624+
});
625+
});
588626
});

test/unit-tests/components/views/voip/LegacyCallView-test.tsx

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ Please see LICENSE files in the repository root for full details.
88
import React from "react";
99
import { render } from "jest-matrix-react";
1010
import { type MatrixCall } from "matrix-js-sdk/src/matrix";
11+
import { type CallFeed } from "matrix-js-sdk/src/webrtc/callFeed";
12+
import { SDPStreamMetadataPurpose } from "matrix-js-sdk/src/webrtc/callEventTypes";
1113

1214
import LegacyCallView from "../../../../../src/components/views/voip/LegacyCallView";
1315
import { stubClient } from "../../../../test-utils";
16+
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
1417

1518
describe("LegacyCallView", () => {
1619
it("should exit full screen on unmount", () => {
@@ -32,9 +35,70 @@ describe("LegacyCallView", () => {
3235
isScreensharing: jest.fn().mockReturnValue(false),
3336
} as unknown as MatrixCall;
3437

35-
const { unmount } = render(<LegacyCallView call={call} />);
38+
const { unmount } = render(<LegacyCallView call={call} sidebarShown={false} />);
3639
expect(document.exitFullscreen).not.toHaveBeenCalled();
3740
unmount();
3841
expect(document.exitFullscreen).toHaveBeenCalled();
3942
});
43+
44+
it("should show/hide the sidebar based on the sidebarShown prop", async () => {
45+
stubClient();
46+
47+
const call = {
48+
roomId: "test-room",
49+
on: jest.fn(),
50+
removeListener: jest.fn(),
51+
getFeeds: jest.fn().mockReturnValue(
52+
[{ local: true }, { local: false }, { local: true, screenshare: true }].map(
53+
(x, i) =>
54+
({
55+
stream: { id: "test-" + i },
56+
addListener: jest.fn(),
57+
removeListener: jest.fn(),
58+
getMember: jest.fn(),
59+
isAudioMuted: jest.fn().mockReturnValue(true),
60+
isVideoMuted: jest.fn().mockReturnValue(true),
61+
isLocal: jest.fn().mockReturnValue(x.local),
62+
purpose: x.screenshare && SDPStreamMetadataPurpose.Screenshare,
63+
}) as unknown as CallFeed,
64+
),
65+
),
66+
isLocalOnHold: jest.fn().mockReturnValue(false),
67+
isRemoteOnHold: jest.fn().mockReturnValue(false),
68+
isMicrophoneMuted: jest.fn().mockReturnValue(true),
69+
isLocalVideoMuted: jest.fn().mockReturnValue(true),
70+
isScreensharing: jest.fn().mockReturnValue(true),
71+
noIncomingFeeds: jest.fn().mockReturnValue(false),
72+
opponentSupportsSDPStreamMetadata: jest.fn().mockReturnValue(true),
73+
} as unknown as MatrixCall;
74+
DMRoomMap.setShared({
75+
getUserIdForRoomId: jest.fn().mockReturnValue("test-user"),
76+
} as unknown as DMRoomMap);
77+
78+
const { container, rerender } = render(<LegacyCallView call={call} sidebarShown={true} />);
79+
expect(container.querySelector(".mx_LegacyCallViewSidebar")).toBeTruthy();
80+
rerender(<LegacyCallView call={call} sidebarShown={true} />);
81+
expect(container.querySelector(".mx_LegacyCallViewSidebar")).toBeTruthy();
82+
});
83+
84+
it("should not show the sidebar button in picture-in-picture mode", async () => {
85+
stubClient();
86+
87+
const call = {
88+
on: jest.fn(),
89+
removeListener: jest.fn(),
90+
getFeeds: jest.fn().mockReturnValue([]),
91+
isLocalOnHold: jest.fn().mockReturnValue(false),
92+
isRemoteOnHold: jest.fn().mockReturnValue(false),
93+
isMicrophoneMuted: jest.fn().mockReturnValue(false),
94+
isLocalVideoMuted: jest.fn().mockReturnValue(false),
95+
isScreensharing: jest.fn().mockReturnValue(false),
96+
} as unknown as MatrixCall;
97+
DMRoomMap.setShared({
98+
getUserIdForRoomId: jest.fn().mockReturnValue("test-user"),
99+
} as unknown as DMRoomMap);
100+
101+
const { container } = render(<LegacyCallView call={call} sidebarShown={false} pipMode={true} />);
102+
expect(container.querySelector(".mx_LegacyCallViewButtons_button_sidebar")).toBeFalsy();
103+
});
40104
});

0 commit comments

Comments
 (0)