This repository has been archived by the owner on Sep 11, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 833
Room call banner #9378
Merged
Merged
Room call banner #9378
Changes from 22 commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
194cdec
Room call banner
toger5 29f48ed
test
toger5 afaee18
more tests
toger5 af4417f
spacing
toger5 13349c1
fix alignment
toger5 3f3f35e
fiox alignment
toger5 fa5ccde
lint and test cleanup
toger5 b2f53cb
remove unused import
toger5 3a81bfe
more tests
toger5 8b7bd6a
Update test/components/views/beacon/RoomCallBanner-test.tsx
toger5 17c6db3
Update src/components/views/beacon/RoomCallBanner.tsx
toger5 fecd428
Update src/components/views/beacon/RoomCallBanner.tsx
toger5 11ed620
Update test/components/views/beacon/RoomCallBanner-test.tsx
toger5 f792d01
Update test/components/views/beacon/RoomCallBanner-test.tsx
toger5 211c9bc
reviews
toger5 8e8d02d
style pcss
toger5 01b708e
stylelint
toger5 7a94db3
remove leave button from header
toger5 bebe0ff
...
toger5 8df7762
review
toger5 6d84327
Merge branch 'develop' into toger5/call_banner
toger5 9741353
imports...
toger5 55146e7
review + more tests
toger5 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
/* | ||
Copyright 2022 The Matrix.org Foundation C.I.C. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
.mx_RoomCallBanner { | ||
width: 100%; | ||
display: flex; | ||
flex-direction: row; | ||
align-items: center; | ||
|
||
box-sizing: border-box; | ||
padding: $spacing-12 $spacing-16; | ||
|
||
color: $primary-content; | ||
background-color: $system; | ||
cursor: pointer; | ||
} | ||
|
||
.mx_RoomCallBanner_text { | ||
display: flex; | ||
flex: 1; | ||
align-items: center; | ||
} | ||
|
||
.mx_RoomCallBanner_label { | ||
color: $primary-content; | ||
font-weight: 600; | ||
padding-right: $spacing-8; | ||
|
||
&::before { | ||
display: inline-block; | ||
vertical-align: text-top; | ||
content: ""; | ||
background-color: $secondary-content; | ||
mask-size: 16px; | ||
width: 16px; | ||
height: 16px; | ||
margin-right: 4px; | ||
bottom: 2px; | ||
mask-image: url("$(res)/img/element-icons/call/video-call.svg"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
/* | ||
Copyright 2022 The Matrix.org Foundation C.I.C. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
import React, { useCallback } from "react"; | ||
import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; | ||
|
||
import { _t } from "../../../languageHandler"; | ||
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; | ||
import dispatcher, { defaultDispatcher } from "../../../dispatcher/dispatcher"; | ||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; | ||
import { Action } from "../../../dispatcher/actions"; | ||
import { Call, ConnectionState, ElementCall } from "../../../models/Call"; | ||
import { useCall } from "../../../hooks/useCall"; | ||
import { RoomViewStore } from "../../../stores/RoomViewStore"; | ||
import { useEventEmitterState } from "../../../hooks/useEventEmitter"; | ||
import { | ||
OwnBeaconStore, | ||
OwnBeaconStoreEvent, | ||
} from "../../../stores/OwnBeaconStore"; | ||
import { CallDurationFromEvent } from "../voip/CallDuration"; | ||
|
||
interface RoomCallBannerProps { | ||
roomId: Room["roomId"]; | ||
call: Call; | ||
} | ||
|
||
const RoomCallBannerInner: React.FC<RoomCallBannerProps> = ({ | ||
roomId, | ||
call, | ||
}) => { | ||
const callEvent: MatrixEvent | null = (call as ElementCall)?.groupCall; | ||
|
||
const connect = useCallback( | ||
(ev: ButtonEvent) => { | ||
ev.preventDefault(); | ||
defaultDispatcher.dispatch<ViewRoomPayload>({ | ||
action: Action.ViewRoom, | ||
room_id: roomId, | ||
view_call: true, | ||
metricsTrigger: undefined, | ||
}); | ||
}, | ||
[roomId], | ||
); | ||
|
||
const onClick = useCallback(() => { | ||
dispatcher.dispatch<ViewRoomPayload>({ | ||
action: Action.ViewRoom, | ||
room_id: roomId, | ||
metricsTrigger: undefined, | ||
event_id: callEvent.getId(), | ||
scroll_into_view: true, | ||
highlighted: true, | ||
}); | ||
}, [callEvent, roomId]); | ||
|
||
return ( | ||
<div | ||
className="mx_RoomCallBanner" | ||
onClick={onClick} | ||
> | ||
<div className="mx_RoomCallBanner_text"> | ||
<span className="mx_RoomCallBanner_label">{ _t("Video call") }</span> | ||
<CallDurationFromEvent mxEvent={callEvent} /> | ||
</div> | ||
|
||
<AccessibleButton | ||
onClick={connect} | ||
kind="primary" | ||
element="button" | ||
disabled={false} | ||
> | ||
{ _t("Join") } | ||
</AccessibleButton> | ||
</div> | ||
); | ||
}; | ||
|
||
interface Props { | ||
roomId: Room["roomId"]; | ||
} | ||
|
||
const RoomCallBanner: React.FC<Props> = ({ roomId }) => { | ||
const call = useCall(roomId); | ||
|
||
// this section is to check if we have a live location share. If so, we dont show the call banner | ||
const isMonitoringLiveLocation = useEventEmitterState( | ||
OwnBeaconStore.instance, | ||
OwnBeaconStoreEvent.MonitoringLivePosition, | ||
() => OwnBeaconStore.instance.isMonitoringLiveLocation, | ||
); | ||
|
||
const liveBeaconIds = useEventEmitterState( | ||
OwnBeaconStore.instance, | ||
OwnBeaconStoreEvent.LivenessChange, | ||
() => OwnBeaconStore.instance.getLiveBeaconIds(roomId), | ||
); | ||
|
||
if (isMonitoringLiveLocation && liveBeaconIds.length) { | ||
toger5 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return null; | ||
} | ||
|
||
// Check if the call is already showing. No banner is needed in this case. | ||
toger5 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (RoomViewStore.instance.isViewingCall()) { | ||
return null; | ||
} | ||
|
||
// split into outer/inner to avoid watching various parts if there is no call | ||
if (call) { | ||
// No banner if the call is not connected | ||
toger5 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (call.connectionState !== ConnectionState.Disconnected) {return null;} | ||
toger5 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return <RoomCallBannerInner call={call} roomId={roomId} />; | ||
} | ||
return null; | ||
}; | ||
|
||
export default RoomCallBanner; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -67,6 +67,7 @@ import IconizedContextMenu, { | |
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; | ||
import { CallDurationFromEvent } from "../voip/CallDuration"; | ||
import { Alignment } from "../elements/Tooltip"; | ||
import RoomCallBanner from '../beacon/RoomCallBanner'; | ||
|
||
class DisabledWithReason { | ||
constructor(public readonly reason: string) { } | ||
|
@@ -733,6 +734,7 @@ export default class RoomHeader extends React.Component<IProps, IState> { | |
{ betaPill } | ||
{ buttons } | ||
</div> | ||
<RoomCallBanner roomId={this.props.room.roomId} /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could avoid having RoomCallBanner depend on beacon store by doing something like:
Would keep things simpler in RoomCallBanner, and make this more future proof There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That makes sense. It would make the tests involve the whole Room Header though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think it is fine leaving it like this in favor of not changing the tests? |
||
<RoomLiveShareWarning roomId={this.props.room.roomId} /> | ||
</header> | ||
); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
/* | ||
Copyright 2022 The Matrix.org Foundation C.I.C. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
import React from "react"; | ||
import { act } from "react-dom/test-utils"; | ||
import { | ||
Room, | ||
PendingEventOrdering, | ||
MatrixClient, | ||
RoomMember, | ||
RoomStateEvent, | ||
} from "matrix-js-sdk/src/matrix"; | ||
import { ClientWidgetApi, Widget } from "matrix-widget-api"; | ||
import { | ||
cleanup, | ||
render, | ||
screen, | ||
} from "@testing-library/react"; | ||
import { mocked, Mocked } from "jest-mock"; | ||
|
||
import { | ||
mkRoomMember, | ||
MockedCall, | ||
setupAsyncStoreWithClient, | ||
stubClient, | ||
useMockedCalls, | ||
} from "../../../test-utils"; | ||
import RoomCallBanner from "../../../../src/components/views/beacon/RoomCallBanner"; | ||
import { CallStore } from "../../../../src/stores/CallStore"; | ||
import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessagingStore"; | ||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; | ||
import { RoomViewStore } from "../../../../src/stores/RoomViewStore"; | ||
|
||
describe("<RoomCallBanner />", () => { | ||
toger5 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let client: Mocked<MatrixClient>; | ||
let room: Room; | ||
let alice: RoomMember; | ||
useMockedCalls(); | ||
|
||
const defaultProps = { | ||
roomId: "!1:example.org", | ||
}; | ||
|
||
beforeEach(() => { | ||
stubClient(); | ||
|
||
client = mocked(MatrixClientPeg.get()); | ||
|
||
room = new Room("!1:example.org", client, "@alice:example.org", { | ||
pendingEventOrdering: PendingEventOrdering.Detached, | ||
}); | ||
alice = mkRoomMember(room.roomId, "@alice:example.org"); | ||
jest.spyOn(room, "getMember").mockImplementation((userId) => | ||
userId === alice.userId ? alice : null, | ||
); | ||
|
||
client.getRoom.mockImplementation((roomId) => | ||
roomId === room.roomId ? room : null, | ||
); | ||
client.getRooms.mockReturnValue([room]); | ||
client.reEmitter.reEmit(room, [RoomStateEvent.Events]); | ||
|
||
setupAsyncStoreWithClient(CallStore.instance, client); | ||
setupAsyncStoreWithClient(WidgetMessagingStore.instance, client); | ||
}); | ||
|
||
afterEach(async () => { | ||
client.reEmitter.stopReEmitting(room, [RoomStateEvent.Events]); | ||
}); | ||
|
||
const renderBanner = async (props = {}): Promise<void> => { | ||
render(<RoomCallBanner {...defaultProps} {...props} />); | ||
await act(() => Promise.resolve()); // Let effects settle | ||
}; | ||
|
||
it("renders nothing when there is no call", async () => { | ||
await renderBanner(); | ||
const banner = await screen.queryByText("Video call"); | ||
expect(banner).toBeFalsy(); | ||
}); | ||
|
||
describe("call started", () => { | ||
let call: MockedCall; | ||
let widget: Widget; | ||
|
||
beforeEach(() => { | ||
MockedCall.create(room, "1"); | ||
const maybeCall = CallStore.instance.getCall(room.roomId); | ||
if (!(maybeCall instanceof MockedCall)) {throw new Error("Failed to create call");} | ||
call = maybeCall; | ||
|
||
widget = new Widget(call.widget); | ||
WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, { | ||
stop: () => {}, | ||
} as unknown as ClientWidgetApi); | ||
}); | ||
afterEach(() => { | ||
cleanup(); // Unmount before we do any cleanup that might update the component | ||
call.destroy(); | ||
WidgetMessagingStore.instance.stopMessaging(widget, room.roomId); | ||
}); | ||
|
||
it("renders if there is a call", async () => { | ||
await renderBanner(); | ||
await screen.findByText("Video call"); | ||
}); | ||
|
||
it("shows Join button if the user has not joined", async () => { | ||
await renderBanner(); | ||
await screen.findByText("Join"); | ||
}); | ||
|
||
it("dont show banner if the call is shown", async () => { | ||
jest.spyOn(RoomViewStore.instance, 'isViewingCall').mockReturnValue(true); | ||
await renderBanner(); | ||
const banner = await screen.queryByText("Video call"); | ||
expect(banner).toBeFalsy(); | ||
}); | ||
}); | ||
|
||
// TODO: test clicking buttons | ||
// TODO: add live location share warning test (should not render if there is an active live location share) | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the comment could mention why, it could be helpful - I'm assuming because they just occupy the same space, in which case this i obviously a bit nonideal & we probably want something a bit more like the toast manager that decides which one takes precedence (but no need to block on that part).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, carry also proposed a better solution than what it currently is. (this makes testing easier though (I think) since it is all inside one component)