Skip to content

Remember whether sidebar is shown for calls when switching rooms #30262

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/LegacyCallHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export enum LegacyCallHandlerEvent {
CallsChanged = "calls_changed",
CallChangeRoom = "call_change_room",
SilencedCallsChanged = "silenced_calls_changed",
ShownSidebarsChanged = "shown_sidebars_changed",
CallState = "call_state",
ProtocolSupport = "protocol_support",
}
Expand All @@ -120,6 +121,7 @@ type EventEmitterMap = {
[LegacyCallHandlerEvent.CallsChanged]: (calls: Map<string, MatrixCall>) => void;
[LegacyCallHandlerEvent.CallChangeRoom]: (call: MatrixCall) => void;
[LegacyCallHandlerEvent.SilencedCallsChanged]: (calls: Set<string>) => void;
[LegacyCallHandlerEvent.ShownSidebarsChanged]: (sidebarsShown: Map<string, boolean>) => void;
[LegacyCallHandlerEvent.CallState]: (mappedRoomId: string | null, status: CallState) => void;
[LegacyCallHandlerEvent.ProtocolSupport]: () => void;
};
Expand All @@ -144,6 +146,8 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl

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

private shownSidebars = new Map<string, boolean>(); // callId (call) -> sidebar show

private backgroundAudio = new BackgroundAudio();
private playingSources: Record<string, AudioBufferSourceNode> = {}; // Record them for stopping

Expand Down Expand Up @@ -240,6 +244,15 @@ export default class LegacyCallHandler extends TypedEventEmitter<LegacyCallHandl
return false;
}

public setCallSidebarShown(callId: string, sidebarShown: boolean): void {
this.shownSidebars.set(callId, sidebarShown);
this.emit(LegacyCallHandlerEvent.ShownSidebarsChanged, this.shownSidebars);
}

public isCallSidebarShown(callId?: string): boolean {
return !!callId && (this.shownSidebars.get(callId) ?? true);
}

private async checkProtocols(maxTries: number): Promise<void> {
try {
const protocols = await MatrixClientPeg.safeGet().getThirdpartyProtocols();
Expand Down
1 change: 1 addition & 0 deletions src/components/structures/PipContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ class PipContainerInner extends React.Component<IProps, IState> {
secondaryCall={this.state.secondaryCall}
pipMode={pipMode}
onResize={onResize}
sidebarShown={false}
/>
));
}
Expand Down
30 changes: 17 additions & 13 deletions src/components/views/voip/LegacyCallView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ interface IProps {
onMouseDownOnHeader?: (event: React.MouseEvent<Element, MouseEvent>) => void;

showApps?: boolean;

sidebarShown: boolean;

setSidebarShown?: (sidebarShown: boolean) => void;
}

interface IState {
Expand All @@ -62,7 +66,6 @@ interface IState {
primaryFeed?: CallFeed;
secondaryFeed?: CallFeed;
sidebarFeeds: Array<CallFeed>;
sidebarShown: boolean;
}

function getFullScreenElement(): Element | null {
Expand Down Expand Up @@ -97,7 +100,6 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
primaryFeed: primary,
secondaryFeed: secondary,
sidebarFeeds: sidebar,
sidebarShown: true,
};
}

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

this.props.setSidebarShown?.(true);

this.setState({
sidebarShown: true,
screensharing: isScreensharing,
});
};
Expand Down Expand Up @@ -320,12 +323,12 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
};

private onToggleSidebar = (): void => {
this.setState({ sidebarShown: !this.state.sidebarShown });
this.props.setSidebarShown?.(!this.props.sidebarShown);
};

private renderCallControls(): JSX.Element {
const { call, pipMode } = this.props;
const { callState, micMuted, vidMuted, screensharing, sidebarShown, secondaryFeed, sidebarFeeds } = this.state;
const { call, pipMode, sidebarShown } = this.props;
const { callState, micMuted, vidMuted, screensharing, secondaryFeed, sidebarFeeds } = this.state;

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

private renderToast(): JSX.Element | null {
const { call } = this.props;
const { call, sidebarShown } = this.props;
const someoneIsScreensharing = call.getFeeds().some((feed) => {
return feed.purpose === SDPStreamMetadataPurpose.Screenshare;
});

if (!someoneIsScreensharing) return null;

const isScreensharing = call.isScreensharing();
const { primaryFeed, sidebarShown } = this.state;
const { primaryFeed } = this.state;
const sharerName = primaryFeed?.getMember()?.name;
if (!sharerName) return null;

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

private renderContent(): JSX.Element {
const { pipMode, call, onResize } = this.props;
const { isLocalOnHold, isRemoteOnHold, sidebarShown, primaryFeed, secondaryFeed, sidebarFeeds } = this.state;
const { pipMode, call, onResize, sidebarShown } = this.props;
const { isLocalOnHold, isRemoteOnHold, primaryFeed, secondaryFeed, sidebarFeeds } = this.state;

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

public render(): React.ReactNode {
const { call, secondaryCall, pipMode, showApps, onMouseDownOnHeader } = this.props;
const { sidebarShown, sidebarFeeds } = this.state;
const { call, secondaryCall, pipMode, showApps, onMouseDownOnHeader, sidebarShown } = this.props;
const { sidebarFeeds } = this.state;

const client = MatrixClientPeg.safeGet();
const callRoomId = LegacyCallHandler.instance.roomIdForCall(call);
Expand Down
20 changes: 19 additions & 1 deletion src/components/views/voip/LegacyCallViewForRoom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface IProps {

interface IState {
call: MatrixCall | null;
sidebarShown: boolean;
}

/*
Expand All @@ -36,24 +37,31 @@ export default class LegacyCallViewForRoom extends React.Component<IProps, IStat
super(props);
this.state = {
call: this.getCall(),
sidebarShown: LegacyCallHandler.instance.isCallSidebarShown(this.props.roomId),
};
}

public componentDidMount(): void {
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallState, this.updateCall);
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.CallChangeRoom, this.updateCall);
LegacyCallHandler.instance.addListener(LegacyCallHandlerEvent.ShownSidebarsChanged, this.updateCall);
}

public componentWillUnmount(): void {
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.CallState, this.updateCall);
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.CallChangeRoom, this.updateCall);
LegacyCallHandler.instance.removeListener(LegacyCallHandlerEvent.ShownSidebarsChanged, this.updateCall);
}

private updateCall = (): void => {
const newCall = this.getCall();
if (newCall !== this.state.call) {
this.setState({ call: newCall });
}
const newSidebarShown = LegacyCallHandler.instance.isCallSidebarShown(this.props.roomId);
if (newSidebarShown !== this.state.sidebarShown) {
this.setState({ sidebarShown: newSidebarShown });
}
};

private getCall(): MatrixCall | null {
Expand All @@ -75,6 +83,10 @@ export default class LegacyCallViewForRoom extends React.Component<IProps, IStat
this.props.resizeNotifier.stopResizing();
};

private setSidebarShown = (sidebarShown: boolean): void => {
LegacyCallHandler.instance.setCallSidebarShown(this.props.roomId, sidebarShown);
};

public render(): React.ReactNode {
if (!this.state.call) return null;

Expand All @@ -99,7 +111,13 @@ export default class LegacyCallViewForRoom extends React.Component<IProps, IStat
className="mx_LegacyCallViewForRoom_ResizeWrapper"
handleClasses={{ bottom: "mx_LegacyCallViewForRoom_ResizeHandle" }}
>
<LegacyCallView call={this.state.call} pipMode={false} showApps={this.props.showApps} />
<LegacyCallView
call={this.state.call}
pipMode={false}
showApps={this.props.showApps}
sidebarShown={this.state.sidebarShown}
setSidebarShown={this.setSidebarShown}
/>
</Resizable>
</div>
);
Expand Down
45 changes: 44 additions & 1 deletion test/unit-tests/components/views/voip/LegacyCallView-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ Please see LICENSE files in the repository root for full details.
import React from "react";
import { render } from "jest-matrix-react";
import { type MatrixCall } from "matrix-js-sdk/src/matrix";
import { type CallFeed } from "matrix-js-sdk/src/webrtc/callFeed";
import { SDPStreamMetadataPurpose } from "matrix-js-sdk/src/webrtc/callEventTypes";

import LegacyCallView from "../../../../../src/components/views/voip/LegacyCallView";
import { stubClient } from "../../../../test-utils";
import DMRoomMap from "../../../../../src/utils/DMRoomMap";

describe("LegacyCallView", () => {
it("should exit full screen on unmount", () => {
Expand All @@ -32,9 +35,49 @@ describe("LegacyCallView", () => {
isScreensharing: jest.fn().mockReturnValue(false),
} as unknown as MatrixCall;

const { unmount } = render(<LegacyCallView call={call} />);
const { unmount } = render(<LegacyCallView call={call} sidebarShown={false} />);
expect(document.exitFullscreen).not.toHaveBeenCalled();
unmount();
expect(document.exitFullscreen).toHaveBeenCalled();
});

it("should show/hide the sidebar based on the sidebarShown prop", async () => {
stubClient();

const call = {
roomId: "test-room",
on: jest.fn(),
removeListener: jest.fn(),
getFeeds: jest.fn().mockReturnValue(
[{ local: true }, { local: false }, { local: true, screenshare: true }].map(
(x, i) =>
({
stream: { id: "test-" + i },
addListener: jest.fn(),
removeListener: jest.fn(),
getMember: jest.fn(),
isAudioMuted: jest.fn().mockReturnValue(true),
isVideoMuted: jest.fn().mockReturnValue(true),
isLocal: jest.fn().mockReturnValue(x.local),
purpose: x.screenshare && SDPStreamMetadataPurpose.Screenshare,
}) as unknown as CallFeed,
),
),
isLocalOnHold: jest.fn().mockReturnValue(false),
isRemoteOnHold: jest.fn().mockReturnValue(false),
isMicrophoneMuted: jest.fn().mockReturnValue(true),
isLocalVideoMuted: jest.fn().mockReturnValue(true),
isScreensharing: jest.fn().mockReturnValue(true),
noIncomingFeeds: jest.fn().mockReturnValue(false),
opponentSupportsSDPStreamMetadata: jest.fn().mockReturnValue(true),
} as unknown as MatrixCall;
DMRoomMap.setShared({
getUserIdForRoomId: jest.fn().mockReturnValue("test-user"),
} as unknown as DMRoomMap);

const { container, rerender } = render(<LegacyCallView call={call} sidebarShown={true} />);
expect(container.querySelector(".mx_LegacyCallViewSidebar")).toBeTruthy();
rerender(<LegacyCallView call={call} sidebarShown={true} />);
expect(container.querySelector(".mx_LegacyCallViewSidebar")).toBeTruthy();
});
});
Loading