From 8e719d57a2ca09787f415b9eabb937ffe857f4c0 Mon Sep 17 00:00:00 2001 From: Michael Weimann Date: Mon, 26 Sep 2022 15:29:38 +0200 Subject: [PATCH] Add voice broadcast recording body (#9316) * Add voice broadcast recording body * Change icon element; update css variables * Update Icon-test snapshots --- package.json | 1 + res/css/_components.pcss | 3 + res/css/components/atoms/_Icon.pcss | 34 ++++ res/css/voice-broadcast/atoms/_LiveBadge.pcss | 27 +++ .../_VoiceBroadcastRecordingBody.pcss | 29 +++ res/themes/dark/css/_dark.pcss | 5 + res/themes/legacy-dark/css/_legacy-dark.pcss | 5 + .../legacy-light/css/_legacy-light.pcss | 5 + res/themes/light/css/_light.pcss | 5 + src/components/atoms/Icon.tsx | 69 +++++++ src/components/structures/MessagePanel.tsx | 12 +- .../views/messages/MessageEvent.tsx | 6 + .../views/rooms/MessageComposer.tsx | 23 ++- .../views/rooms/SendMessageComposer.tsx | 1 + src/events/EventTileFactory.tsx | 5 + src/i18n/strings/en_EN.json | 1 + .../components/VoiceBroadcastBody.tsx | 70 +++++++ .../components/atoms/LiveBadge.tsx | 27 +++ src/voice-broadcast/components/index.ts | 19 ++ .../molecules/VoiceBroadcastRecordingBody.tsx | 56 ++++++ src/voice-broadcast/index.ts | 26 +++ src/voice-broadcast/utils/index.ts | 17 ++ .../shouldDisplayAsVoiceBroadcastTile.ts | 27 +++ test/components/atoms/Icon-test.tsx | 47 +++++ .../atoms/__snapshots__/Icon-test.tsx.snap | 34 ++++ .../components/VoiceBroadcastBody-test.tsx | 182 ++++++++++++++++++ .../components/atoms/LiveBadge-test.tsx | 27 +++ .../__snapshots__/LiveBadge-test.tsx.snap | 17 ++ .../VoiceBroadcastRecordingBody-test.tsx | 99 ++++++++++ .../VoiceBroadcastRecordingBody-test.tsx.snap | 33 ++++ .../shouldDisplayAsVoiceBroadcastTile-test.ts | 149 ++++++++++++++ yarn.lock | 5 + 32 files changed, 1059 insertions(+), 7 deletions(-) create mode 100644 res/css/components/atoms/_Icon.pcss create mode 100644 res/css/voice-broadcast/atoms/_LiveBadge.pcss create mode 100644 res/css/voice-broadcast/molecules/_VoiceBroadcastRecordingBody.pcss create mode 100644 src/components/atoms/Icon.tsx create mode 100644 src/voice-broadcast/components/VoiceBroadcastBody.tsx create mode 100644 src/voice-broadcast/components/atoms/LiveBadge.tsx create mode 100644 src/voice-broadcast/components/index.ts create mode 100644 src/voice-broadcast/components/molecules/VoiceBroadcastRecordingBody.tsx create mode 100644 src/voice-broadcast/utils/index.ts create mode 100644 src/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastTile.ts create mode 100644 test/components/atoms/Icon-test.tsx create mode 100644 test/components/atoms/__snapshots__/Icon-test.tsx.snap create mode 100644 test/voice-broadcast/components/VoiceBroadcastBody-test.tsx create mode 100644 test/voice-broadcast/components/atoms/LiveBadge-test.tsx create mode 100644 test/voice-broadcast/components/atoms/__snapshots__/LiveBadge-test.tsx.snap create mode 100644 test/voice-broadcast/components/molecules/VoiceBroadcastRecordingBody-test.tsx create mode 100644 test/voice-broadcast/components/molecules/__snapshots__/VoiceBroadcastRecordingBody-test.tsx.snap create mode 100644 test/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastTile-test.ts diff --git a/package.json b/package.json index 15dc5b6f79..14c9b297c4 100644 --- a/package.json +++ b/package.json @@ -140,6 +140,7 @@ "@sinonjs/fake-timers": "^9.1.2", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^12.1.5", + "@testing-library/user-event": "^14.4.3", "@types/classnames": "^2.2.11", "@types/commonmark": "^0.27.4", "@types/counterpart": "^0.18.1", diff --git a/res/css/_components.pcss b/res/css/_components.pcss index e4b9c999f0..cfcf88a4d7 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -4,6 +4,7 @@ @import "./_font-sizes.pcss"; @import "./_font-weights.pcss"; @import "./_spacing.pcss"; +@import "./components/atoms/_Icon.pcss"; @import "./components/views/beacon/_BeaconListItem.pcss"; @import "./components/views/beacon/_BeaconStatus.pcss"; @import "./components/views/beacon/_BeaconStatusTooltip.pcss"; @@ -357,3 +358,5 @@ @import "./views/voip/_LegacyCallViewSidebar.pcss"; @import "./views/voip/_PiPContainer.pcss"; @import "./views/voip/_VideoFeed.pcss"; +@import "./voice-broadcast/atoms/_LiveBadge.pcss"; +@import "./voice-broadcast/molecules/_VoiceBroadcastRecordingBody.pcss"; diff --git a/res/css/components/atoms/_Icon.pcss b/res/css/components/atoms/_Icon.pcss new file mode 100644 index 0000000000..08a72d3d5b --- /dev/null +++ b/res/css/components/atoms/_Icon.pcss @@ -0,0 +1,34 @@ +/* +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_Icon { + mask-position: center; + mask-repeat: no-repeat; + mask-size: contain; +} + +.mx_Icon_16 { + height: 16px; + width: 16px; +} + +.mx_Icon_accent { + background-color: $accent; +} + +.mx_Icon_live-badge { + background-color: #fff; +} diff --git a/res/css/voice-broadcast/atoms/_LiveBadge.pcss b/res/css/voice-broadcast/atoms/_LiveBadge.pcss new file mode 100644 index 0000000000..6da1f041a1 --- /dev/null +++ b/res/css/voice-broadcast/atoms/_LiveBadge.pcss @@ -0,0 +1,27 @@ +/* +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_LiveBadge { + align-items: center; + background-color: $alert; + border-radius: 2px; + color: $live-badge-color; + display: flex; + font-size: $font-12px; + font-weight: $font-semi-bold; + gap: $spacing-4; + padding: 2px 4px; +} diff --git a/res/css/voice-broadcast/molecules/_VoiceBroadcastRecordingBody.pcss b/res/css/voice-broadcast/molecules/_VoiceBroadcastRecordingBody.pcss new file mode 100644 index 0000000000..13e3104c9a --- /dev/null +++ b/res/css/voice-broadcast/molecules/_VoiceBroadcastRecordingBody.pcss @@ -0,0 +1,29 @@ +/* +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_VoiceBroadcastRecordingBody { + align-items: flex-start; + background-color: $quinary-content; + border-radius: 8px; + display: inline-flex; + gap: $spacing-8; + padding: 12px; +} + +.mx_VoiceBroadcastRecordingBody_title { + font-size: $font-12px; + font-weight: $font-semi-bold; +} diff --git a/res/themes/dark/css/_dark.pcss b/res/themes/dark/css/_dark.pcss index b17010f275..6fbdb66165 100644 --- a/res/themes/dark/css/_dark.pcss +++ b/res/themes/dark/css/_dark.pcss @@ -206,6 +206,11 @@ $location-live-secondary-color: #deddfd; } /* ******************** */ +/* Voice Broadcast */ +/* ******************** */ +$live-badge-color: #ffffff; +/* ******************** */ + /* One-off colors */ /* ******************** */ $progressbar-bg-color: $system; diff --git a/res/themes/legacy-dark/css/_legacy-dark.pcss b/res/themes/legacy-dark/css/_legacy-dark.pcss index 49043b648d..92767ded99 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.pcss +++ b/res/themes/legacy-dark/css/_legacy-dark.pcss @@ -203,6 +203,11 @@ $location-live-color: #5c56f5; $location-live-secondary-color: #deddfd; /* ******************** */ +/* Voice Broadcast */ +/* ******************** */ +$live-badge-color: #ffffff; +/* ******************** */ + /* ***** Mixins! ***** */ @define-mixin mx_DialogButton { diff --git a/res/themes/legacy-light/css/_legacy-light.pcss b/res/themes/legacy-light/css/_legacy-light.pcss index 4380bef408..6972d5a20b 100644 --- a/res/themes/legacy-light/css/_legacy-light.pcss +++ b/res/themes/legacy-light/css/_legacy-light.pcss @@ -303,6 +303,11 @@ $location-live-color: #5c56f5; $location-live-secondary-color: #deddfd; /* ******************** */ +/* Voice Broadcast */ +/* ******************** */ +$live-badge-color: #ffffff; +/* ******************** */ + /* ***** Mixins! ***** */ @define-mixin mx_DialogButton { diff --git a/res/themes/light/css/_light.pcss b/res/themes/light/css/_light.pcss index 630ca8c70d..708b943c7c 100644 --- a/res/themes/light/css/_light.pcss +++ b/res/themes/light/css/_light.pcss @@ -337,6 +337,11 @@ $location-live-color: #5c56f5; $location-live-secondary-color: #deddfd; /* ******************** */ +/* Voice Broadcast */ +/* ******************** */ +$live-badge-color: #ffffff; +/* ******************** */ + /* Mixins */ /* ******************** */ @define-mixin mx_DialogButton { diff --git a/src/components/atoms/Icon.tsx b/src/components/atoms/Icon.tsx new file mode 100644 index 0000000000..bb6ea61524 --- /dev/null +++ b/src/components/atoms/Icon.tsx @@ -0,0 +1,69 @@ +/* +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 liveIcon from "../../../res/img/element-icons/live.svg"; + +export enum IconType { + Live, +} + +const iconTypeMap = new Map([ + [IconType.Live, liveIcon], +]); + +export enum IconColour { + Accent = "accent", + LiveBadge = "live-badge", +} + +export enum IconSize { + S16 = "16", +} + +interface IconProps { + colour?: IconColour; + size?: IconSize; + type: IconType; +} + +export const Icon: React.FC = ({ + size = IconSize.S16, + colour = IconColour.Accent, + type, + ...rest +}) => { + const classes = [ + "mx_Icon", + `mx_Icon_${size}`, + `mx_Icon_${colour}`, + ]; + + const styles: React.CSSProperties = { + maskImage: `url("${iconTypeMap.get(type)}")`, + }; + + return ( + + ); +}; diff --git a/src/components/structures/MessagePanel.tsx b/src/components/structures/MessagePanel.tsx index 0dbd10cb46..223eb0a6db 100644 --- a/src/components/structures/MessagePanel.tsx +++ b/src/components/structures/MessagePanel.tsx @@ -59,6 +59,7 @@ import { IReadReceiptInfo } from "../views/rooms/ReadReceiptMarker"; import { haveRendererForEvent } from "../../events/EventTileFactory"; import { editorRoomKey } from "../../Editing"; import { hasThreadSummary } from "../../utils/EventUtils"; +import { VoiceBroadcastInfoEventType } from '../../voice-broadcast'; const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes const continuedTypes = [EventType.Sticker, EventType.RoomMessage]; @@ -1091,11 +1092,20 @@ class CreationGrouper extends BaseGrouper { && (ev.getStateKey() !== createEvent.getSender() || ev.getContent()["membership"] !== "join")) { return false; } + + const eventType = ev.getType(); + // beacons are not part of room creation configuration // should be shown in timeline - if (M_BEACON_INFO.matches(ev.getType())) { + if (M_BEACON_INFO.matches(eventType)) { return false; } + + if (VoiceBroadcastInfoEventType === eventType) { + // always show voice broadcast info events in timeline + return false; + } + if (ev.isState() && ev.getSender() === createEvent.getSender()) { return true; } diff --git a/src/components/views/messages/MessageEvent.tsx b/src/components/views/messages/MessageEvent.tsx index d2d207eec3..f93cd71017 100644 --- a/src/components/views/messages/MessageEvent.tsx +++ b/src/components/views/messages/MessageEvent.tsx @@ -42,6 +42,7 @@ import MLocationBody from "./MLocationBody"; import MjolnirBody from "./MjolnirBody"; import MBeaconBody from "./MBeaconBody"; import { IEventTileOps } from "../rooms/EventTile"; +import { VoiceBroadcastBody, VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from '../../../voice-broadcast'; // onMessageAllowed is handled internally interface IProps extends Omit { @@ -74,6 +75,7 @@ const baseEvTypes = new Map>>([ [M_POLL_START.altName, MPollBody], [M_BEACON_INFO.name, MBeaconBody], [M_BEACON_INFO.altName, MBeaconBody], + [VoiceBroadcastInfoEventType, VoiceBroadcastBody], ]); export default class MessageEvent extends React.Component implements IMediaBody, IOperableEventTile { @@ -171,6 +173,10 @@ export default class MessageEvent extends React.Component implements IMe ) { BodyType = MLocationBody; } + + if (type === VoiceBroadcastInfoEventType && content?.state === VoiceBroadcastInfoState.Started) { + BodyType = VoiceBroadcastBody; + } } if (SettingsStore.getValue("feature_mjolnir")) { diff --git a/src/components/views/rooms/MessageComposer.tsx b/src/components/views/rooms/MessageComposer.tsx index f9aaf21105..f752386650 100644 --- a/src/components/views/rooms/MessageComposer.tsx +++ b/src/components/views/rooms/MessageComposer.tsx @@ -54,6 +54,11 @@ import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { isLocalRoom } from '../../../utils/localRoom/isLocalRoom'; import { Features } from '../../../settings/Settings'; import { VoiceMessageRecording } from '../../../audio/VoiceMessageRecording'; +import { + VoiceBroadcastInfoEventContent, + VoiceBroadcastInfoEventType, + VoiceBroadcastInfoState, +} from '../../../voice-broadcast'; let instanceCount = 0; @@ -503,12 +508,18 @@ export default class MessageComposer extends React.Component { showStickersButton={this.showStickersButton} toggleButtonMenu={this.toggleButtonMenu} showVoiceBroadcastButton={this.showVoiceBroadcastButton} - onStartVoiceBroadcastClick={() => { - // Sends a voice message. To be replaced by voice broadcast during development. - this.voiceRecordingButton.current?.onRecordStartEndClick(); - if (this.context.narrow) { - this.toggleButtonMenu(); - } + onStartVoiceBroadcastClick={async () => { + const client = MatrixClientPeg.get(); + client.sendStateEvent( + this.props.room.roomId, + VoiceBroadcastInfoEventType, + { + state: VoiceBroadcastInfoState.Started, + chunk_length: 300, + } as VoiceBroadcastInfoEventContent, + client.getUserId(), + ); + this.toggleButtonMenu(); }} /> } { showSendButton && ( diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index 6750343a0d..8c423663f5 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -88,6 +88,7 @@ export function createMessageContent( model = unescapeMessage(model); const body = textSerialize(model); + const content: IContent = { msgtype: isEmote ? "m.emote" : "m.text", body: body, diff --git a/src/events/EventTileFactory.tsx b/src/events/EventTileFactory.tsx index 53b9049c00..d1702aac98 100644 --- a/src/events/EventTileFactory.tsx +++ b/src/events/EventTileFactory.tsx @@ -43,6 +43,7 @@ import { getMessageModerationState, MessageModerationState } from "../utils/Even import HiddenBody from "../components/views/messages/HiddenBody"; import ViewSourceEvent from "../components/views/messages/ViewSourceEvent"; import { shouldDisplayAsBeaconTile } from "../utils/beacon/timeline"; +import { shouldDisplayAsVoiceBroadcastTile } from "../voice-broadcast/utils/shouldDisplayAsVoiceBroadcastTile"; // Subset of EventTile's IProps plus some mixins export interface EventTileTypeProps { @@ -220,6 +221,10 @@ export function pickFactory( return MessageEventFactory; } + if (shouldDisplayAsVoiceBroadcastTile(mxEvent)) { + return MessageEventFactory; + } + if (SINGULAR_STATE_EVENTS.has(evType) && mxEvent.getStateKey() !== '') { return noEventFactoryFactory(); // improper event type to render } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 5c1359d499..9bd1dd8124 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -636,6 +636,7 @@ "Send %(msgtype)s messages as you in your active room": "Send %(msgtype)s messages as you in your active room", "See %(msgtype)s messages posted to this room": "See %(msgtype)s messages posted to this room", "See %(msgtype)s messages posted to your active room": "See %(msgtype)s messages posted to your active room", + "Live": "Live", "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", "Your %(brand)s is misconfigured": "Your %(brand)s is misconfigured", diff --git a/src/voice-broadcast/components/VoiceBroadcastBody.tsx b/src/voice-broadcast/components/VoiceBroadcastBody.tsx new file mode 100644 index 0000000000..40bbbd1768 --- /dev/null +++ b/src/voice-broadcast/components/VoiceBroadcastBody.tsx @@ -0,0 +1,70 @@ +/* +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 { MatrixEvent, RelationType } from "matrix-js-sdk/src/matrix"; + +import { VoiceBroadcastInfoEventType, VoiceBroadcastInfoState, VoiceBroadcastRecordingBody } from ".."; +import { IBodyProps } from "../../components/views/messages/IBodyProps"; +import { MatrixClientPeg } from "../../MatrixClientPeg"; + +/** + * Temporary component to display voice broadcasts. + * XXX: To be refactored to some fancy store/hook/controller architecture. + */ +export const VoiceBroadcastBody: React.FC = ({ + getRelationsForEvent, + mxEvent, +}) => { + const client = MatrixClientPeg.get(); + const relations = getRelationsForEvent?.( + mxEvent.getId(), + RelationType.Reference, + VoiceBroadcastInfoEventType, + ); + const relatedEvents = relations?.getRelations(); + const live = !relatedEvents?.find((event: MatrixEvent) => { + return event.getContent()?.state === VoiceBroadcastInfoState.Stopped; + }); + + const stopVoiceBroadcast = () => { + if (!live) return; + + client.sendStateEvent( + mxEvent.getRoomId(), + VoiceBroadcastInfoEventType, + { + state: VoiceBroadcastInfoState.Stopped, + ["m.relates_to"]: { + rel_type: RelationType.Reference, + event_id: mxEvent.getId(), + }, + }, + client.getUserId(), + ); + }; + + const room = client.getRoom(mxEvent.getRoomId()); + const senderId = mxEvent.getSender(); + const sender = mxEvent.sender; + return ; +}; diff --git a/src/voice-broadcast/components/atoms/LiveBadge.tsx b/src/voice-broadcast/components/atoms/LiveBadge.tsx new file mode 100644 index 0000000000..cd2a16e797 --- /dev/null +++ b/src/voice-broadcast/components/atoms/LiveBadge.tsx @@ -0,0 +1,27 @@ +/* +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 { _t } from "../../../languageHandler"; + +export const LiveBadge: React.FC = () => { + return
+ + { _t("Live") } +
; +}; diff --git a/src/voice-broadcast/components/index.ts b/src/voice-broadcast/components/index.ts new file mode 100644 index 0000000000..e98500a5d7 --- /dev/null +++ b/src/voice-broadcast/components/index.ts @@ -0,0 +1,19 @@ +/* +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. +*/ + +export * from "./atoms/LiveBadge"; +export * from "./molecules/VoiceBroadcastRecordingBody"; +export * from "./VoiceBroadcastBody"; diff --git a/src/voice-broadcast/components/molecules/VoiceBroadcastRecordingBody.tsx b/src/voice-broadcast/components/molecules/VoiceBroadcastRecordingBody.tsx new file mode 100644 index 0000000000..13ea504ac4 --- /dev/null +++ b/src/voice-broadcast/components/molecules/VoiceBroadcastRecordingBody.tsx @@ -0,0 +1,56 @@ +/* +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, { MouseEventHandler } from "react"; +import { RoomMember } from "matrix-js-sdk/src/matrix"; + +import { LiveBadge } from "../.."; +import MemberAvatar from "../../../components/views/avatars/MemberAvatar"; + +interface VoiceBroadcastRecordingBodyProps { + live: boolean; + member: RoomMember; + onClick: MouseEventHandler; + title: string; + userId: string; +} + +export const VoiceBroadcastRecordingBody: React.FC = ({ + live, + member, + onClick, + title, + userId, +}) => { + const liveBadge = live + ? + : null; + + return ( +
+ +
+
+ { title } +
+
+ { liveBadge } +
+ ); +}; diff --git a/src/voice-broadcast/index.ts b/src/voice-broadcast/index.ts index 473bbf73ef..8f6312a775 100644 --- a/src/voice-broadcast/index.ts +++ b/src/voice-broadcast/index.ts @@ -14,4 +14,30 @@ See the License for the specific language governing permissions and limitations under the License. */ +/** + * Voice Broadcast module + * {@link https://github.com/vector-im/element-meta/discussions/632} + */ + +import { RelationType } from "matrix-js-sdk/src/matrix"; + +export * from "./components"; +export * from "./utils"; + export const VoiceBroadcastInfoEventType = "io.element.voice_broadcast_info"; + +export enum VoiceBroadcastInfoState { + Started = "started", + Paused = "paused", + Running = "running", + Stopped = "stopped", +} + +export interface VoiceBroadcastInfoEventContent { + state: VoiceBroadcastInfoState; + chunk_length: number; + ["m.relates_to"]?: { + rel_type: RelationType; + event_id: string; + }; +} diff --git a/src/voice-broadcast/utils/index.ts b/src/voice-broadcast/utils/index.ts new file mode 100644 index 0000000000..9fb93c73b1 --- /dev/null +++ b/src/voice-broadcast/utils/index.ts @@ -0,0 +1,17 @@ +/* +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. +*/ + +export * from "./shouldDisplayAsVoiceBroadcastTile"; diff --git a/src/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastTile.ts b/src/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastTile.ts new file mode 100644 index 0000000000..ef55eed3bb --- /dev/null +++ b/src/voice-broadcast/utils/shouldDisplayAsVoiceBroadcastTile.ts @@ -0,0 +1,27 @@ +/* +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 { MatrixEvent } from "matrix-js-sdk/src/matrix"; + +import { VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from ".."; + +export const shouldDisplayAsVoiceBroadcastTile = (event: MatrixEvent) => ( + event.getType?.() === VoiceBroadcastInfoEventType + && ( + event.getContent?.()?.state === VoiceBroadcastInfoState.Started + || event.isRedacted() + ) +); diff --git a/test/components/atoms/Icon-test.tsx b/test/components/atoms/Icon-test.tsx new file mode 100644 index 0000000000..57e6e3990c --- /dev/null +++ b/test/components/atoms/Icon-test.tsx @@ -0,0 +1,47 @@ +/* +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 } from "@testing-library/react"; + +import { Icon, IconColour, IconSize, IconType } from "../../../src/components/atoms/Icon"; + +describe("Icon", () => { + it.each([ + IconColour.Accent, + IconColour.LiveBadge, + ])("should render the colour %s", (colour: IconColour) => { + const { container } = render( + , + ); + expect(container).toMatchSnapshot(); + }); + + it.each([ + IconSize.S16, + ])("should render the size %s", (size: IconSize) => { + const { container } = render( + , + ); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/test/components/atoms/__snapshots__/Icon-test.tsx.snap b/test/components/atoms/__snapshots__/Icon-test.tsx.snap new file mode 100644 index 0000000000..c30b4ba332 --- /dev/null +++ b/test/components/atoms/__snapshots__/Icon-test.tsx.snap @@ -0,0 +1,34 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Icon should render the colour accent 1`] = ` +
+