diff --git a/res/css/_components.pcss b/res/css/_components.pcss index 1d6452aa82..f916d5925d 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -370,3 +370,4 @@ @import "./voice-broadcast/atoms/_VoiceBroadcastHeader.pcss"; @import "./voice-broadcast/molecules/_VoiceBroadcastPlaybackBody.pcss"; @import "./voice-broadcast/molecules/_VoiceBroadcastRecordingBody.pcss"; +@import "./voice-broadcast/molecules/_VoiceBroadcastRecordingPip.pcss"; diff --git a/res/css/voice-broadcast/molecules/_VoiceBroadcastRecordingPip.pcss b/res/css/voice-broadcast/molecules/_VoiceBroadcastRecordingPip.pcss new file mode 100644 index 0000000000..b01b1b80db --- /dev/null +++ b/res/css/voice-broadcast/molecules/_VoiceBroadcastRecordingPip.pcss @@ -0,0 +1,35 @@ +/* +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_VoiceBroadcastRecordingPip { + background-color: $system; + border-radius: 8px; + box-shadow: 0 2px 8px 0 #0000004a; + display: inline-block; + padding: $spacing-12; +} + +.mx_VoiceBroadcastRecordingPip_divider { + background-color: $quinary-content; + border: 0; + height: 1px; + margin: $spacing-12 0; +} + +.mx_VoiceBroadcastRecordingPip_controls { + display: flex; + justify-content: center; +} diff --git a/res/img/element-icons/Stop.svg b/res/img/element-icons/Stop.svg new file mode 100644 index 0000000000..29c7a0cef7 --- /dev/null +++ b/res/img/element-icons/Stop.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/SdkConfig.ts b/src/SdkConfig.ts index 0d3400f4bb..6698f3ffb2 100644 --- a/src/SdkConfig.ts +++ b/src/SdkConfig.ts @@ -47,7 +47,7 @@ export const DEFAULTS: IConfigOptions = { url: "https://element.io/get-started", }, voice_broadcast: { - chunk_length: 60 * 1000, // one minute + chunk_length: 60, // one minute }, }; diff --git a/src/components/atoms/Icon.tsx b/src/components/atoms/Icon.tsx index 2241a711ca..56d8236250 100644 --- a/src/components/atoms/Icon.tsx +++ b/src/components/atoms/Icon.tsx @@ -20,12 +20,14 @@ import liveIcon from "../../../res/img/element-icons/live.svg"; import microphoneIcon from "../../../res/img/voip/call-view/mic-on.svg"; import pauseIcon from "../../../res/img/element-icons/pause.svg"; import playIcon from "../../../res/img/element-icons/play.svg"; +import stopIcon from "../../../res/img/element-icons/Stop.svg"; export enum IconType { Live, Microphone, Pause, Play, + Stop, } const iconTypeMap = new Map([ @@ -33,6 +35,7 @@ const iconTypeMap = new Map([ [IconType.Microphone, microphoneIcon], [IconType.Pause, pauseIcon], [IconType.Play, playIcon], + [IconType.Stop, stopIcon], ]); export enum IconColour { diff --git a/src/components/views/voip/PipView.tsx b/src/components/views/voip/PipView.tsx index 0c6feec0d5..0bebfe1bf3 100644 --- a/src/components/views/voip/PipView.tsx +++ b/src/components/views/voip/PipView.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { createRef } from 'react'; +import React, { createRef, useState } from 'react'; import { CallEvent, CallState, MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; import { logger } from "matrix-js-sdk/src/logger"; import classNames from 'classnames'; @@ -35,6 +35,13 @@ import WidgetStore, { IApp } from "../../../stores/WidgetStore"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { UPDATE_EVENT } from '../../../stores/AsyncStore'; import { CallStore } from "../../../stores/CallStore"; +import { + VoiceBroadcastRecording, + VoiceBroadcastRecordingPip, + VoiceBroadcastRecordingsStore, + VoiceBroadcastRecordingsStoreEvent, +} from '../../../voice-broadcast'; +import { useTypedEventEmitter } from '../../../hooks/useEventEmitter'; const SHOW_CALL_IN_STATES = [ CallState.Connected, @@ -46,6 +53,7 @@ const SHOW_CALL_IN_STATES = [ ]; interface IProps { + voiceBroadcastRecording?: VoiceBroadcastRecording; } interface IState { @@ -115,7 +123,7 @@ function getPrimarySecondaryCallsForPip(roomId: string): [MatrixCall, MatrixCall * and all widgets that are active but not shown in any other possible container. */ -export default class PipView extends React.Component { +class PipView extends React.Component { private movePersistedElement = createRef<() => void>(); constructor(props: IProps) { @@ -353,6 +361,14 @@ export default class PipView extends React.Component { ; } + if (this.props.voiceBroadcastRecording) { + pipContent = ({ onStartMoving }) =>
+ +
; + } + if (!!pipContent) { return { return null; } } + +const PipViewHOC: React.FC = (props) => { + // TODO Michael W: extract to custom hook + + const voiceBroadcastRecordingsStore = VoiceBroadcastRecordingsStore.instance(); + const [voiceBroadcastRecording, setVoiceBroadcastRecording] = useState( + voiceBroadcastRecordingsStore.getCurrent(), + ); + + useTypedEventEmitter( + voiceBroadcastRecordingsStore, + VoiceBroadcastRecordingsStoreEvent.CurrentChanged, + (recording: VoiceBroadcastRecording) => { + setVoiceBroadcastRecording(recording); + }, + ); + + return ; +}; + +export default PipViewHOC; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 1b05ce98f8..0af4100c05 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -640,6 +640,7 @@ "Live": "Live", "pause voice broadcast": "pause voice broadcast", "resume voice broadcast": "resume voice broadcast", + "stop voice broadcast": "stop voice broadcast", "Voice broadcast": "Voice broadcast", "Cannot reach homeserver": "Cannot reach homeserver", "Ensure you have a stable internet connection, or get in touch with the server admin": "Ensure you have a stable internet connection, or get in touch with the server admin", diff --git a/src/voice-broadcast/components/atoms/StopButton.tsx b/src/voice-broadcast/components/atoms/StopButton.tsx new file mode 100644 index 0000000000..50abb209d0 --- /dev/null +++ b/src/voice-broadcast/components/atoms/StopButton.tsx @@ -0,0 +1,40 @@ +/* +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 { Icon, IconColour, IconType } from "../../../components/atoms/Icon"; +import AccessibleButton from "../../../components/views/elements/AccessibleButton"; +import { _t } from "../../../languageHandler"; + +interface Props { + onClick: () => void; +} + +export const StopButton: React.FC = ({ + onClick, +}) => { + return + + ; +}; diff --git a/src/voice-broadcast/components/molecules/VoiceBroadcastRecordingPip.tsx b/src/voice-broadcast/components/molecules/VoiceBroadcastRecordingPip.tsx new file mode 100644 index 0000000000..c7604b7d90 --- /dev/null +++ b/src/voice-broadcast/components/molecules/VoiceBroadcastRecordingPip.tsx @@ -0,0 +1,51 @@ +/* +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 { + StopButton, + VoiceBroadcastRecording, +} from "../.."; +import { useVoiceBroadcastRecording } from "../../hooks/useVoiceBroadcastRecording"; +import { VoiceBroadcastHeader } from "../atoms/VoiceBroadcastHeader"; + +interface VoiceBroadcastRecordingPipProps { + recording: VoiceBroadcastRecording; +} + +export const VoiceBroadcastRecordingPip: React.FC = ({ recording }) => { + const { + live, + sender, + room, + stopRecording, + } = useVoiceBroadcastRecording(recording); + + return
+ +
+
+ +
+
; +}; diff --git a/src/voice-broadcast/index.ts b/src/voice-broadcast/index.ts index 2a3bb6573f..7262382b0c 100644 --- a/src/voice-broadcast/index.ts +++ b/src/voice-broadcast/index.ts @@ -27,15 +27,17 @@ export * from "./audio/VoiceBroadcastRecorder"; export * from "./components/VoiceBroadcastBody"; export * from "./components/atoms/LiveBadge"; export * from "./components/atoms/PlaybackControlButton"; +export * from "./components/atoms/StopButton"; export * from "./components/atoms/VoiceBroadcastHeader"; export * from "./components/molecules/VoiceBroadcastPlaybackBody"; export * from "./components/molecules/VoiceBroadcastRecordingBody"; +export * from "./components/molecules/VoiceBroadcastRecordingPip"; +export * from "./hooks/useVoiceBroadcastRecording"; export * from "./stores/VoiceBroadcastPlaybacksStore"; export * from "./stores/VoiceBroadcastRecordingsStore"; export * from "./utils/shouldDisplayAsVoiceBroadcastRecordingTile"; export * from "./utils/shouldDisplayAsVoiceBroadcastTile"; export * from "./utils/startNewVoiceBroadcastRecording"; -export * from "./hooks/useVoiceBroadcastRecording"; export const VoiceBroadcastInfoEventType = "io.element.voice_broadcast_info"; export const VoiceBroadcastChunkEventType = "io.element.voice_broadcast_chunk"; diff --git a/test/voice-broadcast/components/atoms/StopButton-test.tsx b/test/voice-broadcast/components/atoms/StopButton-test.tsx new file mode 100644 index 0000000000..742844fca1 --- /dev/null +++ b/test/voice-broadcast/components/atoms/StopButton-test.tsx @@ -0,0 +1,45 @@ +/* +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 { render, RenderResult } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + +import { StopButton } from "../../../../src/voice-broadcast"; + +describe("StopButton", () => { + let result: RenderResult; + let onClick: () => {}; + + beforeEach(() => { + onClick = jest.fn(); + result = render(); + }); + + it("should render as expected", () => { + expect(result.container).toMatchSnapshot(); + }); + + describe("when clicking it", () => { + beforeEach(async () => { + await userEvent.click(result.getByLabelText("stop voice broadcast")); + }); + + it("should invoke the callback", () => { + expect(onClick).toHaveBeenCalled(); + }); + }); +}); diff --git a/test/voice-broadcast/components/atoms/__snapshots__/StopButton-test.tsx.snap b/test/voice-broadcast/components/atoms/__snapshots__/StopButton-test.tsx.snap new file mode 100644 index 0000000000..ca5015a79a --- /dev/null +++ b/test/voice-broadcast/components/atoms/__snapshots__/StopButton-test.tsx.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`StopButton should render as expected 1`] = ` +
+
+