Hook up CallEventGrouper

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
Šimon Brandner 2021-05-30 16:28:30 +02:00
parent 8dc0e2a7ab
commit 85bcf8ed52
No known key found for this signature in database
GPG key ID: 9760693FDD98A790
4 changed files with 29 additions and 140 deletions

View file

@ -26,7 +26,6 @@ import * as sdk from '../../index';
import {MatrixClientPeg} from '../../MatrixClientPeg'; import {MatrixClientPeg} from '../../MatrixClientPeg';
import SettingsStore from '../../settings/SettingsStore'; import SettingsStore from '../../settings/SettingsStore';
import TimelineCallEventStore from "../../stores/TimelineCallEventStore";
import {Layout, LayoutPropType} from "../../settings/Layout"; import {Layout, LayoutPropType} from "../../settings/Layout";
import {_t} from "../../languageHandler"; import {_t} from "../../languageHandler";
import {haveTileForEvent} from "../views/rooms/EventTile"; import {haveTileForEvent} from "../views/rooms/EventTile";
@ -36,6 +35,7 @@ import DMRoomMap from "../../utils/DMRoomMap";
import NewRoomIntro from "../views/rooms/NewRoomIntro"; import NewRoomIntro from "../views/rooms/NewRoomIntro";
import {replaceableComponent} from "../../utils/replaceableComponent"; import {replaceableComponent} from "../../utils/replaceableComponent";
import defaultDispatcher from '../../dispatcher/dispatcher'; import defaultDispatcher from '../../dispatcher/dispatcher';
import CallEventGrouper from "./CallEventGrouper";
const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes
const continuedTypes = ['m.sticker', 'm.room.message']; const continuedTypes = ['m.sticker', 'm.room.message'];
@ -210,6 +210,9 @@ export default class MessagePanel extends React.Component {
this._showTypingNotificationsWatcherRef = this._showTypingNotificationsWatcherRef =
SettingsStore.watchSetting("showTypingNotifications", null, this.onShowTypingNotificationsChange); SettingsStore.watchSetting("showTypingNotifications", null, this.onShowTypingNotificationsChange);
// A map of <callId, CallEventGrouper>
this._callEventGroupers = new Map();
} }
componentDidMount() { componentDidMount() {
@ -530,7 +533,20 @@ export default class MessagePanel extends React.Component {
const last = (mxEv === lastShownEvent); const last = (mxEv === lastShownEvent);
const {nextEvent, nextTile} = this._getNextEventInfo(this.props.events, i); 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) {
if (grouper.shouldGroup(mxEv)) { if (grouper.shouldGroup(mxEv)) {
@ -646,6 +662,8 @@ export default class MessagePanel extends React.Component {
// it's successful: we received it. // it's successful: we received it.
isLastSuccessful = isLastSuccessful && mxEv.getSender() === MatrixClientPeg.get().getUserId(); 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 // use txnId as key if available so that we don't remount during sending
ret.push( ret.push(
<li <li
@ -678,6 +696,7 @@ export default class MessagePanel extends React.Component {
layout={this.props.layout} layout={this.props.layout}
enableFlair={this.props.enableFlair} enableFlair={this.props.enableFlair}
showReadReceipts={this.props.showReadReceipts} showReadReceipts={this.props.showReadReceipts}
callState={callState}
/> />
</TileErrorBoundary> </TileErrorBoundary>
</li>, </li>,

View file

@ -18,62 +18,18 @@ import React from 'react';
import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import TimelineCallEventStore, {
TimelineCall as TimelineCallSt,
TimelineCallEventStoreEvent,
TimelineCallState,
} from "../../../stores/TimelineCallEventStore";
import MemberAvatar from '../avatars/MemberAvatar'; import MemberAvatar from '../avatars/MemberAvatar';
import { TimelineCallState } from '../../structures/CallEventGrouper';
interface IProps { interface IProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
}
interface IState {
callState: TimelineCallState; callState: TimelineCallState;
} }
export default class CallEvent extends React.Component<IProps, IState> { export default class CallEvent extends React.Component<IProps> {
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<string, TimelineCallSt>) => {
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;
}
render() { render() {
const event = this.props.mxEvent; const event = this.props.mxEvent;
const sender = event.sender ? event.sender.name : event.getSender(); const sender = event.sender ? event.sender.name : event.getSender();
const state = this.state.callState;
return ( return (
<div className="mx_CallEvent"> <div className="mx_CallEvent">
@ -87,8 +43,7 @@ export default class CallEvent extends React.Component<IProps, IState> {
{sender} {sender}
</div> </div>
<div className="mx_CallEvent_type"> <div className="mx_CallEvent_type">
{ this.isVoice() ? _t("Voice call") : _t("Video call") } { this.props.callState.isVoice ? _t("Voice call") : _t("Video call") }
{ state ? state : TimelineCallState.Unknown }
</div> </div>
</div> </div>
</div> </div>

View file

@ -46,6 +46,7 @@ import { EditorStateTransfer } from "../../../utils/EditorStateTransfer";
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks'; import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState"; import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState";
import NotificationBadge from "./NotificationBadge"; import NotificationBadge from "./NotificationBadge";
import { TimelineCallState } from "../../structures/CallEventGrouper";
const eventTileTypes = { const eventTileTypes = {
[EventType.RoomMessage]: 'messages.MessageEvent', [EventType.RoomMessage]: 'messages.MessageEvent',
@ -274,6 +275,9 @@ interface IProps {
// Helper to build permalinks for the room // Helper to build permalinks for the room
permalinkCreator?: RoomPermalinkCreator; permalinkCreator?: RoomPermalinkCreator;
// CallEventGrouper for this event
callState?: TimelineCallState;
} }
interface IState { interface IState {
@ -1139,6 +1143,7 @@ export default class EventTile extends React.Component<IProps, IState> {
showUrlPreview={this.props.showUrlPreview} showUrlPreview={this.props.showUrlPreview}
permalinkCreator={this.props.permalinkCreator} permalinkCreator={this.props.permalinkCreator}
onHeightChanged={this.props.onHeightChanged} onHeightChanged={this.props.onHeightChanged}
callState={this.props.callState}
/> />
{ keyRequestInfo } { keyRequestInfo }
{ reactionsRow } { reactionsRow }

View file

@ -1,90 +0,0 @@
/*
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
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<string, TimelineCall> = 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)
}
}