diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index e2bb3135cf..ab5fe01e47 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -26,7 +26,6 @@ import * as sdk from '../../index'; import {MatrixClientPeg} from '../../MatrixClientPeg'; import SettingsStore from '../../settings/SettingsStore'; -import TimelineCallEventStore from "../../stores/TimelineCallEventStore"; import {Layout, LayoutPropType} from "../../settings/Layout"; import {_t} from "../../languageHandler"; import {haveTileForEvent} from "../views/rooms/EventTile"; @@ -36,6 +35,7 @@ import DMRoomMap from "../../utils/DMRoomMap"; import NewRoomIntro from "../views/rooms/NewRoomIntro"; import {replaceableComponent} from "../../utils/replaceableComponent"; import defaultDispatcher from '../../dispatcher/dispatcher'; +import CallEventGrouper from "./CallEventGrouper"; const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes const continuedTypes = ['m.sticker', 'm.room.message']; @@ -210,6 +210,9 @@ export default class MessagePanel extends React.Component { this._showTypingNotificationsWatcherRef = SettingsStore.watchSetting("showTypingNotifications", null, this.onShowTypingNotificationsChange); + + // A map of + this._callEventGroupers = new Map(); } componentDidMount() { @@ -530,7 +533,20 @@ export default class MessagePanel extends React.Component { const last = (mxEv === lastShownEvent); const {nextEvent, nextTile} = this._getNextEventInfo(this.props.events, i); - TimelineCallEventStore.instance.addEvent(mxEv); + if ( + mxEv.getType().indexOf("m.call.") === 0 || + mxEv.getType().indexOf("org.matrix.call.") === 0 + ) { + const callId = mxEv.getContent().call_id; + if (this._callEventGroupers.has(callId)) { + this._callEventGroupers.get(callId).add(mxEv); + } else { + const callEventGrouper = new CallEventGrouper(); + callEventGrouper.add(mxEv); + + this._callEventGroupers.set(callId, callEventGrouper); + } + } if (grouper) { if (grouper.shouldGroup(mxEv)) { @@ -646,6 +662,8 @@ export default class MessagePanel extends React.Component { // it's successful: we received it. isLastSuccessful = isLastSuccessful && mxEv.getSender() === MatrixClientPeg.get().getUserId(); + const callState = this._callEventGroupers.get(mxEv.getContent().call_id)?.getState(); + // use txnId as key if available so that we don't remount during sending ret.push(
  • , diff --git a/src/components/views/messages/CallEvent.tsx b/src/components/views/messages/CallEvent.tsx index e8e6642776..182645c048 100644 --- a/src/components/views/messages/CallEvent.tsx +++ b/src/components/views/messages/CallEvent.tsx @@ -18,62 +18,18 @@ import React from 'react'; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { _t } from '../../../languageHandler'; -import TimelineCallEventStore, { - TimelineCall as TimelineCallSt, - TimelineCallEventStoreEvent, - TimelineCallState, -} from "../../../stores/TimelineCallEventStore"; import MemberAvatar from '../avatars/MemberAvatar'; +import { TimelineCallState } from '../../structures/CallEventGrouper'; interface IProps { mxEvent: MatrixEvent; -} - -interface IState { callState: TimelineCallState; } -export default class CallEvent extends React.Component { - constructor(props: IProps) { - super(props); - - this.state = { - callState: null, - } - } - - componentDidMount() { - TimelineCallEventStore.instance.addListener(TimelineCallEventStoreEvent.CallsChanged, this.onCallsChanged); - } - - componentWillUnmount() { - TimelineCallEventStore.instance.removeListener(TimelineCallEventStoreEvent.CallsChanged, this.onCallsChanged); - } - - private onCallsChanged = (calls: Map) => { - const callId = this.props.mxEvent.getContent().call_id; - const call = calls.get(callId); - if (!call) return; - this.setState({callState: call.state}); - } - - private isVoice(): boolean { - const event = this.props.mxEvent; - - // FIXME: Find a better way to determine this from the event? - let isVoice = true; - if (event.getContent().offer && event.getContent().offer.sdp && - event.getContent().offer.sdp.indexOf('m=video') !== -1) { - isVoice = false; - } - - return isVoice; - } - +export default class CallEvent extends React.Component { render() { const event = this.props.mxEvent; const sender = event.sender ? event.sender.name : event.getSender(); - const state = this.state.callState; return (
    @@ -87,8 +43,7 @@ export default class CallEvent extends React.Component { {sender}
    - { this.isVoice() ? _t("Voice call") : _t("Video call") } - { state ? state : TimelineCallState.Unknown } + { this.props.callState.isVoice ? _t("Voice call") : _t("Video call") }
    diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 71a7e39eba..eb76354975 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -46,6 +46,7 @@ import { EditorStateTransfer } from "../../../utils/EditorStateTransfer"; import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks'; import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState"; import NotificationBadge from "./NotificationBadge"; +import { TimelineCallState } from "../../structures/CallEventGrouper"; const eventTileTypes = { [EventType.RoomMessage]: 'messages.MessageEvent', @@ -274,6 +275,9 @@ interface IProps { // Helper to build permalinks for the room permalinkCreator?: RoomPermalinkCreator; + + // CallEventGrouper for this event + callState?: TimelineCallState; } interface IState { @@ -1139,6 +1143,7 @@ export default class EventTile extends React.Component { showUrlPreview={this.props.showUrlPreview} permalinkCreator={this.props.permalinkCreator} onHeightChanged={this.props.onHeightChanged} + callState={this.props.callState} /> { keyRequestInfo } { reactionsRow } diff --git a/src/stores/TimelineCallEventStore.ts b/src/stores/TimelineCallEventStore.ts deleted file mode 100644 index 4689183adf..0000000000 --- a/src/stores/TimelineCallEventStore.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* -Copyright 2021 Šimon Brandner - -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 EventEmitter from "events"; -import { EventType } from "matrix-js-sdk/src/@types/event"; -import { MatrixEvent } from "matrix-js-sdk/src/models/event"; - -export enum TimelineCallEventStoreEvent { - CallsChanged = "calls_changed" -} - -export enum TimelineCallState { - Invite = "invited", - Answered = "answered", - Ended = "ended", - Rejected = "rejected", - Unknown = "unknown" -} - -const EVENT_TYPE_TO_TIMELINE_CALL_STATE = new Map([ - [EventType.CallInvite, TimelineCallState.Invite], - [EventType.CallSelectAnswer, TimelineCallState.Answered], - [EventType.CallHangup, TimelineCallState.Ended], - [EventType.CallReject, TimelineCallState.Rejected], -]); - -export interface TimelineCall { - state: TimelineCallState; - date: Date; -} - -/** - * This gathers call events and creates objects for them accordingly, these can then be retrieved by CallEvent - */ -export default class TimelineCallEventStore extends EventEmitter { - private calls: Map = new Map(); - private static internalInstance: TimelineCallEventStore; - - public static get instance(): TimelineCallEventStore { - if (!TimelineCallEventStore.internalInstance) { - TimelineCallEventStore.internalInstance = new TimelineCallEventStore; - } - - return TimelineCallEventStore.internalInstance; - } - - public clear() { - this.calls.clear(); - } - - public getInfoByCallId(callId: string): TimelineCall { - return this.calls.get(callId); - } - - private getCallState(type: EventType): TimelineCallState { - return EVENT_TYPE_TO_TIMELINE_CALL_STATE.get(type); - } - - public addEvent(event: MatrixEvent) { - if (!Array.from(EVENT_TYPE_TO_TIMELINE_CALL_STATE.keys()).includes(event.getType())) return; - - const callId = event.getContent().call_id; - const date = event.getDate(); - const state = this.getCallState(event.getType()); - - - if (date < this.calls.get(callId)?.date) return; - if (!state) return; - - this.calls.set(callId, { - state: state, - date: date, - }); - - this.emit(TimelineCallEventStoreEvent.CallsChanged, this.calls) - } -}