MM-54865 - Calls: Multisession support (#7647)
* add multisession support for calls mobile * ensure backwards compatibility * remove extra line
This commit is contained in:
parent
63857a34d9
commit
c9fe72460b
21 changed files with 514 additions and 328 deletions
|
|
@ -4,7 +4,12 @@
|
|||
import {markChannelAsViewed} from '@actions/local/channel';
|
||||
import {dataRetentionCleanup} from '@actions/local/systems';
|
||||
import {markChannelAsRead} from '@actions/remote/channel';
|
||||
import {deferredAppEntryActions, entry, handleEntryAfterLoadNavigation, registerDeviceToken} from '@actions/remote/entry/common';
|
||||
import {
|
||||
deferredAppEntryActions,
|
||||
entry,
|
||||
handleEntryAfterLoadNavigation,
|
||||
registerDeviceToken,
|
||||
} from '@actions/remote/entry/common';
|
||||
import {fetchPostsForChannel, fetchPostThread} from '@actions/remote/post';
|
||||
import {openAllUnreadChannels} from '@actions/remote/preference';
|
||||
import {autoUpdateTimezone} from '@actions/remote/user';
|
||||
|
|
@ -17,9 +22,9 @@ import {
|
|||
handleCallRecordingState,
|
||||
handleCallScreenOff,
|
||||
handleCallScreenOn,
|
||||
handleCallStarted,
|
||||
handleCallUserConnected,
|
||||
handleCallUserDisconnected,
|
||||
handleCallStarted, handleCallUserConnected, handleCallUserDisconnected,
|
||||
handleCallUserJoined,
|
||||
handleCallUserLeft,
|
||||
handleCallUserMuted,
|
||||
handleCallUserRaiseHand,
|
||||
handleCallUserReacted,
|
||||
|
|
@ -51,8 +56,14 @@ import {setTeamLoading} from '@store/team_load_store';
|
|||
import {isTablet} from '@utils/helpers';
|
||||
import {logDebug, logInfo} from '@utils/log';
|
||||
|
||||
import {handleCategoryCreatedEvent, handleCategoryDeletedEvent, handleCategoryOrderUpdatedEvent, handleCategoryUpdatedEvent} from './category';
|
||||
import {handleChannelConvertedEvent, handleChannelCreatedEvent,
|
||||
import {
|
||||
handleCategoryCreatedEvent,
|
||||
handleCategoryDeletedEvent,
|
||||
handleCategoryOrderUpdatedEvent,
|
||||
handleCategoryUpdatedEvent,
|
||||
} from './category';
|
||||
import {
|
||||
handleChannelConvertedEvent, handleChannelCreatedEvent,
|
||||
handleChannelDeletedEvent,
|
||||
handleChannelMemberUpdatedEvent,
|
||||
handleChannelUnarchiveEvent,
|
||||
|
|
@ -61,15 +72,39 @@ import {handleChannelConvertedEvent, handleChannelCreatedEvent,
|
|||
handleMultipleChannelsViewedEvent,
|
||||
handleDirectAddedEvent,
|
||||
handleUserAddedToChannelEvent,
|
||||
handleUserRemovedFromChannelEvent} from './channel';
|
||||
import {handleGroupMemberAddEvent, handleGroupMemberDeleteEvent, handleGroupReceivedEvent, handleGroupTeamAssociatedEvent, handleGroupTeamDissociateEvent} from './group';
|
||||
handleUserRemovedFromChannelEvent,
|
||||
} from './channel';
|
||||
import {
|
||||
handleGroupMemberAddEvent,
|
||||
handleGroupMemberDeleteEvent,
|
||||
handleGroupReceivedEvent,
|
||||
handleGroupTeamAssociatedEvent,
|
||||
handleGroupTeamDissociateEvent,
|
||||
} from './group';
|
||||
import {handleOpenDialogEvent} from './integrations';
|
||||
import {handleNewPostEvent, handlePostAcknowledgementAdded, handlePostAcknowledgementRemoved, handlePostDeleted, handlePostEdited, handlePostUnread} from './posts';
|
||||
import {handlePreferenceChangedEvent, handlePreferencesChangedEvent, handlePreferencesDeletedEvent} from './preferences';
|
||||
import {
|
||||
handleNewPostEvent,
|
||||
handlePostAcknowledgementAdded,
|
||||
handlePostAcknowledgementRemoved,
|
||||
handlePostDeleted,
|
||||
handlePostEdited,
|
||||
handlePostUnread,
|
||||
} from './posts';
|
||||
import {
|
||||
handlePreferenceChangedEvent,
|
||||
handlePreferencesChangedEvent,
|
||||
handlePreferencesDeletedEvent,
|
||||
} from './preferences';
|
||||
import {handleAddCustomEmoji, handleReactionRemovedFromPostEvent, handleReactionAddedToPostEvent} from './reactions';
|
||||
import {handleUserRoleUpdatedEvent, handleTeamMemberRoleUpdatedEvent, handleRoleUpdatedEvent} from './roles';
|
||||
import {handleLicenseChangedEvent, handleConfigChangedEvent} from './system';
|
||||
import {handleLeaveTeamEvent, handleUserAddedToTeamEvent, handleUpdateTeamEvent, handleTeamArchived, handleTeamRestored} from './teams';
|
||||
import {
|
||||
handleLeaveTeamEvent,
|
||||
handleUserAddedToTeamEvent,
|
||||
handleUpdateTeamEvent,
|
||||
handleTeamArchived,
|
||||
handleTeamRestored,
|
||||
} from './teams';
|
||||
import {handleThreadUpdatedEvent, handleThreadReadChangedEvent, handleThreadFollowChangedEvent} from './threads';
|
||||
import {handleUserUpdatedEvent, handleUserTypingEvent, handleStatusChangedEvent} from './users';
|
||||
|
||||
|
|
@ -347,12 +382,23 @@ export async function handleEvent(serverUrl: string, msg: WebSocketMessage) {
|
|||
case WebsocketEvents.CALLS_CHANNEL_DISABLED:
|
||||
handleCallChannelDisabled(serverUrl, msg);
|
||||
break;
|
||||
|
||||
// DEPRECATED in favour of user_joined (since v0.21.0)
|
||||
case WebsocketEvents.CALLS_USER_CONNECTED:
|
||||
handleCallUserConnected(serverUrl, msg);
|
||||
break;
|
||||
|
||||
// DEPRECATED in favour of user_left (since v0.21.0)
|
||||
case WebsocketEvents.CALLS_USER_DISCONNECTED:
|
||||
handleCallUserDisconnected(serverUrl, msg);
|
||||
break;
|
||||
|
||||
case WebsocketEvents.CALLS_USER_JOINED:
|
||||
handleCallUserJoined(serverUrl, msg);
|
||||
break;
|
||||
case WebsocketEvents.CALLS_USER_LEFT:
|
||||
handleCallUserLeft(serverUrl, msg);
|
||||
break;
|
||||
case WebsocketEvents.CALLS_USER_MUTED:
|
||||
handleCallUserMuted(serverUrl, msg);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,13 @@ const RequiredServer = {
|
|||
PATCH_VERSION: 0,
|
||||
};
|
||||
|
||||
const MultiSessionCallsVersion = {
|
||||
FULL_VERSION: '0.21.0',
|
||||
MAJOR_VERSION: 0,
|
||||
MIN_VERSION: 21,
|
||||
PATCH_VERSION: 0,
|
||||
};
|
||||
|
||||
const PluginId = 'com.mattermost.calls';
|
||||
|
||||
const REACTION_TIMEOUT = 10000;
|
||||
|
|
@ -26,6 +33,7 @@ export enum MessageBarType {
|
|||
export default {
|
||||
RefreshConfigMillis,
|
||||
RequiredServer,
|
||||
MultiSessionCallsVersion,
|
||||
PluginId,
|
||||
REACTION_TIMEOUT,
|
||||
REACTION_LIMIT,
|
||||
|
|
|
|||
|
|
@ -61,8 +61,15 @@ const WebsocketEvents = {
|
|||
APPS_FRAMEWORK_REFRESH_BINDINGS: 'custom_com.mattermost.apps_refresh_bindings',
|
||||
CALLS_CHANNEL_ENABLED: `custom_${Calls.PluginId}_channel_enable_voice`,
|
||||
CALLS_CHANNEL_DISABLED: `custom_${Calls.PluginId}_channel_disable_voice`,
|
||||
|
||||
// DEPRECATED in favour of user_joined (since v0.21.0)
|
||||
CALLS_USER_CONNECTED: `custom_${Calls.PluginId}_user_connected`,
|
||||
|
||||
// DEPRECATED in favour of user_left (since v0.21.0)
|
||||
CALLS_USER_DISCONNECTED: `custom_${Calls.PluginId}_user_disconnected`,
|
||||
|
||||
CALLS_USER_JOINED: `custom_${Calls.PluginId}_user_joined`,
|
||||
CALLS_USER_LEFT: `custom_${Calls.PluginId}_user_left`,
|
||||
CALLS_USER_MUTED: `custom_${Calls.PluginId}_user_muted`,
|
||||
CALLS_USER_UNMUTED: `custom_${Calls.PluginId}_user_unmuted`,
|
||||
CALLS_USER_VOICE_ON: `custom_${Calls.PluginId}_user_voice_on`,
|
||||
|
|
|
|||
|
|
@ -39,10 +39,9 @@ const mockClient = {
|
|||
getCalls: jest.fn(() => [
|
||||
{
|
||||
call: {
|
||||
users: ['user-1', 'user-2'],
|
||||
states: {
|
||||
'user-1': {unmuted: true},
|
||||
'user-2': {unmuted: false},
|
||||
sessions: {
|
||||
session1: {session_id: 'session1', user_id: 'user-1', unmuted: true},
|
||||
session2: {session_id: 'session1', user_id: 'user-1', unmuted: false},
|
||||
},
|
||||
start_at: 123,
|
||||
screen_sharing_id: '',
|
||||
|
|
@ -58,6 +57,7 @@ const mockClient = {
|
|||
DefaultEnabled: true,
|
||||
last_retrieved_at: 1234,
|
||||
})),
|
||||
getVersion: jest.fn(() => ({})),
|
||||
getPluginsManifests: jest.fn(() => (
|
||||
[
|
||||
{id: 'playbooks'},
|
||||
|
|
@ -101,13 +101,13 @@ jest.mock('react-native-navigation', () => ({
|
|||
const addFakeCall = (serverUrl: string, channelId: string) => {
|
||||
const call: Call = {
|
||||
id: 'call',
|
||||
participants: {
|
||||
xohi8cki9787fgiryne716u84o: {id: 'xohi8cki9787fgiryne716u84o', muted: false, raisedHand: 0},
|
||||
xohi8cki9787fgiryne716u841: {id: 'xohi8cki9787fgiryne716u84o', muted: true, raisedHand: 0},
|
||||
xohi8cki9787fgiryne716u842: {id: 'xohi8cki9787fgiryne716u84o', muted: false, raisedHand: 0},
|
||||
xohi8cki9787fgiryne716u843: {id: 'xohi8cki9787fgiryne716u84o', muted: true, raisedHand: 0},
|
||||
xohi8cki9787fgiryne716u844: {id: 'xohi8cki9787fgiryne716u84o', muted: false, raisedHand: 0},
|
||||
xohi8cki9787fgiryne716u845: {id: 'xohi8cki9787fgiryne716u84o', muted: true, raisedHand: 0},
|
||||
sessions: {
|
||||
a23456abcdefghijklmnopqrs: {sessionId: 'a23456abcdefghijklmnopqrs', userId: 'xohi8cki9787fgiryne716u84o', muted: false, raisedHand: 0},
|
||||
a12345667890bcdefghijklmn1: {sessionId: 'a12345667890bcdefghijklmn1', userId: 'xohi8cki9787fgiryne716u84o', muted: true, raisedHand: 0},
|
||||
a12345667890bcdefghijklmn2: {sessionId: 'a12345667890bcdefghijklmn2', userId: 'xohi8cki9787fgiryne716u84o', muted: false, raisedHand: 0},
|
||||
a12345667890bcdefghijklmn3: {sessionId: 'a12345667890bcdefghijklmn3', userId: 'xohi8cki9787fgiryne716u84o', muted: true, raisedHand: 0},
|
||||
a12345667890bcdefghijklmn4: {sessionId: 'a12345667890bcdefghijklmn4', userId: 'xohi8cki9787fgiryne716u84o', muted: false, raisedHand: 0},
|
||||
a12345667890bcdefghijklmn5: {sessionId: 'a12345667890bcdefghijklmn5', userId: 'xohi8cki9787fgiryne716u84o', muted: true, raisedHand: 0},
|
||||
},
|
||||
channelId,
|
||||
startTime: (new Date()).getTime(),
|
||||
|
|
@ -205,7 +205,7 @@ describe('Actions.Calls', () => {
|
|||
|
||||
// manually call newCurrentConnection because newConnection is mocked
|
||||
newCurrentCall('server1', 'channel-id', 'myUserId');
|
||||
userJoinedCall('server1', 'channel-id', 'myUserId');
|
||||
userJoinedCall('server1', 'channel-id', 'myUserId', 'mySessionId');
|
||||
});
|
||||
assert.equal(response!.data, 'channel-id');
|
||||
assert.equal((result.current[1] as CurrentCall | null)?.channelId, 'channel-id');
|
||||
|
|
@ -239,7 +239,7 @@ describe('Actions.Calls', () => {
|
|||
|
||||
// manually call newCurrentConnection because newConnection is mocked
|
||||
newCurrentCall('server1', 'channel-id', 'myUserId');
|
||||
userJoinedCall('server1', 'channel-id', 'myUserId');
|
||||
userJoinedCall('server1', 'channel-id', 'myUserId', 'mySessionId');
|
||||
});
|
||||
assert.equal(response!.data, 'channel-id');
|
||||
assert.equal((result.current[1] as CurrentCall | null)?.channelId, 'channel-id');
|
||||
|
|
@ -269,7 +269,7 @@ describe('Actions.Calls', () => {
|
|||
|
||||
// manually call newCurrentConnection because newConnection is mocked
|
||||
newCurrentCall('server1', 'channel-id', 'myUserId');
|
||||
userJoinedCall('server1', 'channel-id', 'myUserId');
|
||||
userJoinedCall('server1', 'channel-id', 'myUserId', 'mySessionId');
|
||||
});
|
||||
assert.equal(response!.data, 'channel-id');
|
||||
assert.equal((result.current[1] as CurrentCall | null)?.channelId, 'channel-id');
|
||||
|
|
|
|||
|
|
@ -39,8 +39,8 @@ import {displayUsername, getUserIdFromChannelName, isSystemAdmin} from '@utils/u
|
|||
|
||||
import {newConnection} from '../connection/connection';
|
||||
|
||||
import type {AudioDevice, Call, CallParticipant, CallsConnection} from '@calls/types/calls';
|
||||
import type {CallChannelState, CallState, EmojiData} from '@mattermost/calls/lib/types';
|
||||
import type {AudioDevice, Call, CallSession, CallsConnection} from '@calls/types/calls';
|
||||
import type {CallChannelState, CallState, EmojiData, SessionState} from '@mattermost/calls/lib/types';
|
||||
import type {IntlShape} from 'react-intl';
|
||||
|
||||
let connection: CallsConnection | null = null;
|
||||
|
|
@ -59,8 +59,8 @@ export const loadConfig = async (serverUrl: string, force = false) => {
|
|||
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
const data = await client.getCallsConfig();
|
||||
const nextConfig = {...data, last_retrieved_at: now};
|
||||
const configs = await Promise.all([client.getCallsConfig(), client.getVersion()]);
|
||||
const nextConfig = {...configs[0], version: configs[1], last_retrieved_at: now};
|
||||
setConfig(serverUrl, nextConfig);
|
||||
return {data: nextConfig};
|
||||
} catch (error) {
|
||||
|
|
@ -87,7 +87,7 @@ export const loadCalls = async (serverUrl: string, userId: string) => {
|
|||
|
||||
for (const channel of resp) {
|
||||
if (channel.call) {
|
||||
callsResults[channel.channel_id] = createCallAndAddToIds(channel.channel_id, channel.call, ids);
|
||||
callsResults[channel.channel_id] = createCallAndAddToIds(channel.channel_id, convertOldCallToNew(channel.call), ids);
|
||||
}
|
||||
|
||||
if (typeof channel.enabled !== 'undefined') {
|
||||
|
|
@ -119,7 +119,7 @@ export const loadCallForChannel = async (serverUrl: string, channelId: string) =
|
|||
let call: Call | undefined;
|
||||
const ids = new Set<string>();
|
||||
if (resp.call) {
|
||||
call = createCallAndAddToIds(channelId, resp.call, ids);
|
||||
call = createCallAndAddToIds(channelId, convertOldCallToNew(resp.call), ids);
|
||||
}
|
||||
|
||||
// Batch load user models async because we'll need them later
|
||||
|
|
@ -132,22 +132,48 @@ export const loadCallForChannel = async (serverUrl: string, channelId: string) =
|
|||
return {data: {call, enabled: resp.enabled}};
|
||||
};
|
||||
|
||||
// Converts pre-0.21.0 call to 0.21.0+ call. Can be removed when we stop supporting pre-0.21.0
|
||||
// Also can be removed: all code prefaced with a "Pre v0.21.0, sessionID == userID" comment
|
||||
// Does nothing if the call is in the new format.
|
||||
const convertOldCallToNew = (call: CallState): CallState => {
|
||||
if (call.sessions) {
|
||||
return call;
|
||||
}
|
||||
|
||||
return {
|
||||
...call,
|
||||
sessions: call.users.reduce((accum, cur, curIdx) => {
|
||||
accum.push({
|
||||
session_id: cur,
|
||||
user_id: cur,
|
||||
unmuted: call.states && call.states[curIdx] ? call.states[curIdx].unmuted : false,
|
||||
raised_hand: call.states && call.states[curIdx] ? call.states[curIdx].raised_hand : 0,
|
||||
});
|
||||
return accum;
|
||||
}, [] as SessionState[]),
|
||||
screen_sharing_session_id: call.screen_sharing_id,
|
||||
};
|
||||
};
|
||||
|
||||
const createCallAndAddToIds = (channelId: string, call: CallState, ids: Set<string>) => {
|
||||
return {
|
||||
participants: call.users.reduce((accum, cur, curIdx) => {
|
||||
sessions: Object.values(call.sessions).reduce((accum, cur) => {
|
||||
// Add the id to the set of UserModels we want to ensure are loaded.
|
||||
ids.add(cur);
|
||||
ids.add(cur.user_id);
|
||||
|
||||
// Create the CallParticipant
|
||||
const muted = call.states && call.states[curIdx] ? !call.states[curIdx].unmuted : true;
|
||||
const raisedHand = call.states && call.states[curIdx] ? call.states[curIdx].raised_hand : 0;
|
||||
accum[cur] = {id: cur, muted, raisedHand};
|
||||
accum[cur.session_id] = {
|
||||
userId: cur.user_id,
|
||||
sessionId: cur.session_id,
|
||||
raisedHand: cur.raised_hand || 0,
|
||||
muted: !cur.unmuted,
|
||||
};
|
||||
return accum;
|
||||
}, {} as Dictionary<CallParticipant>),
|
||||
}, {} as Dictionary<CallSession>),
|
||||
channelId,
|
||||
id: call.id,
|
||||
startTime: call.start_at,
|
||||
screenOn: call.screen_sharing_id,
|
||||
screenOn: call.screen_sharing_session_id,
|
||||
threadId: call.thread_id,
|
||||
ownerId: call.owner_id,
|
||||
hostId: call.host_id,
|
||||
|
|
@ -351,12 +377,12 @@ export const getEndCallMessage = async (serverUrl: string, channelId: string, cu
|
|||
return msg;
|
||||
}
|
||||
|
||||
const numParticipants = Object.keys(call.participants).length;
|
||||
const numSessions = Object.keys(call.sessions).length;
|
||||
|
||||
msg = intl.formatMessage({
|
||||
id: 'mobile.calls_end_msg_channel',
|
||||
defaultMessage: 'Are you sure you want to end a call with {numParticipants} participants in {displayName}?',
|
||||
}, {numParticipants, displayName: channel.displayName});
|
||||
defaultMessage: 'Are you sure you want to end a call with {numSessions} participants in {displayName}?',
|
||||
}, {numSessions, displayName: channel.displayName});
|
||||
|
||||
if (channel.type === General.DM_CHANNEL) {
|
||||
const otherID = getUserIdFromChannelName(currentUserId, channel.name);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {ApiResp} from '@calls/types/calls';
|
||||
import type {ApiResp, CallsVersion} from '@calls/types/calls';
|
||||
import type {CallChannelState, CallRecordingState, CallsConfig} from '@mattermost/calls/lib/types';
|
||||
import type {RTCIceServer} from 'react-native-webrtc';
|
||||
|
||||
|
|
@ -10,6 +10,7 @@ export interface ClientCallsMix {
|
|||
getCalls: () => Promise<CallChannelState[]>;
|
||||
getCallForChannel: (channelId: string) => Promise<CallChannelState>;
|
||||
getCallsConfig: () => Promise<CallsConfig>;
|
||||
getVersion: () => Promise<CallsVersion>;
|
||||
enableChannelCalls: (channelId: string, enable: boolean) => Promise<CallChannelState>;
|
||||
endCall: (channelId: string) => Promise<ApiResp>;
|
||||
genTURNCredentials: () => Promise<RTCIceServer[]>;
|
||||
|
|
@ -52,6 +53,17 @@ const ClientCalls = (superclass: any) => class extends superclass {
|
|||
) as CallsConfig;
|
||||
};
|
||||
|
||||
getVersion = async () => {
|
||||
try {
|
||||
return this.doFetch(
|
||||
`${this.getCallsRoute()}/version`,
|
||||
{method: 'get'},
|
||||
);
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
enableChannelCalls = async (channelId: string, enable: boolean) => {
|
||||
return this.doFetch(
|
||||
`${this.getCallsRoute()}/${channelId}`,
|
||||
|
|
|
|||
|
|
@ -23,14 +23,13 @@ import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
|||
import {typography} from '@utils/typography';
|
||||
import {displayUsername} from '@utils/user';
|
||||
|
||||
import type {CallsTheme, CurrentCall} from '@calls/types/calls';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
import type {CallSession, CallsTheme, CurrentCall} from '@calls/types/calls';
|
||||
import type {Options} from 'react-native-navigation';
|
||||
|
||||
type Props = {
|
||||
displayName: string;
|
||||
currentCall: CurrentCall | null;
|
||||
userModelsDict: Dictionary<UserModel>;
|
||||
sessionsDict: Dictionary<CallSession>;
|
||||
teammateNameDisplay: string;
|
||||
micPermissionsGranted: boolean;
|
||||
threadScreen?: boolean;
|
||||
|
|
@ -135,7 +134,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: CallsTheme) => {
|
|||
const CurrentCallBar = ({
|
||||
displayName,
|
||||
currentCall,
|
||||
userModelsDict,
|
||||
sessionsDict,
|
||||
teammateNameDisplay,
|
||||
micPermissionsGranted,
|
||||
threadScreen,
|
||||
|
|
@ -169,7 +168,7 @@ const CurrentCallBar = ({
|
|||
leaveCall();
|
||||
}, []);
|
||||
|
||||
const myParticipant = currentCall?.participants[currentCall.myUserId];
|
||||
const mySession = currentCall?.sessions[currentCall.mySessionId];
|
||||
|
||||
// Since we can only see one user talking, it doesn't really matter who we show here (e.g., we can't
|
||||
// tell who is speaking louder).
|
||||
|
|
@ -185,7 +184,7 @@ const CurrentCallBar = ({
|
|||
if (speaker) {
|
||||
talkingMessage = (
|
||||
<Text style={style.speakingUser}>
|
||||
{displayUsername(userModelsDict[speaker], intl.locale, teammateNameDisplay)}
|
||||
{displayUsername(sessionsDict[speaker].userModel, intl.locale, teammateNameDisplay)}
|
||||
{' '}
|
||||
<Text style={style.speakingPostfix}>{
|
||||
formatMessage({
|
||||
|
|
@ -197,7 +196,7 @@ const CurrentCallBar = ({
|
|||
}
|
||||
|
||||
const muteUnmute = () => {
|
||||
if (myParticipant?.muted) {
|
||||
if (mySession?.muted) {
|
||||
unmuteMyself();
|
||||
} else {
|
||||
muteMyself();
|
||||
|
|
@ -208,7 +207,7 @@ const CurrentCallBar = ({
|
|||
|
||||
// The user should receive an alert if all of the following conditions apply:
|
||||
// - Recording has started and recording has not ended.
|
||||
const isHost = Boolean(currentCall?.hostId === myParticipant?.id);
|
||||
const isHost = Boolean(currentCall?.hostId === mySession?.userId);
|
||||
if (currentCall?.recState?.start_at && !currentCall?.recState?.end_at) {
|
||||
recordingAlert(isHost, intl);
|
||||
}
|
||||
|
|
@ -233,7 +232,7 @@ const CurrentCallBar = ({
|
|||
>
|
||||
<View style={[!speaker && style.avatarOutline]}>
|
||||
<CallAvatar
|
||||
userModel={userModelsDict[speaker || '']}
|
||||
userModel={sessionsDict[speaker || '']?.userModel}
|
||||
speaking={Boolean(speaker)}
|
||||
serverUrl={currentCall?.serverUrl || ''}
|
||||
size={speaker ? 40 : 24}
|
||||
|
|
@ -254,11 +253,11 @@ const CurrentCallBar = ({
|
|||
<View style={style.buttonContainer}>
|
||||
<Pressable
|
||||
onPress={muteUnmute}
|
||||
style={[style.pressable, style.micIconContainer, myParticipant?.muted && style.muted]}
|
||||
style={[style.pressable, style.micIconContainer, mySession?.muted && style.muted]}
|
||||
disabled={!micPermissionsGranted}
|
||||
>
|
||||
<UnavailableIconWrapper
|
||||
name={myParticipant?.muted ? 'microphone-off' : 'microphone'}
|
||||
name={mySession?.muted ? 'microphone-off' : 'microphone'}
|
||||
size={24}
|
||||
unavailable={!micPermissionsGranted}
|
||||
style={style.micIcon}
|
||||
|
|
|
|||
|
|
@ -5,16 +5,14 @@ import withObservables from '@nozbe/with-observables';
|
|||
import {combineLatest, of as of$} from 'rxjs';
|
||||
import {distinctUntilChanged, switchMap} from 'rxjs/operators';
|
||||
|
||||
import {observeCurrentSessionsDict} from '@calls/observers';
|
||||
import {observeCurrentCall, observeGlobalCallsState} from '@calls/state';
|
||||
import {idsAreEqual} from '@calls/utils';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {observeChannel} from '@queries/servers/channel';
|
||||
import {observeTeammateNameDisplay, queryUsersById} from '@queries/servers/user';
|
||||
import {observeTeammateNameDisplay} from '@queries/servers/user';
|
||||
|
||||
import CurrentCallBar from './current_call_bar';
|
||||
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
const enhanced = withObservables([], () => {
|
||||
const currentCall = observeCurrentCall();
|
||||
const ccServerUrl = currentCall.pipe(
|
||||
|
|
@ -33,17 +31,9 @@ const enhanced = withObservables([], () => {
|
|||
switchMap((c) => of$(c?.displayName || '')),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
const participantIds = currentCall.pipe(
|
||||
distinctUntilChanged((prev, curr) => prev?.participants === curr?.participants), // Did the participants object ref change?
|
||||
switchMap((call) => (call ? of$(Object.keys(call.participants)) : of$([]))),
|
||||
distinctUntilChanged((prev, curr) => idsAreEqual(prev, curr)),
|
||||
);
|
||||
const userModelsDict = combineLatest([database, participantIds]).pipe(
|
||||
switchMap(([db, ids]) => (db && ids.length > 0 ? queryUsersById(db, ids).observeWithColumns(['nickname', 'username', 'first_name', 'last_name']) : of$([]))),
|
||||
switchMap((ps) => of$(arrayToDic(ps))),
|
||||
);
|
||||
const teammateNameDisplay = database.pipe(
|
||||
switchMap((db) => (db ? observeTeammateNameDisplay(db) : of$(''))),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
const micPermissionsGranted = observeGlobalCallsState().pipe(
|
||||
switchMap((gs) => of$(gs.micPermissionsGranted)),
|
||||
|
|
@ -53,17 +43,10 @@ const enhanced = withObservables([], () => {
|
|||
return {
|
||||
displayName,
|
||||
currentCall,
|
||||
userModelsDict,
|
||||
sessionsDict: observeCurrentSessionsDict(),
|
||||
teammateNameDisplay,
|
||||
micPermissionsGranted,
|
||||
};
|
||||
});
|
||||
|
||||
function arrayToDic(participants: UserModel[]) {
|
||||
return participants.reduce((accum, cur) => {
|
||||
accum[cur.id] = cur;
|
||||
return accum;
|
||||
}, {} as Dictionary<UserModel>);
|
||||
}
|
||||
|
||||
export default enhanced(CurrentCallBar);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {distinctUntilChanged, switchMap} from 'rxjs/operators';
|
|||
import JoinCallBanner from '@calls/components/join_call_banner/join_call_banner';
|
||||
import {observeIsCallLimitRestricted} from '@calls/observers';
|
||||
import {observeCallsState} from '@calls/state';
|
||||
import {idsAreEqual} from '@calls/utils';
|
||||
import {idsAreEqual, userIds} from '@calls/utils';
|
||||
import {queryUsersById} from '@queries/servers/user';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
|
|
@ -28,10 +28,10 @@ const enhanced = withObservables(['serverUrl', 'channelId'], ({
|
|||
const callsState = observeCallsState(serverUrl).pipe(
|
||||
switchMap((state) => of$(state.calls[channelId])),
|
||||
);
|
||||
const participants = callsState.pipe(
|
||||
distinctUntilChanged((prev, curr) => prev?.participants === curr?.participants), // Did the participants object ref change?
|
||||
switchMap((call) => (call ? of$(Object.keys(call.participants)) : of$([]))),
|
||||
distinctUntilChanged((prev, curr) => idsAreEqual(prev, curr)), // Continue only if we have a different set of participant ids
|
||||
const userModels = callsState.pipe(
|
||||
distinctUntilChanged((prev, curr) => prev?.sessions === curr?.sessions), // Did the userModels object ref change?
|
||||
switchMap((call) => (call ? of$(userIds(Object.values(call.sessions))) : of$([]))),
|
||||
distinctUntilChanged((prev, curr) => idsAreEqual(prev, curr)), // Continue only if we have a different set of participant userIds
|
||||
switchMap((ids) => (ids.length > 0 ? queryUsersById(database, ids).observeWithColumns(['last_picture_update']) : of$([]))),
|
||||
);
|
||||
const channelCallStartTime = callsState.pipe(
|
||||
|
|
@ -47,7 +47,7 @@ const enhanced = withObservables(['serverUrl', 'channelId'], ({
|
|||
|
||||
return {
|
||||
callId,
|
||||
participants,
|
||||
userModels,
|
||||
channelCallStartTime,
|
||||
limitRestrictedInfo: observeIsCallLimitRestricted(database, serverUrl, channelId),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ type Props = {
|
|||
channelId: string;
|
||||
callId: string;
|
||||
serverUrl: string;
|
||||
participants: UserModel[];
|
||||
userModels: UserModel[];
|
||||
channelCallStartTime: number;
|
||||
limitRestrictedInfo: LimitRestrictedInfo;
|
||||
}
|
||||
|
|
@ -134,7 +134,7 @@ const JoinCallBanner = ({
|
|||
channelId,
|
||||
callId,
|
||||
serverUrl,
|
||||
participants,
|
||||
userModels,
|
||||
channelCallStartTime,
|
||||
limitRestrictedInfo,
|
||||
}: Props) => {
|
||||
|
|
@ -191,7 +191,7 @@ const JoinCallBanner = ({
|
|||
<UserAvatarsStack
|
||||
channelId={channelId}
|
||||
location={Screens.CHANNEL}
|
||||
users={participants}
|
||||
users={userModels}
|
||||
breakAt={3}
|
||||
avatarStyle={style.avatarStyle}
|
||||
overflowContainerStyle={style.overflowContainer}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {DeviceEventEmitter} from 'react-native';
|
|||
import {fetchUsersByIds} from '@actions/remote/user';
|
||||
import {
|
||||
callEnded,
|
||||
callStarted,
|
||||
callStarted, getCallsConfig,
|
||||
removeIncomingCall,
|
||||
setCallScreenOff,
|
||||
setCallScreenOn,
|
||||
|
|
@ -20,6 +20,7 @@ import {
|
|||
userLeftCall,
|
||||
userReacted,
|
||||
} from '@calls/state';
|
||||
import {isMultiSessionSupported} from '@calls/utils';
|
||||
import {WebsocketEvents} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getCurrentUserId} from '@queries/servers/system';
|
||||
|
|
@ -32,6 +33,8 @@ import type {
|
|||
UserConnectedData,
|
||||
UserDisconnectedData,
|
||||
UserDismissedNotification,
|
||||
UserJoinedData,
|
||||
UserLeftData,
|
||||
UserMutedUnmutedData,
|
||||
UserRaiseUnraiseHandData,
|
||||
UserReactionData,
|
||||
|
|
@ -39,31 +42,58 @@ import type {
|
|||
UserVoiceOnOffData,
|
||||
} from '@mattermost/calls/lib/types';
|
||||
|
||||
// DEPRECATED in favour of user_joined (since v0.21.0)
|
||||
export const handleCallUserConnected = (serverUrl: string, msg: WebSocketMessage<UserConnectedData>) => {
|
||||
if (isMultiSessionSupported(getCallsConfig(serverUrl).version)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load user model async (if needed).
|
||||
fetchUsersByIds(serverUrl, [msg.data.userID]);
|
||||
|
||||
userJoinedCall(serverUrl, msg.broadcast.channel_id, msg.data.userID);
|
||||
// Pre v0.21.0, sessionID == userID
|
||||
userJoinedCall(serverUrl, msg.broadcast.channel_id, msg.data.userID, msg.data.userID);
|
||||
};
|
||||
|
||||
// DEPRECATED in favour of user_left (since v0.21.0)
|
||||
export const handleCallUserDisconnected = (serverUrl: string, msg: WebSocketMessage<UserDisconnectedData>) => {
|
||||
if (isMultiSessionSupported(getCallsConfig(serverUrl).version)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// pre v0.21.0, sessionID == userID
|
||||
userLeftCall(serverUrl, msg.broadcast.channel_id, msg.data.userID);
|
||||
};
|
||||
|
||||
export const handleCallUserJoined = (serverUrl: string, msg: WebSocketMessage<UserJoinedData>) => {
|
||||
// Load user model async (if needed).
|
||||
fetchUsersByIds(serverUrl, [msg.data.user_id]);
|
||||
|
||||
userJoinedCall(serverUrl, msg.broadcast.channel_id, msg.data.user_id, msg.data.session_id);
|
||||
};
|
||||
|
||||
export const handleCallUserLeft = (serverUrl: string, msg: WebSocketMessage<UserLeftData>) => {
|
||||
userLeftCall(serverUrl, msg.broadcast.channel_id, msg.data.session_id);
|
||||
};
|
||||
|
||||
export const handleCallUserMuted = (serverUrl: string, msg: WebSocketMessage<UserMutedUnmutedData>) => {
|
||||
setUserMuted(serverUrl, msg.broadcast.channel_id, msg.data.userID, true);
|
||||
// pre v0.21.0, sessionID == userID
|
||||
setUserMuted(serverUrl, msg.broadcast.channel_id, msg.data.session_id || msg.data.userID, true);
|
||||
};
|
||||
|
||||
export const handleCallUserUnmuted = (serverUrl: string, msg: WebSocketMessage<UserMutedUnmutedData>) => {
|
||||
setUserMuted(serverUrl, msg.broadcast.channel_id, msg.data.userID, false);
|
||||
// pre v0.21.0, sessionID == userID
|
||||
setUserMuted(serverUrl, msg.broadcast.channel_id, msg.data.session_id || msg.data.userID, false);
|
||||
};
|
||||
|
||||
export const handleCallUserVoiceOn = (msg: WebSocketMessage<UserVoiceOnOffData>) => {
|
||||
setUserVoiceOn(msg.broadcast.channel_id, msg.data.userID, true);
|
||||
// pre v0.21.0, sessionID == userID
|
||||
setUserVoiceOn(msg.broadcast.channel_id, msg.data.session_id || msg.data.userID, true);
|
||||
};
|
||||
|
||||
export const handleCallUserVoiceOff = (msg: WebSocketMessage<UserVoiceOnOffData>) => {
|
||||
setUserVoiceOn(msg.broadcast.channel_id, msg.data.userID, false);
|
||||
// pre v0.21.0, sessionID == userID
|
||||
setUserVoiceOn(msg.broadcast.channel_id, msg.data.session_id || msg.data.userID, false);
|
||||
};
|
||||
|
||||
export const handleCallStarted = (serverUrl: string, msg: WebSocketMessage<CallStartData>) => {
|
||||
|
|
@ -73,7 +103,7 @@ export const handleCallStarted = (serverUrl: string, msg: WebSocketMessage<CallS
|
|||
startTime: msg.data.start_at,
|
||||
threadId: msg.data.thread_id,
|
||||
screenOn: '',
|
||||
participants: {},
|
||||
sessions: {},
|
||||
ownerId: msg.data.owner_id,
|
||||
hostId: msg.data.host_id,
|
||||
dismissed: {},
|
||||
|
|
@ -97,22 +127,31 @@ export const handleCallChannelDisabled = (serverUrl: string, msg: WebSocketMessa
|
|||
};
|
||||
|
||||
export const handleCallScreenOn = (serverUrl: string, msg: WebSocketMessage<UserScreenOnOffData>) => {
|
||||
setCallScreenOn(serverUrl, msg.broadcast.channel_id, msg.data.userID);
|
||||
// pre v0.21.0, sessionID == userID
|
||||
setCallScreenOn(serverUrl, msg.broadcast.channel_id, msg.data.session_id || msg.data.userID);
|
||||
};
|
||||
|
||||
export const handleCallScreenOff = (serverUrl: string, msg: WebSocketMessage<UserScreenOnOffData>) => {
|
||||
setCallScreenOff(serverUrl, msg.broadcast.channel_id);
|
||||
// pre v0.21.0, sessionID == userID
|
||||
setCallScreenOff(serverUrl, msg.broadcast.channel_id, msg.data.session_id || msg.data.userID);
|
||||
};
|
||||
|
||||
export const handleCallUserRaiseHand = (serverUrl: string, msg: WebSocketMessage<UserRaiseUnraiseHandData>) => {
|
||||
setRaisedHand(serverUrl, msg.broadcast.channel_id, msg.data.userID, msg.data.raised_hand);
|
||||
// pre v0.21.0, sessionID == userID
|
||||
setRaisedHand(serverUrl, msg.broadcast.channel_id, msg.data.session_id || msg.data.userID, msg.data.raised_hand);
|
||||
};
|
||||
|
||||
export const handleCallUserUnraiseHand = (serverUrl: string, msg: WebSocketMessage<UserRaiseUnraiseHandData>) => {
|
||||
setRaisedHand(serverUrl, msg.broadcast.channel_id, msg.data.userID, msg.data.raised_hand);
|
||||
// pre v0.21.0, sessionID == userID
|
||||
setRaisedHand(serverUrl, msg.broadcast.channel_id, msg.data.session_id || msg.data.userID, msg.data.raised_hand);
|
||||
};
|
||||
|
||||
export const handleCallUserReacted = (serverUrl: string, msg: WebSocketMessage<UserReactionData>) => {
|
||||
// pre v0.21.0, sessionID == userID
|
||||
if (!isMultiSessionSupported(getCallsConfig(serverUrl).version)) {
|
||||
msg.data.session_id = msg.data.user_id;
|
||||
}
|
||||
|
||||
userReacted(serverUrl, msg.broadcast.channel_id, msg.data);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,16 @@
|
|||
|
||||
import {distinctUntilChanged, switchMap, combineLatest, Observable, of as of$} from 'rxjs';
|
||||
|
||||
import {observeCallsConfig, observeCallsState} from '@calls/state';
|
||||
import {observeCallsConfig, observeCallsState, observeCurrentCall} from '@calls/state';
|
||||
import {fillUserModels, userIds} from '@calls/utils';
|
||||
import {License} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {observeConfigValue, observeLicense} from '@queries/servers/system';
|
||||
import {queryUsersById} from '@queries/servers/user';
|
||||
import UserModel from '@typings/database/models/servers/user';
|
||||
import {isMinimumServerVersion} from '@utils/helpers';
|
||||
|
||||
import type {CallSession} from '@calls/types/calls';
|
||||
import type {Database} from '@nozbe/watermelondb';
|
||||
|
||||
export type LimitRestrictedInfo = {
|
||||
|
|
@ -44,7 +49,7 @@ export const observeIsCallLimitRestricted = (database: Database, serverUrl: stri
|
|||
distinctUntilChanged(),
|
||||
);
|
||||
const callNumOfParticipants = observeCallsState(serverUrl).pipe(
|
||||
switchMap((cs) => of$(Object.keys(cs.calls[channelId]?.participants || {}).length)),
|
||||
switchMap((cs) => of$(Object.keys(cs.calls[channelId]?.sessions || {}).length)),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
const isCloud = observeLicense(database).pipe(
|
||||
|
|
@ -65,3 +70,21 @@ export const observeIsCallLimitRestricted = (database: Database, serverUrl: stri
|
|||
prev.limitRestricted === curr.limitRestricted && prev.maxParticipants === curr.maxParticipants && prev.isCloudStarter === curr.isCloudStarter),
|
||||
) as Observable<LimitRestrictedInfo>;
|
||||
};
|
||||
|
||||
export const observeCurrentSessionsDict = () => {
|
||||
const currentCall = observeCurrentCall();
|
||||
const database = currentCall.pipe(
|
||||
switchMap((call) => of$(call ? call.serverUrl : '')),
|
||||
distinctUntilChanged(),
|
||||
switchMap((url) => of$(DatabaseManager.serverDatabases[url]?.database)),
|
||||
);
|
||||
|
||||
return combineLatest([database, currentCall]).pipe(
|
||||
switchMap(([db, call]) => (db && call ? queryUsersById(db, userIds(Object.values(call.sessions))).observeWithColumns(['nickname', 'username', 'first_name', 'last_name', 'last_picture_update']) : of$([])).pipe(
|
||||
|
||||
// We now have a UserModel[] one for each userId, but we need the session dictionary with user models
|
||||
// eslint-disable-next-line max-nested-callbacks
|
||||
switchMap((ps: UserModel[]) => of$(fillUserModels(call?.sessions || {}, ps))),
|
||||
)),
|
||||
) as Observable<Dictionary<CallSession>>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ import {
|
|||
useCallsConfig,
|
||||
useIncomingCalls,
|
||||
} from '@calls/state';
|
||||
import {getHandsRaised, makeCallsTheme, sortParticipants} from '@calls/utils';
|
||||
import {getHandsRaised, makeCallsTheme, sortSessions} from '@calls/utils';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import SlideUpPanelItem, {ITEM_HEIGHT} from '@components/slide_up_panel_item';
|
||||
|
|
@ -68,7 +68,7 @@ import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
|||
import {typography} from '@utils/typography';
|
||||
import {displayUsername} from '@utils/user';
|
||||
|
||||
import type {CallParticipant, CallsTheme, CurrentCall} from '@calls/types/calls';
|
||||
import type {CallSession, CallsTheme, CurrentCall} from '@calls/types/calls';
|
||||
import type {AvailableScreens} from '@typings/screens/navigation';
|
||||
|
||||
const avatarL = 96;
|
||||
|
|
@ -79,7 +79,7 @@ const usernameM = 92;
|
|||
export type Props = {
|
||||
componentId: AvailableScreens;
|
||||
currentCall: CurrentCall | null;
|
||||
participantsDict: Dictionary<CallParticipant>;
|
||||
sessionsDict: Dictionary<CallSession>;
|
||||
micPermissionsGranted: boolean;
|
||||
teammateNameDisplay: string;
|
||||
fromThreadScreen?: boolean;
|
||||
|
|
@ -317,7 +317,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: CallsTheme) => ({
|
|||
const CallScreen = ({
|
||||
componentId,
|
||||
currentCall,
|
||||
participantsDict,
|
||||
sessionsDict,
|
||||
micPermissionsGranted,
|
||||
teammateNameDisplay,
|
||||
fromThreadScreen,
|
||||
|
|
@ -339,13 +339,13 @@ const CallScreen = ({
|
|||
const [centerUsers, setCenterUsers] = useState(false);
|
||||
const [layout, setLayout] = useState<LayoutRectangle | null>(null);
|
||||
|
||||
const myParticipant = currentCall?.participants[currentCall.myUserId];
|
||||
const mySession = currentCall?.sessions[currentCall.mySessionId];
|
||||
const micPermissionsError = !micPermissionsGranted && !currentCall?.micPermissionsErrorDismissed;
|
||||
const screenShareOn = Boolean(currentCall?.screenOn);
|
||||
const isLandscape = width > height;
|
||||
const smallerAvatar = isLandscape || screenShareOn;
|
||||
const avatarSize = smallerAvatar ? avatarM : avatarL;
|
||||
const numParticipants = Object.keys(participantsDict).length;
|
||||
const numSessions = Object.keys(sessionsDict).length;
|
||||
const showIncomingCalls = incomingCalls.incomingCalls.length > 0;
|
||||
|
||||
const callThreadOptionTitle = intl.formatMessage({id: 'mobile.calls_call_thread', defaultMessage: 'Call Thread'});
|
||||
|
|
@ -389,12 +389,12 @@ const CallScreen = ({
|
|||
}, []);
|
||||
|
||||
const muteUnmuteHandler = useCallback(() => {
|
||||
if (myParticipant?.muted) {
|
||||
if (mySession?.muted) {
|
||||
unmuteMyself();
|
||||
} else {
|
||||
muteMyself();
|
||||
}
|
||||
}, [myParticipant?.muted]);
|
||||
}, [mySession?.muted]);
|
||||
|
||||
const toggleReactions = useCallback(() => {
|
||||
setShowReactions((prev) => !prev);
|
||||
|
|
@ -450,7 +450,7 @@ const CallScreen = ({
|
|||
|
||||
// The user should receive a recording alert if all of the following conditions apply:
|
||||
// - Recording has started, recording has not ended
|
||||
const isHost = Boolean(currentCall?.hostId === myParticipant?.id);
|
||||
const isHost = Boolean(currentCall?.hostId === mySession?.userId);
|
||||
const recording = Boolean(currentCall?.recState?.start_at && !currentCall.recState.end_at);
|
||||
if (recording) {
|
||||
recordingAlert(isHost, intl);
|
||||
|
|
@ -541,8 +541,8 @@ const CallScreen = ({
|
|||
const avatarCellWidth = usernameSize + 20; // name width + padding
|
||||
|
||||
const perRow = Math.floor(layout.width / avatarCellWidth);
|
||||
const totalHeight = Math.ceil(numParticipants / perRow) * avatarCellHeight;
|
||||
const totalWidth = numParticipants * avatarCellWidth;
|
||||
const totalHeight = Math.ceil(numSessions / perRow) * avatarCellHeight;
|
||||
const totalWidth = numSessions * avatarCellWidth;
|
||||
|
||||
// If screenShareOn, we care about width, otherwise we care about height.
|
||||
if ((screenShareOn && totalWidth > layout.width) || (!screenShareOn && totalHeight > layout.height)) {
|
||||
|
|
@ -550,13 +550,13 @@ const CallScreen = ({
|
|||
} else {
|
||||
setCenterUsers(true);
|
||||
}
|
||||
}, [layout, numParticipants]);
|
||||
}, [layout, numSessions]);
|
||||
|
||||
const onLayout = useCallback((e: LayoutChangeEvent) => {
|
||||
setLayout(e.nativeEvent.layout);
|
||||
}, []);
|
||||
|
||||
if (!currentCall || !myParticipant) {
|
||||
if (!currentCall || !mySession) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -576,7 +576,7 @@ const CallScreen = ({
|
|||
<FormattedText
|
||||
id={'mobile.calls_viewing_screen'}
|
||||
defaultMessage={'You are viewing {name}\'s screen'}
|
||||
values={{name: displayUsername(participantsDict[currentCall.screenOn].userModel, intl.locale, teammateNameDisplay)}}
|
||||
values={{name: displayUsername(sessionsDict[currentCall.screenOn].userModel, intl.locale, teammateNameDisplay)}}
|
||||
style={style.screenShareText}
|
||||
/>
|
||||
}
|
||||
|
|
@ -584,8 +584,8 @@ const CallScreen = ({
|
|||
);
|
||||
}
|
||||
|
||||
const raisedHands = getHandsRaised(participantsDict);
|
||||
const participants = sortParticipants(intl.locale, teammateNameDisplay, participantsDict, currentCall.screenOn);
|
||||
const raisedHands = getHandsRaised(sessionsDict);
|
||||
const sessions = sortSessions(intl.locale, teammateNameDisplay, sessionsDict, currentCall.screenOn);
|
||||
let usersList = null;
|
||||
if (!screenShareOn || !isLandscape) {
|
||||
usersList = (
|
||||
|
|
@ -601,20 +601,20 @@ const CallScreen = ({
|
|||
onPress={toggleControlsInLandscape}
|
||||
style={style.users}
|
||||
>
|
||||
{participants.map((user) => {
|
||||
{sessions.map((sess) => {
|
||||
return (
|
||||
<View
|
||||
style={[style.user, screenShareOn && style.userScreenOn]}
|
||||
key={user.id}
|
||||
key={sess.sessionId}
|
||||
>
|
||||
<View style={[screenShareOn && style.profileScreenOn]}>
|
||||
<CallAvatar
|
||||
userModel={user.userModel}
|
||||
speaking={currentCall.voiceOn[user.id]}
|
||||
muted={user.muted}
|
||||
sharingScreen={user.id === currentCall.screenOn}
|
||||
raisedHand={Boolean(user.raisedHand)}
|
||||
reaction={user.reaction?.emoji}
|
||||
userModel={sess.userModel}
|
||||
speaking={currentCall.voiceOn[sess.sessionId]}
|
||||
muted={sess.muted}
|
||||
sharingScreen={sess.sessionId === currentCall.screenOn}
|
||||
raisedHand={Boolean(sess.raisedHand)}
|
||||
reaction={sess.reaction?.emoji}
|
||||
size={avatarSize}
|
||||
serverUrl={currentCall.serverUrl}
|
||||
/>
|
||||
|
|
@ -623,12 +623,12 @@ const CallScreen = ({
|
|||
style={[style.username, smallerAvatar && style.usernameShort]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{displayUsername(user.userModel, intl.locale, teammateNameDisplay)}
|
||||
{user.id === myParticipant.id &&
|
||||
{displayUsername(sess.userModel, intl.locale, teammateNameDisplay)}
|
||||
{sess.sessionId === mySession.sessionId &&
|
||||
` ${intl.formatMessage({id: 'mobile.calls_you', defaultMessage: '(you)'})}`
|
||||
}
|
||||
</Text>
|
||||
{user.id === currentCall.hostId && <CallsBadge type={CallsBadgeType.Host}/>}
|
||||
{sess.userId === currentCall.hostId && <CallsBadge type={CallsBadgeType.Host}/>}
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
|
|
@ -668,7 +668,7 @@ const CallScreen = ({
|
|||
/>
|
||||
<RaisedHandBanner
|
||||
raisedHands={raisedHands}
|
||||
currentUserId={currentCall.myUserId}
|
||||
sessionId={currentCall.mySessionId}
|
||||
teammateNameDisplay={teammateNameDisplay}
|
||||
/>
|
||||
<Pressable
|
||||
|
|
@ -729,22 +729,22 @@ const CallScreen = ({
|
|||
]}
|
||||
>
|
||||
{showReactions &&
|
||||
<ReactionBar raisedHand={myParticipant.raisedHand}/>
|
||||
<ReactionBar raisedHand={mySession.raisedHand}/>
|
||||
}
|
||||
{!isLandscape &&
|
||||
<Pressable
|
||||
testID='mute-unmute'
|
||||
style={[style.mute, myParticipant.muted && style.muteMuted]}
|
||||
style={[style.mute, mySession.muted && style.muteMuted]}
|
||||
onPress={muteUnmuteHandler}
|
||||
disabled={!micPermissionsGranted}
|
||||
>
|
||||
<UnavailableIconWrapper
|
||||
name={myParticipant.muted ? 'microphone-off' : 'microphone'}
|
||||
name={mySession.muted ? 'microphone-off' : 'microphone'}
|
||||
size={32}
|
||||
unavailable={!micPermissionsGranted}
|
||||
style={style.muteIcon}
|
||||
/>
|
||||
{myParticipant.muted ? UnmuteText : MuteText}
|
||||
{mySession.muted ? UnmuteText : MuteText}
|
||||
</Pressable>
|
||||
}
|
||||
<View style={[style.otherButtons, isLandscape && style.otherButtonsLandscape]}>
|
||||
|
|
@ -813,18 +813,18 @@ const CallScreen = ({
|
|||
onPress={muteUnmuteHandler}
|
||||
>
|
||||
<UnavailableIconWrapper
|
||||
name={myParticipant.muted ? 'microphone-off' : 'microphone'}
|
||||
name={mySession.muted ? 'microphone-off' : 'microphone'}
|
||||
size={32}
|
||||
unavailable={!micPermissionsGranted}
|
||||
style={[
|
||||
style.buttonIcon,
|
||||
isLandscape && style.buttonIconLandscape,
|
||||
style.muteIconLandscape,
|
||||
myParticipant?.muted && style.muteIconLandscapeMuted,
|
||||
mySession?.muted && style.muteIconLandscapeMuted,
|
||||
]}
|
||||
errorContainerStyle={isLandscape && style.errorContainerLandscape}
|
||||
/>
|
||||
{myParticipant.muted ? UnmuteText : MuteText}
|
||||
{mySession.muted ? UnmuteText : MuteText}
|
||||
</Pressable>
|
||||
}
|
||||
{(isLandscape || !isHost) &&
|
||||
|
|
|
|||
|
|
@ -2,16 +2,14 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {combineLatest, of as of$} from 'rxjs';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {distinctUntilChanged, switchMap} from 'rxjs/operators';
|
||||
|
||||
import {observeCurrentSessionsDict} from '@calls/observers';
|
||||
import CallScreen from '@calls/screens/call_screen/call_screen';
|
||||
import {observeCurrentCall, observeGlobalCallsState} from '@calls/state';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {observeTeammateNameDisplay, queryUsersById} from '@queries/servers/user';
|
||||
|
||||
import type {CallParticipant} from '@calls/types/calls';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
import {observeTeammateNameDisplay} from '@queries/servers/user';
|
||||
|
||||
const enhanced = withObservables([], () => {
|
||||
const currentCall = observeCurrentCall();
|
||||
|
|
@ -20,20 +18,6 @@ const enhanced = withObservables([], () => {
|
|||
distinctUntilChanged(),
|
||||
switchMap((url) => of$(DatabaseManager.serverDatabases[url]?.database)),
|
||||
);
|
||||
|
||||
// TODO: to be optimized https://mattermost.atlassian.net/browse/MM-49338
|
||||
const participantsDict = combineLatest([database, currentCall]).pipe(
|
||||
switchMap(([db, call]) => (db && call ? queryUsersById(db, Object.keys(call.participants)).observeWithColumns(['nickname', 'username', 'first_name', 'last_name', 'last_picture_update']) : of$([])).pipe(
|
||||
// eslint-disable-next-line max-nested-callbacks
|
||||
switchMap((ps: UserModel[]) => of$(ps.reduce((accum, cur) => {
|
||||
accum[cur.id] = {
|
||||
...call!.participants[cur.id],
|
||||
userModel: cur,
|
||||
};
|
||||
return accum;
|
||||
}, {} as Dictionary<CallParticipant>))),
|
||||
)),
|
||||
);
|
||||
const micPermissionsGranted = observeGlobalCallsState().pipe(
|
||||
switchMap((gs) => of$(gs.micPermissionsGranted)),
|
||||
distinctUntilChanged(),
|
||||
|
|
@ -45,7 +29,7 @@ const enhanced = withObservables([], () => {
|
|||
|
||||
return {
|
||||
currentCall,
|
||||
participantsDict,
|
||||
sessionsDict: observeCurrentSessionsDict(),
|
||||
micPermissionsGranted,
|
||||
teammateNameDisplay,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@ import {useTheme} from '@context/theme';
|
|||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
import type {CallParticipant, CallsTheme} from '@calls/types/calls';
|
||||
import type {CallSession, CallsTheme} from '@calls/types/calls';
|
||||
|
||||
export type Props = {
|
||||
raisedHands: CallParticipant[];
|
||||
currentUserId: string;
|
||||
raisedHands: CallSession[];
|
||||
sessionId: string;
|
||||
teammateNameDisplay: string;
|
||||
}
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: CallsTheme) => ({
|
|||
},
|
||||
}));
|
||||
|
||||
export const RaisedHandBanner = ({raisedHands, currentUserId, teammateNameDisplay}: Props) => {
|
||||
export const RaisedHandBanner = ({raisedHands, sessionId, teammateNameDisplay}: Props) => {
|
||||
const intl = useIntl();
|
||||
const theme = useTheme();
|
||||
const callsTheme = useMemo(() => makeCallsTheme(theme), [theme]);
|
||||
|
|
@ -63,7 +63,7 @@ export const RaisedHandBanner = ({raisedHands, currentUserId, teammateNameDispla
|
|||
return <View style={style.raisedHandBannerContainer}/>;
|
||||
}
|
||||
|
||||
const names = getHandsRaisedNames(raisedHands, currentUserId, intl.locale, teammateNameDisplay, intl);
|
||||
const names = getHandsRaisedNames(raisedHands, sessionId, intl.locale, teammateNameDisplay, intl);
|
||||
|
||||
return (
|
||||
<View style={style.raisedHandBannerContainer}>
|
||||
|
|
|
|||
|
|
@ -102,9 +102,9 @@ jest.mock('react-native-navigation', () => ({
|
|||
|
||||
const call1: Call = {
|
||||
id: 'call1',
|
||||
participants: {
|
||||
'user-1': {id: 'user-1', muted: false, raisedHand: 0},
|
||||
'user-2': {id: 'user-2', muted: true, raisedHand: 0},
|
||||
sessions: {
|
||||
session1: {sessionId: 'session1', userId: 'user-1', muted: false, raisedHand: 0},
|
||||
session2: {sessionId: 'session2', userId: 'user-2', muted: true, raisedHand: 0},
|
||||
},
|
||||
channelId: 'channel-1',
|
||||
startTime: 123,
|
||||
|
|
@ -116,9 +116,9 @@ const call1: Call = {
|
|||
};
|
||||
const call2: Call = {
|
||||
id: 'call2',
|
||||
participants: {
|
||||
'user-3': {id: 'user-3', muted: false, raisedHand: 0},
|
||||
'user-4': {id: 'user-4', muted: true, raisedHand: 0},
|
||||
sessions: {
|
||||
session3: {sessionId: 'session3', userId: 'user-3', muted: false, raisedHand: 0},
|
||||
session4: {sessionId: 'session4', userId: 'user-4', muted: true, raisedHand: 0},
|
||||
},
|
||||
channelId: 'channel-2',
|
||||
startTime: 123,
|
||||
|
|
@ -130,9 +130,9 @@ const call2: Call = {
|
|||
};
|
||||
const call3: Call = {
|
||||
id: 'call3',
|
||||
participants: {
|
||||
'user-5': {id: 'user-5', muted: false, raisedHand: 0},
|
||||
'user-6': {id: 'user-6', muted: true, raisedHand: 0},
|
||||
sessions: {
|
||||
session5: {sessionId: 'session5', userId: 'user-5', muted: false, raisedHand: 0},
|
||||
session6: {sessionId: 'session6', userId: 'user-6', muted: true, raisedHand: 0},
|
||||
},
|
||||
channelId: 'channel-3',
|
||||
startTime: 123,
|
||||
|
|
@ -144,8 +144,8 @@ const call3: Call = {
|
|||
};
|
||||
const callDM: Call = {
|
||||
id: 'callDM',
|
||||
participants: {
|
||||
'user-5': {id: 'user-5', muted: false, raisedHand: 0},
|
||||
sessions: {
|
||||
session5: {sessionId: 'session5', userId: 'user-5', muted: false, raisedHand: 0},
|
||||
},
|
||||
channelId: 'channel-private',
|
||||
startTime: 123,
|
||||
|
|
@ -206,10 +206,10 @@ describe('useCallsState', () => {
|
|||
};
|
||||
const testNewCall1 = {
|
||||
...call1,
|
||||
participants: {
|
||||
'user-1': {id: 'user-1', muted: false, raisedHand: 0},
|
||||
'user-2': {id: 'user-2', muted: true, raisedHand: 0},
|
||||
'user-3': {id: 'user-3', muted: false, raisedHand: 123},
|
||||
sessions: {
|
||||
session1: {sessionId: 'session1', userId: 'user-1', muted: false, raisedHand: 0},
|
||||
session2: {sessionId: 'session2', userId: 'user-2', muted: true, raisedHand: 0},
|
||||
session3: {sessionId: 'session3', userId: 'user-3', muted: false, raisedHand: 123},
|
||||
},
|
||||
};
|
||||
const test = {
|
||||
|
|
@ -279,10 +279,10 @@ describe('useCallsState', () => {
|
|||
const expectedCallsState = {
|
||||
'channel-1': {
|
||||
id: 'call1',
|
||||
participants: {
|
||||
'user-1': {id: 'user-1', muted: false, raisedHand: 0},
|
||||
'user-2': {id: 'user-2', muted: true, raisedHand: 0},
|
||||
'user-3': {id: 'user-3', muted: true, raisedHand: 0},
|
||||
sessions: {
|
||||
session1: {sessionId: 'session1', userId: 'user-1', muted: false, raisedHand: 0},
|
||||
session2: {sessionId: 'session2', userId: 'user-2', muted: true, raisedHand: 0},
|
||||
session3: {sessionId: 'session3', userId: 'user-3', muted: true, raisedHand: 0},
|
||||
},
|
||||
channelId: 'channel-1',
|
||||
startTime: 123,
|
||||
|
|
@ -313,11 +313,11 @@ describe('useCallsState', () => {
|
|||
assert.deepEqual(result.current[2], initialCurrentCallState);
|
||||
|
||||
// test
|
||||
act(() => userJoinedCall('server1', 'channel-1', 'user-3'));
|
||||
act(() => userJoinedCall('server1', 'channel-1', 'user-3', 'session3'));
|
||||
assert.deepEqual((result.current[0] as CallsState).calls, expectedCallsState);
|
||||
assert.deepEqual(result.current[1], expectedChannelsWithCallsState);
|
||||
assert.deepEqual(result.current[2], expectedCurrentCallState);
|
||||
act(() => userJoinedCall('server1', 'invalid-channel', 'user-1'));
|
||||
act(() => userJoinedCall('server1', 'invalid-channel', 'user-1', 'session1'));
|
||||
assert.deepEqual((result.current[0] as CallsState).calls, expectedCallsState);
|
||||
assert.deepEqual(result.current[1], expectedChannelsWithCallsState);
|
||||
assert.deepEqual(result.current[2], expectedCurrentCallState);
|
||||
|
|
@ -341,8 +341,8 @@ describe('useCallsState', () => {
|
|||
const expectedCallsState = {
|
||||
'channel-1': {
|
||||
id: 'call1',
|
||||
participants: {
|
||||
'user-2': {id: 'user-2', muted: true, raisedHand: 0},
|
||||
sessions: {
|
||||
session2: {sessionId: 'session2', userId: 'user-2', muted: true, raisedHand: 0},
|
||||
},
|
||||
channelId: 'channel-1',
|
||||
startTime: 123,
|
||||
|
|
@ -373,11 +373,11 @@ describe('useCallsState', () => {
|
|||
assert.deepEqual(result.current[2], initialCurrentCallState);
|
||||
|
||||
// test
|
||||
act(() => userLeftCall('server1', 'channel-1', 'user-1'));
|
||||
act(() => userLeftCall('server1', 'channel-1', 'session1'));
|
||||
assert.deepEqual((result.current[0] as CallsState).calls, expectedCallsState);
|
||||
assert.deepEqual(result.current[1], expectedChannelsWithCallsState);
|
||||
assert.deepEqual(result.current[2], expectedCurrentCallState);
|
||||
act(() => userLeftCall('server1', 'invalid-channel', 'user-2'));
|
||||
act(() => userLeftCall('server1', 'invalid-channel', 'session2'));
|
||||
assert.deepEqual((result.current[0] as CallsState).calls, expectedCallsState);
|
||||
assert.deepEqual(result.current[1], expectedChannelsWithCallsState);
|
||||
assert.deepEqual(result.current[2], expectedCurrentCallState);
|
||||
|
|
@ -389,7 +389,7 @@ describe('useCallsState', () => {
|
|||
calls: {
|
||||
'channel-1': {
|
||||
...call1,
|
||||
screenOn: 'user-1',
|
||||
screenOn: 'session1',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -402,13 +402,13 @@ describe('useCallsState', () => {
|
|||
serverUrl: 'server1',
|
||||
myUserId: 'myUserId',
|
||||
...call1,
|
||||
screenOn: 'user-1',
|
||||
screenOn: 'session1',
|
||||
};
|
||||
const expectedCallsState = {
|
||||
'channel-1': {
|
||||
id: 'call1',
|
||||
participants: {
|
||||
'user-2': {id: 'user-2', muted: true, raisedHand: 0},
|
||||
sessions: {
|
||||
session2: {sessionId: 'session2', userId: 'user-2', muted: true, raisedHand: 0},
|
||||
},
|
||||
channelId: 'channel-1',
|
||||
startTime: 123,
|
||||
|
|
@ -439,7 +439,7 @@ describe('useCallsState', () => {
|
|||
assert.deepEqual(result.current[2], initialCurrentCallState);
|
||||
|
||||
// test
|
||||
act(() => userLeftCall('server1', 'channel-1', 'user-1'));
|
||||
act(() => userLeftCall('server1', 'channel-1', 'session1'));
|
||||
assert.deepEqual((result.current[0] as CallsState).calls, expectedCallsState);
|
||||
assert.deepEqual(result.current[1], expectedChannelsWithCallsState);
|
||||
assert.deepEqual(result.current[2], expectedCurrentCallState);
|
||||
|
|
@ -534,22 +534,22 @@ describe('useCallsState', () => {
|
|||
assert.deepEqual(result.current[2], initialCurrentCallState);
|
||||
|
||||
// test
|
||||
act(() => setUserMuted('server1', 'channel-1', 'user-1', true));
|
||||
assert.deepEqual((result.current[0] as CallsState).calls['channel-1'].participants['user-1'].muted, true);
|
||||
assert.deepEqual((result.current[2] as CurrentCall | null)?.participants['user-1'].muted, true);
|
||||
act(() => setUserMuted('server1', 'channel-1', 'session1', true));
|
||||
assert.deepEqual((result.current[0] as CallsState).calls['channel-1'].sessions.session1.muted, true);
|
||||
assert.deepEqual((result.current[2] as CurrentCall | null)?.sessions.session1.muted, true);
|
||||
act(() => {
|
||||
setUserMuted('server1', 'channel-1', 'user-1', false);
|
||||
setUserMuted('server1', 'channel-1', 'user-2', false);
|
||||
setUserMuted('server1', 'channel-1', 'session1', false);
|
||||
setUserMuted('server1', 'channel-1', 'session2', false);
|
||||
});
|
||||
assert.deepEqual((result.current[0] as CallsState).calls['channel-1'].participants['user-1'].muted, false);
|
||||
assert.deepEqual((result.current[0] as CallsState).calls['channel-1'].participants['user-2'].muted, false);
|
||||
assert.deepEqual((result.current[2] as CurrentCall | null)?.participants['user-1'].muted, false);
|
||||
assert.deepEqual((result.current[2] as CurrentCall | null)?.participants['user-2'].muted, false);
|
||||
act(() => setUserMuted('server1', 'channel-1', 'user-2', true));
|
||||
assert.deepEqual((result.current[0] as CallsState).calls['channel-1'].participants['user-2'].muted, true);
|
||||
assert.deepEqual((result.current[2] as CurrentCall | null)?.participants['user-2'].muted, true);
|
||||
assert.deepEqual((result.current[0] as CallsState).calls['channel-1'].sessions.session1.muted, false);
|
||||
assert.deepEqual((result.current[0] as CallsState).calls['channel-1'].sessions.session2.muted, false);
|
||||
assert.deepEqual((result.current[2] as CurrentCall | null)?.sessions.session1.muted, false);
|
||||
assert.deepEqual((result.current[2] as CurrentCall | null)?.sessions.session2.muted, false);
|
||||
act(() => setUserMuted('server1', 'channel-1', 'session2', true));
|
||||
assert.deepEqual((result.current[0] as CallsState).calls['channel-1'].sessions.session2.muted, true);
|
||||
assert.deepEqual((result.current[2] as CurrentCall | null)?.sessions.session2.muted, true);
|
||||
assert.deepEqual(result.current[0], initialCallsState);
|
||||
act(() => setUserMuted('server1', 'invalid-channel', 'user-1', true));
|
||||
act(() => setUserMuted('server1', 'invalid-channel', 'session1', true));
|
||||
assert.deepEqual(result.current[0], initialCallsState);
|
||||
});
|
||||
|
||||
|
|
@ -580,11 +580,11 @@ describe('useCallsState', () => {
|
|||
assert.deepEqual(result.current[2], initialCurrentCallState);
|
||||
|
||||
// test
|
||||
act(() => setCallScreenOn('server1', 'channel-1', 'user-1'));
|
||||
assert.deepEqual((result.current[0] as CallsState).calls['channel-1'].screenOn, 'user-1');
|
||||
act(() => setCallScreenOn('server1', 'channel-1', 'session1'));
|
||||
assert.deepEqual((result.current[0] as CallsState).calls['channel-1'].screenOn, 'session1');
|
||||
assert.deepEqual(result.current[1], initialChannelsWithCallsState);
|
||||
assert.deepEqual((result.current[2] as CurrentCall).screenOn, 'user-1');
|
||||
act(() => setCallScreenOff('server1', 'channel-1'));
|
||||
assert.deepEqual((result.current[2] as CurrentCall).screenOn, 'session1');
|
||||
act(() => setCallScreenOff('server1', 'channel-1', 'session1'));
|
||||
assert.deepEqual(result.current[0], initialCallsState);
|
||||
assert.deepEqual(result.current[1], initialChannelsWithCallsState);
|
||||
assert.deepEqual(result.current[2], initialCurrentCallState);
|
||||
|
|
@ -592,7 +592,7 @@ describe('useCallsState', () => {
|
|||
assert.deepEqual(result.current[0], initialCallsState);
|
||||
assert.deepEqual(result.current[1], initialChannelsWithCallsState);
|
||||
assert.deepEqual(result.current[2], initialCurrentCallState);
|
||||
act(() => setCallScreenOff('server1', 'invalid-channel'));
|
||||
act(() => setCallScreenOff('server1', 'invalid-channel', 'session1'));
|
||||
assert.deepEqual(result.current[0], initialCallsState);
|
||||
assert.deepEqual(result.current[1], initialChannelsWithCallsState);
|
||||
assert.deepEqual(result.current[2], initialCurrentCallState);
|
||||
|
|
@ -606,9 +606,9 @@ describe('useCallsState', () => {
|
|||
const expectedCalls = {
|
||||
'channel-1': {
|
||||
id: 'call1',
|
||||
participants: {
|
||||
'user-1': {id: 'user-1', muted: false, raisedHand: 0},
|
||||
'user-2': {id: 'user-2', muted: true, raisedHand: 345},
|
||||
sessions: {
|
||||
session1: {sessionId: 'session1', userId: 'user-1', muted: false, raisedHand: 0},
|
||||
session2: {sessionId: 'session2', userId: 'user-2', muted: true, raisedHand: 345},
|
||||
},
|
||||
channelId: 'channel-1',
|
||||
startTime: 123,
|
||||
|
|
@ -643,16 +643,16 @@ describe('useCallsState', () => {
|
|||
assert.deepEqual(result.current[1], initialCurrentCallState);
|
||||
|
||||
// test
|
||||
act(() => setRaisedHand('server1', 'channel-1', 'user-2', 345));
|
||||
act(() => setRaisedHand('server1', 'channel-1', 'session2', 345));
|
||||
assert.deepEqual((result.current[0] as CallsState).calls, expectedCalls);
|
||||
assert.deepEqual((result.current[1] as CurrentCall | null), expectedCurrentCallState);
|
||||
|
||||
act(() => setRaisedHand('server1', 'invalid-channel', 'user-1', 345));
|
||||
act(() => setRaisedHand('server1', 'invalid-channel', 'session1', 345));
|
||||
assert.deepEqual((result.current[0] as CallsState).calls, expectedCalls);
|
||||
assert.deepEqual((result.current[1] as CurrentCall | null), expectedCurrentCallState);
|
||||
|
||||
// unraise hand:
|
||||
act(() => setRaisedHand('server1', 'channel-1', 'user-2', 0));
|
||||
act(() => setRaisedHand('server1', 'channel-1', 'session2', 0));
|
||||
assert.deepEqual(result.current[0], initialCallsState);
|
||||
assert.deepEqual(result.current[1], initialCurrentCallState);
|
||||
});
|
||||
|
|
@ -665,9 +665,9 @@ describe('useCallsState', () => {
|
|||
};
|
||||
const newCall1 = {
|
||||
...call1,
|
||||
participants: {
|
||||
...call1.participants,
|
||||
myUserId: {id: 'myUserId', muted: true, raisedHand: 0},
|
||||
sessions: {
|
||||
...call1.sessions,
|
||||
mySessionId: {sessionId: 'mySessionId', userId: 'myUserId', muted: true, raisedHand: 0},
|
||||
},
|
||||
};
|
||||
const expectedCallsState = {
|
||||
|
|
@ -682,6 +682,7 @@ describe('useCallsState', () => {
|
|||
connected: true,
|
||||
serverUrl: 'server1',
|
||||
myUserId: 'myUserId',
|
||||
mySessionId: 'mySessionId',
|
||||
...newCall1,
|
||||
};
|
||||
|
||||
|
|
@ -696,14 +697,14 @@ describe('useCallsState', () => {
|
|||
// test
|
||||
act(() => {
|
||||
newCurrentCall('server1', 'channel-1', 'myUserId');
|
||||
userJoinedCall('server1', 'channel-1', 'myUserId');
|
||||
userJoinedCall('server1', 'channel-1', 'myUserId', 'mySessionId');
|
||||
});
|
||||
assert.deepEqual(result.current[0], expectedCallsState);
|
||||
assert.deepEqual(result.current[1], expectedCurrentCallState);
|
||||
|
||||
act(() => {
|
||||
myselfLeftCall();
|
||||
userLeftCall('server1', 'channel-1', 'myUserId');
|
||||
userLeftCall('server1', 'channel-1', 'mySessionId');
|
||||
});
|
||||
assert.deepEqual(result.current[0], initialCallsState);
|
||||
assert.deepEqual(result.current[1], null);
|
||||
|
|
@ -768,14 +769,14 @@ describe('useCallsState', () => {
|
|||
|
||||
// test joining a call and setting url:
|
||||
act(() => newCurrentCall('server1', 'channel-1', 'myUserId'));
|
||||
act(() => userJoinedCall('server1', 'channel-1', 'myUserId'));
|
||||
act(() => userJoinedCall('server1', 'channel-1', 'myUserId', 'mySessionId'));
|
||||
assert.deepEqual((result.current[1] as CurrentCall | null)?.screenShareURL, '');
|
||||
act(() => setScreenShareURL('testUrl'));
|
||||
assert.deepEqual((result.current[1] as CurrentCall | null)?.screenShareURL, 'testUrl');
|
||||
|
||||
act(() => {
|
||||
myselfLeftCall();
|
||||
userLeftCall('server1', 'channel-1', 'myUserId');
|
||||
userLeftCall('server1', 'channel-1', 'mySessionId');
|
||||
setScreenShareURL('test');
|
||||
});
|
||||
assert.deepEqual(result.current[0], initialCallsState);
|
||||
|
|
@ -790,9 +791,9 @@ describe('useCallsState', () => {
|
|||
};
|
||||
const newCall1 = {
|
||||
...call1,
|
||||
participants: {
|
||||
...call1.participants,
|
||||
myUserId: {id: 'myUserId', muted: true, raisedHand: 0},
|
||||
sessions: {
|
||||
...call1.sessions,
|
||||
mySessionId: {sessionId: 'mySessionId', userId: 'myUserId', muted: true, raisedHand: 0},
|
||||
},
|
||||
};
|
||||
const expectedCallsState = {
|
||||
|
|
@ -813,7 +814,7 @@ describe('useCallsState', () => {
|
|||
|
||||
// test
|
||||
act(() => newCurrentCall('server1', 'channel-1', 'myUserId'));
|
||||
act(() => userJoinedCall('server1', 'channel-1', 'myUserId'));
|
||||
act(() => userJoinedCall('server1', 'channel-1', 'myUserId', 'mySessionId'));
|
||||
assert.deepEqual((result.current[1] as CurrentCall | null)?.speakerphoneOn, false);
|
||||
act(() => setSpeakerPhone(true));
|
||||
assert.deepEqual((result.current[1] as CurrentCall | null)?.speakerphoneOn, true);
|
||||
|
|
@ -836,9 +837,9 @@ describe('useCallsState', () => {
|
|||
};
|
||||
const newCall1 = {
|
||||
...call1,
|
||||
participants: {
|
||||
...call1.participants,
|
||||
myUserId: {id: 'myUserId', muted: true, raisedHand: 0},
|
||||
sessions: {
|
||||
...call1.sessions,
|
||||
mySessionId: {sessionId: 'mySessionId', userId: 'myUserId', muted: true, raisedHand: 0},
|
||||
},
|
||||
};
|
||||
const expectedCallsState = {
|
||||
|
|
@ -868,7 +869,7 @@ describe('useCallsState', () => {
|
|||
|
||||
// test
|
||||
act(() => newCurrentCall('server1', 'channel-1', 'myUserId'));
|
||||
act(() => userJoinedCall('server1', 'channel-1', 'myUserId'));
|
||||
act(() => userJoinedCall('server1', 'channel-1', 'myUserId', 'mySessionId'));
|
||||
assert.deepEqual((result.current[1] as CurrentCall | null)?.audioDeviceInfo, defaultAudioDeviceInfo);
|
||||
act(() => setAudioDeviceInfo(newAudioDeviceInfo));
|
||||
assert.deepEqual((result.current[1] as CurrentCall | null)?.audioDeviceInfo, newAudioDeviceInfo);
|
||||
|
|
@ -890,9 +891,9 @@ describe('useCallsState', () => {
|
|||
};
|
||||
const newCall1: Call = {
|
||||
...call1,
|
||||
participants: {
|
||||
...call1.participants,
|
||||
myUserId: {id: 'myUserId', muted: true, raisedHand: 0},
|
||||
sessions: {
|
||||
...call1.sessions,
|
||||
mySessionId: {sessionId: 'mySessionId', userId: 'myUserId', muted: true, raisedHand: 0},
|
||||
},
|
||||
};
|
||||
const expectedCallsState: CallsState = {
|
||||
|
|
@ -906,6 +907,7 @@ describe('useCallsState', () => {
|
|||
...DefaultCurrentCall,
|
||||
serverUrl: 'server1',
|
||||
myUserId: 'myUserId',
|
||||
mySessionId: 'mySessionId',
|
||||
connected: true,
|
||||
...newCall1,
|
||||
};
|
||||
|
|
@ -930,7 +932,7 @@ describe('useCallsState', () => {
|
|||
act(() => {
|
||||
setMicPermissionsGranted(false);
|
||||
newCurrentCall('server1', 'channel-1', 'myUserId');
|
||||
userJoinedCall('server1', 'channel-1', 'myUserId');
|
||||
userJoinedCall('server1', 'channel-1', 'myUserId', 'mySessionId');
|
||||
});
|
||||
assert.deepEqual(result.current[0], expectedCallsState);
|
||||
assert.deepEqual(result.current[1], expectedCurrentCallState);
|
||||
|
|
@ -950,7 +952,7 @@ describe('useCallsState', () => {
|
|||
|
||||
act(() => {
|
||||
myselfLeftCall();
|
||||
userLeftCall('server1', 'channel-1', 'myUserId');
|
||||
userLeftCall('server1', 'channel-1', 'mySessionId');
|
||||
});
|
||||
assert.deepEqual(result.current[0], initialCallsState);
|
||||
assert.deepEqual(result.current[1], null);
|
||||
|
|
@ -964,9 +966,9 @@ describe('useCallsState', () => {
|
|||
};
|
||||
const newCall1: Call = {
|
||||
...call1,
|
||||
participants: {
|
||||
...call1.participants,
|
||||
myUserId: {id: 'myUserId', muted: true, raisedHand: 0},
|
||||
sessions: {
|
||||
...call1.sessions,
|
||||
mySessionId: {sessionId: 'mySessionId', userId: 'myUserId', muted: true, raisedHand: 0},
|
||||
},
|
||||
};
|
||||
const expectedCallsState: CallsState = {
|
||||
|
|
@ -980,6 +982,7 @@ describe('useCallsState', () => {
|
|||
...DefaultCurrentCall,
|
||||
serverUrl: 'server1',
|
||||
myUserId: 'myUserId',
|
||||
mySessionId: 'mySessionId',
|
||||
connected: true,
|
||||
...newCall1,
|
||||
};
|
||||
|
|
@ -995,7 +998,7 @@ describe('useCallsState', () => {
|
|||
// join call
|
||||
act(() => {
|
||||
newCurrentCall('server1', 'channel-1', 'myUserId');
|
||||
userJoinedCall('server1', 'channel-1', 'myUserId');
|
||||
userJoinedCall('server1', 'channel-1', 'myUserId', 'mySessionId');
|
||||
});
|
||||
assert.deepEqual(result.current[0], expectedCallsState);
|
||||
assert.deepEqual(result.current[1], currentCallNoAlertNoDismissed);
|
||||
|
|
@ -1058,14 +1061,14 @@ describe('useCallsState', () => {
|
|||
assert.deepEqual(result.current[1], initialCurrentCallState);
|
||||
|
||||
// test
|
||||
act(() => setUserVoiceOn('channel-1', 'user-1', true));
|
||||
assert.deepEqual(result.current[1], {...initialCurrentCallState, voiceOn: {'user-1': true}});
|
||||
act(() => setUserVoiceOn('channel-1', 'session1', true));
|
||||
assert.deepEqual(result.current[1], {...initialCurrentCallState, voiceOn: {session1: true}});
|
||||
assert.deepEqual(result.current[0], initialCallsState);
|
||||
act(() => setUserVoiceOn('channel-1', 'user-2', true));
|
||||
assert.deepEqual(result.current[1], {...initialCurrentCallState, voiceOn: {'user-1': true, 'user-2': true}});
|
||||
act(() => setUserVoiceOn('channel-1', 'session2', true));
|
||||
assert.deepEqual(result.current[1], {...initialCurrentCallState, voiceOn: {session1: true, session2: true}});
|
||||
assert.deepEqual(result.current[0], initialCallsState);
|
||||
act(() => setUserVoiceOn('channel-1', 'user-1', false));
|
||||
assert.deepEqual(result.current[1], {...initialCurrentCallState, voiceOn: {'user-2': true}});
|
||||
act(() => setUserVoiceOn('channel-1', 'session1', false));
|
||||
assert.deepEqual(result.current[1], {...initialCurrentCallState, voiceOn: {session2: true}});
|
||||
assert.deepEqual(result.current[0], initialCallsState);
|
||||
|
||||
// test that voice state is cleared on reconnect
|
||||
|
|
@ -1128,22 +1131,24 @@ describe('useCallsState', () => {
|
|||
{name: 'smile', latestTimestamp: 202, count: 1, literal: undefined},
|
||||
{name: '+1', latestTimestamp: 145, count: 2, literal: undefined},
|
||||
],
|
||||
participants: {
|
||||
...initialCurrentCallState.participants,
|
||||
'user-1': {
|
||||
...initialCurrentCallState.participants['user-1'],
|
||||
sessions: {
|
||||
...initialCurrentCallState.sessions,
|
||||
session1: {
|
||||
...initialCurrentCallState.sessions.session1,
|
||||
reaction: {
|
||||
user_id: 'user-1',
|
||||
session_id: 'session1',
|
||||
emoji: {name: 'smile', unified: 'something'},
|
||||
timestamp: 202,
|
||||
},
|
||||
},
|
||||
'user-2': {
|
||||
...initialCurrentCallState.participants['user-2'],
|
||||
session2: {
|
||||
...initialCurrentCallState.sessions.session2,
|
||||
reaction: {
|
||||
user_id: 'user-2',
|
||||
session_id: 'session2',
|
||||
emoji: {name: '+1', unified: 'something'},
|
||||
timestamp: 123,
|
||||
timestamp: 145,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -1164,16 +1169,19 @@ describe('useCallsState', () => {
|
|||
act(() => {
|
||||
userReacted('server1', 'channel-1', {
|
||||
user_id: 'user-2',
|
||||
session_id: 'session2',
|
||||
emoji: {name: '+1', unified: 'something'},
|
||||
timestamp: 123,
|
||||
});
|
||||
userReacted('server1', 'channel-1', {
|
||||
user_id: 'user-1',
|
||||
user_id: 'user-2',
|
||||
session_id: 'session2',
|
||||
emoji: {name: '+1', unified: 'something'},
|
||||
timestamp: 145,
|
||||
});
|
||||
userReacted('server1', 'channel-1', {
|
||||
user_id: 'user-1',
|
||||
session_id: 'session1',
|
||||
emoji: {name: 'smile', unified: 'something'},
|
||||
timestamp: 202,
|
||||
});
|
||||
|
|
@ -1319,8 +1327,8 @@ describe('useCallsState', () => {
|
|||
};
|
||||
const callIStarted: Call = {
|
||||
id: 'callIStartedid',
|
||||
participants: {
|
||||
'user-5': {id: 'user-5', muted: false, raisedHand: 0},
|
||||
sessions: {
|
||||
session5: {sessionId: 'session5', userId: 'user-5', muted: false, raisedHand: 0},
|
||||
},
|
||||
channelId: 'channel-private2',
|
||||
startTime: 123,
|
||||
|
|
@ -1332,7 +1340,7 @@ describe('useCallsState', () => {
|
|||
};
|
||||
const callImIn: Call = {
|
||||
id: 'callImInId',
|
||||
participants: {},
|
||||
sessions: {},
|
||||
channelId: 'channel-private2',
|
||||
startTime: 123,
|
||||
screenOn: '',
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ export const setCallForChannel = (serverUrl: string, channelId: string, enabled?
|
|||
}
|
||||
};
|
||||
|
||||
export const userJoinedCall = (serverUrl: string, channelId: string, userId: string) => {
|
||||
export const userJoinedCall = (serverUrl: string, channelId: string, userId: string, sessionId: string) => {
|
||||
const callsState = getCallsState(serverUrl);
|
||||
if (!callsState.calls[channelId]) {
|
||||
return;
|
||||
|
|
@ -227,10 +227,11 @@ export const userJoinedCall = (serverUrl: string, channelId: string, userId: str
|
|||
|
||||
const nextCall = {
|
||||
...callsState.calls[channelId],
|
||||
participants: {...callsState.calls[channelId].participants},
|
||||
sessions: {...callsState.calls[channelId].sessions},
|
||||
};
|
||||
nextCall.participants[userId] = {
|
||||
id: userId,
|
||||
nextCall.sessions[sessionId] = {
|
||||
userId,
|
||||
sessionId,
|
||||
muted: true,
|
||||
raisedHand: 0,
|
||||
};
|
||||
|
|
@ -242,46 +243,48 @@ export const userJoinedCall = (serverUrl: string, channelId: string, userId: str
|
|||
const currentCall = getCurrentCall();
|
||||
if (currentCall && currentCall.channelId === channelId) {
|
||||
const voiceOn = {...currentCall.voiceOn};
|
||||
delete voiceOn[userId];
|
||||
delete voiceOn[sessionId];
|
||||
|
||||
const nextCurrentCall = {
|
||||
...currentCall,
|
||||
participants: {...currentCall.participants, [userId]: nextCall.participants[userId]},
|
||||
sessions: {...currentCall.sessions, [sessionId]: nextCall.sessions[sessionId]},
|
||||
voiceOn,
|
||||
};
|
||||
|
||||
// If this is the currentUser, that means we've connected to the call we created.
|
||||
if (userId === nextCurrentCall.myUserId) {
|
||||
if (userId === nextCurrentCall.myUserId && !nextCurrentCall.connected) {
|
||||
nextCurrentCall.connected = true;
|
||||
nextCurrentCall.mySessionId = sessionId;
|
||||
}
|
||||
|
||||
setCurrentCall(nextCurrentCall);
|
||||
}
|
||||
|
||||
// We've joined (from whatever client), so remove that call's notification
|
||||
if (userId === callsState.myUserId) {
|
||||
removeIncomingCall(serverUrl, callsState.calls[channelId].id, channelId);
|
||||
}
|
||||
};
|
||||
|
||||
export const userLeftCall = (serverUrl: string, channelId: string, userId: string) => {
|
||||
export const userLeftCall = (serverUrl: string, channelId: string, sessionId: string) => {
|
||||
const callsState = getCallsState(serverUrl);
|
||||
if (!callsState.calls[channelId]?.participants[userId]) {
|
||||
if (!callsState.calls[channelId]?.sessions[sessionId]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextCall = {
|
||||
...callsState.calls[channelId],
|
||||
participants: {...callsState.calls[channelId].participants},
|
||||
sessions: {...callsState.calls[channelId].sessions},
|
||||
};
|
||||
delete nextCall.participants[userId];
|
||||
delete nextCall.sessions[sessionId];
|
||||
|
||||
// If they were screensharing, remove that.
|
||||
if (nextCall.screenOn === userId) {
|
||||
if (nextCall.screenOn === sessionId) {
|
||||
nextCall.screenOn = '';
|
||||
}
|
||||
|
||||
const nextCalls = {...callsState.calls};
|
||||
if (Object.keys(nextCall.participants).length === 0) {
|
||||
if (Object.keys(nextCall.sessions).length === 0) {
|
||||
delete nextCalls[channelId];
|
||||
|
||||
const callId = callsState.calls[channelId].id;
|
||||
|
|
@ -303,25 +306,24 @@ export const userLeftCall = (serverUrl: string, channelId: string, userId: strin
|
|||
return;
|
||||
}
|
||||
|
||||
// Was the user me?
|
||||
if (userId === callsState.myUserId) {
|
||||
if (sessionId === currentCall.mySessionId) {
|
||||
myselfLeftCall();
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear them from the voice list
|
||||
const voiceOn = {...currentCall.voiceOn};
|
||||
delete voiceOn[userId];
|
||||
delete voiceOn[sessionId];
|
||||
|
||||
const nextCurrentCall = {
|
||||
...currentCall,
|
||||
participants: {...currentCall.participants},
|
||||
sessions: {...currentCall.sessions},
|
||||
voiceOn,
|
||||
};
|
||||
delete nextCurrentCall.participants[userId];
|
||||
delete nextCurrentCall.sessions[sessionId];
|
||||
|
||||
// If they were screensharing, remove that.
|
||||
if (nextCurrentCall.screenOn === userId) {
|
||||
if (nextCurrentCall.screenOn === sessionId) {
|
||||
nextCurrentCall.screenOn = '';
|
||||
}
|
||||
|
||||
|
|
@ -407,18 +409,18 @@ export const callEnded = (serverUrl: string, channelId: string) => {
|
|||
// currentCall is set to null by the disconnect.
|
||||
};
|
||||
|
||||
export const setUserMuted = (serverUrl: string, channelId: string, userId: string, muted: boolean) => {
|
||||
export const setUserMuted = (serverUrl: string, channelId: string, sessionId: string, muted: boolean) => {
|
||||
const callsState = getCallsState(serverUrl);
|
||||
if (!callsState.calls[channelId] || !callsState.calls[channelId].participants[userId]) {
|
||||
if (!callsState.calls[channelId] || !callsState.calls[channelId].sessions[sessionId]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextUser = {...callsState.calls[channelId].participants[userId], muted};
|
||||
const nextUser = {...callsState.calls[channelId].sessions[sessionId], muted};
|
||||
const nextCall = {
|
||||
...callsState.calls[channelId],
|
||||
participants: {...callsState.calls[channelId].participants},
|
||||
sessions: {...callsState.calls[channelId].sessions},
|
||||
};
|
||||
nextCall.participants[userId] = nextUser;
|
||||
nextCall.sessions[sessionId] = nextUser;
|
||||
const nextCalls = {...callsState.calls};
|
||||
nextCalls[channelId] = nextCall;
|
||||
setCallsState(serverUrl, {...callsState, calls: nextCalls});
|
||||
|
|
@ -431,15 +433,15 @@ export const setUserMuted = (serverUrl: string, channelId: string, userId: strin
|
|||
|
||||
const nextCurrentCall = {
|
||||
...currentCall,
|
||||
participants: {
|
||||
...currentCall.participants,
|
||||
[userId]: {...currentCall.participants[userId], muted},
|
||||
sessions: {
|
||||
...currentCall.sessions,
|
||||
[sessionId]: {...currentCall.sessions[sessionId], muted},
|
||||
},
|
||||
};
|
||||
setCurrentCall(nextCurrentCall);
|
||||
};
|
||||
|
||||
export const setUserVoiceOn = (channelId: string, userId: string, voiceOn: boolean) => {
|
||||
export const setUserVoiceOn = (channelId: string, sessionId: string, voiceOn: boolean) => {
|
||||
const currentCall = getCurrentCall();
|
||||
if (!currentCall || currentCall.channelId !== channelId) {
|
||||
return;
|
||||
|
|
@ -447,9 +449,9 @@ export const setUserVoiceOn = (channelId: string, userId: string, voiceOn: boole
|
|||
|
||||
const nextVoiceOn = {...currentCall.voiceOn};
|
||||
if (voiceOn) {
|
||||
nextVoiceOn[userId] = true;
|
||||
nextVoiceOn[sessionId] = true;
|
||||
} else {
|
||||
delete nextVoiceOn[userId];
|
||||
delete nextVoiceOn[sessionId];
|
||||
}
|
||||
|
||||
const nextCurrentCall = {
|
||||
|
|
@ -459,18 +461,18 @@ export const setUserVoiceOn = (channelId: string, userId: string, voiceOn: boole
|
|||
setCurrentCall(nextCurrentCall);
|
||||
};
|
||||
|
||||
export const setRaisedHand = (serverUrl: string, channelId: string, userId: string, timestamp: number) => {
|
||||
export const setRaisedHand = (serverUrl: string, channelId: string, sessionId: string, timestamp: number) => {
|
||||
const callsState = getCallsState(serverUrl);
|
||||
if (!callsState.calls[channelId] || !callsState.calls[channelId].participants[userId]) {
|
||||
if (!callsState.calls[channelId] || !callsState.calls[channelId].sessions[sessionId]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextUser = {...callsState.calls[channelId].participants[userId], raisedHand: timestamp};
|
||||
const nextUser = {...callsState.calls[channelId].sessions[sessionId], raisedHand: timestamp};
|
||||
const nextCall = {
|
||||
...callsState.calls[channelId],
|
||||
participants: {...callsState.calls[channelId].participants},
|
||||
sessions: {...callsState.calls[channelId].sessions},
|
||||
};
|
||||
nextCall.participants[userId] = nextUser;
|
||||
nextCall.sessions[sessionId] = nextUser;
|
||||
const nextCalls = {...callsState.calls};
|
||||
nextCalls[channelId] = nextCall;
|
||||
setCallsState(serverUrl, {...callsState, calls: nextCalls});
|
||||
|
|
@ -483,21 +485,21 @@ export const setRaisedHand = (serverUrl: string, channelId: string, userId: stri
|
|||
|
||||
const nextCurrentCall = {
|
||||
...currentCall,
|
||||
participants: {
|
||||
...currentCall.participants,
|
||||
[userId]: {...currentCall.participants[userId], raisedHand: timestamp},
|
||||
sessions: {
|
||||
...currentCall.sessions,
|
||||
[sessionId]: {...currentCall.sessions[sessionId], raisedHand: timestamp},
|
||||
},
|
||||
};
|
||||
setCurrentCall(nextCurrentCall);
|
||||
};
|
||||
|
||||
export const setCallScreenOn = (serverUrl: string, channelId: string, userId: string) => {
|
||||
export const setCallScreenOn = (serverUrl: string, channelId: string, sessionId: string) => {
|
||||
const callsState = getCallsState(serverUrl);
|
||||
if (!callsState.calls[channelId] || !callsState.calls[channelId].participants[userId]) {
|
||||
if (!callsState.calls[channelId] || !callsState.calls[channelId].sessions[sessionId]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextCall = {...callsState.calls[channelId], screenOn: userId};
|
||||
const nextCall = {...callsState.calls[channelId], screenOn: sessionId};
|
||||
const nextCalls = {...callsState.calls};
|
||||
nextCalls[channelId] = nextCall;
|
||||
setCallsState(serverUrl, {...callsState, calls: nextCalls});
|
||||
|
|
@ -510,14 +512,14 @@ export const setCallScreenOn = (serverUrl: string, channelId: string, userId: st
|
|||
|
||||
const nextCurrentCall = {
|
||||
...currentCall,
|
||||
screenOn: userId,
|
||||
screenOn: sessionId,
|
||||
};
|
||||
setCurrentCall(nextCurrentCall);
|
||||
};
|
||||
|
||||
export const setCallScreenOff = (serverUrl: string, channelId: string) => {
|
||||
export const setCallScreenOff = (serverUrl: string, channelId: string, sessionId: string) => {
|
||||
const callsState = getCallsState(serverUrl);
|
||||
if (!callsState.calls[channelId]) {
|
||||
if (!callsState.calls[channelId] || callsState.calls[channelId].screenOn !== sessionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -634,16 +636,16 @@ export const userReacted = (serverUrl: string, channelId: string, reaction: User
|
|||
}
|
||||
|
||||
// Update the participant.
|
||||
const nextParticipants = {...currentCall.participants};
|
||||
if (nextParticipants[reaction.user_id]) {
|
||||
const nextUser = {...nextParticipants[reaction.user_id], reaction};
|
||||
nextParticipants[reaction.user_id] = nextUser;
|
||||
const nextSessions = {...currentCall.sessions};
|
||||
if (nextSessions[reaction.session_id]) {
|
||||
const nextUser = {...nextSessions[reaction.session_id], reaction};
|
||||
nextSessions[reaction.session_id] = nextUser;
|
||||
}
|
||||
|
||||
const nextCurrentCall: CurrentCall = {
|
||||
...currentCall,
|
||||
reactionStream: newReactionStream,
|
||||
participants: nextParticipants,
|
||||
sessions: nextSessions,
|
||||
};
|
||||
setCurrentCall(nextCurrentCall);
|
||||
|
||||
|
|
@ -661,17 +663,17 @@ const userReactionTimeout = (serverUrl: string, channelId: string, reaction: Use
|
|||
// Remove the reaction only if it was the last time that emoji was used.
|
||||
const newReactions = currentCall.reactionStream.filter((e) => e.latestTimestamp !== reaction.timestamp);
|
||||
|
||||
const nextParticipants = {...currentCall.participants};
|
||||
if (nextParticipants[reaction.user_id] && nextParticipants[reaction.user_id].reaction?.timestamp === reaction.timestamp) {
|
||||
const nextUser = {...nextParticipants[reaction.user_id]};
|
||||
const nextSessions = {...currentCall.sessions};
|
||||
if (nextSessions[reaction.session_id] && nextSessions[reaction.session_id].reaction?.timestamp === reaction.timestamp) {
|
||||
const nextUser = {...nextSessions[reaction.session_id]};
|
||||
delete nextUser.reaction;
|
||||
nextParticipants[reaction.user_id] = nextUser;
|
||||
nextSessions[reaction.session_id] = nextUser;
|
||||
}
|
||||
|
||||
const nextCurrentCall: CurrentCall = {
|
||||
...currentCall,
|
||||
reactionStream: newReactions,
|
||||
participants: nextParticipants,
|
||||
sessions: nextSessions,
|
||||
};
|
||||
setCurrentCall(nextCurrentCall);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export const DefaultIncomingCalls: IncomingCalls = {
|
|||
|
||||
export type Call = {
|
||||
id: string;
|
||||
participants: Dictionary<CallParticipant>;
|
||||
sessions: Dictionary<CallSession>;
|
||||
channelId: string;
|
||||
startTime: number;
|
||||
screenOn: string;
|
||||
|
|
@ -63,7 +63,7 @@ export type Call = {
|
|||
|
||||
export const DefaultCall: Call = {
|
||||
id: '',
|
||||
participants: {},
|
||||
sessions: {},
|
||||
channelId: '',
|
||||
startTime: 0,
|
||||
screenOn: '',
|
||||
|
|
@ -85,6 +85,7 @@ export type CurrentCall = Call & {
|
|||
connected: boolean;
|
||||
serverUrl: string;
|
||||
myUserId: string;
|
||||
mySessionId: string;
|
||||
screenShareURL: string;
|
||||
speakerphoneOn: boolean;
|
||||
audioDeviceInfo: AudioDeviceInfo;
|
||||
|
|
@ -100,6 +101,7 @@ export const DefaultCurrentCall: CurrentCall = {
|
|||
connected: false,
|
||||
serverUrl: '',
|
||||
myUserId: '',
|
||||
mySessionId: '',
|
||||
screenShareURL: '',
|
||||
speakerphoneOn: false,
|
||||
audioDeviceInfo: {availableAudioDeviceList: [], selectedAudioDevice: AudioDevice.None},
|
||||
|
|
@ -110,8 +112,9 @@ export const DefaultCurrentCall: CurrentCall = {
|
|||
callQualityAlertDismissed: 0,
|
||||
};
|
||||
|
||||
export type CallParticipant = {
|
||||
id: string;
|
||||
export type CallSession = {
|
||||
sessionId: string;
|
||||
userId: string;
|
||||
muted: boolean;
|
||||
raisedHand: number;
|
||||
userModel?: UserModel;
|
||||
|
|
@ -134,11 +137,13 @@ export type CallsConnection = {
|
|||
export type CallsConfigState = CallsConfig & {
|
||||
AllowEnableCalls: boolean;
|
||||
pluginEnabled: boolean;
|
||||
version: CallsVersion;
|
||||
last_retrieved_at: number;
|
||||
}
|
||||
|
||||
export const DefaultCallsConfig: CallsConfigState = {
|
||||
pluginEnabled: false,
|
||||
version: {},
|
||||
ICEServers: [], // deprecated
|
||||
ICEServersConfigs: [],
|
||||
AllowEnableCalls: false,
|
||||
|
|
@ -152,6 +157,7 @@ export const DefaultCallsConfig: CallsConfigState = {
|
|||
AllowScreenSharing: true,
|
||||
EnableSimulcast: false,
|
||||
EnableRinging: false,
|
||||
EnableTranscriptions: false,
|
||||
};
|
||||
|
||||
export type ApiResp = {
|
||||
|
|
@ -182,3 +188,8 @@ export type AudioDeviceInfo = {
|
|||
availableAudioDeviceList: AudioDevice[];
|
||||
selectedAudioDevice: AudioDevice;
|
||||
};
|
||||
|
||||
export type CallsVersion = {
|
||||
version?: string;
|
||||
build?: string;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,26 +9,27 @@ import {NOTIFICATION_SUB_TYPE} from '@constants/push_notification';
|
|||
import {isMinimumServerVersion} from '@utils/helpers';
|
||||
import {displayUsername} from '@utils/user';
|
||||
|
||||
import type {CallParticipant, CallsTheme} from '@calls/types/calls';
|
||||
import type {CallSession, CallsTheme, CallsVersion} from '@calls/types/calls';
|
||||
import type {CallsConfig} from '@mattermost/calls/lib/types';
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
import type {IntlShape} from 'react-intl';
|
||||
import type {RTCIceServer} from 'react-native-webrtc';
|
||||
|
||||
const callsMessageRegex = /^\u200b.* is inviting you to a call$/;
|
||||
|
||||
export function sortParticipants(locale: string, teammateNameDisplay: string, participants?: Dictionary<CallParticipant>, presenterID?: string): CallParticipant[] {
|
||||
if (!participants) {
|
||||
export function sortSessions(locale: string, teammateNameDisplay: string, sessions?: Dictionary<CallSession>, presenterID?: string): CallSession[] {
|
||||
if (!sessions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const users = Object.values(participants);
|
||||
const sessns = Object.values(sessions);
|
||||
|
||||
return users.sort(sortByName(locale, teammateNameDisplay)).sort(sortByState(presenterID));
|
||||
return sessns.sort(sortByName(locale, teammateNameDisplay)).sort(sortByState(presenterID));
|
||||
}
|
||||
|
||||
const sortByName = (locale: string, teammateNameDisplay: string) => {
|
||||
return (a: CallParticipant, b: CallParticipant) => {
|
||||
return (a: CallSession, b: CallSession) => {
|
||||
const nameA = displayUsername(a.userModel, locale, teammateNameDisplay);
|
||||
const nameB = displayUsername(b.userModel, locale, teammateNameDisplay);
|
||||
return nameA.localeCompare(nameB);
|
||||
|
|
@ -36,10 +37,10 @@ const sortByName = (locale: string, teammateNameDisplay: string) => {
|
|||
};
|
||||
|
||||
const sortByState = (presenterID?: string) => {
|
||||
return (a: CallParticipant, b: CallParticipant) => {
|
||||
if (a.id === presenterID) {
|
||||
return (a: CallSession, b: CallSession) => {
|
||||
if (a.sessionId === presenterID) {
|
||||
return -1;
|
||||
} else if (b.id === presenterID) {
|
||||
} else if (b.sessionId === presenterID) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
@ -61,13 +62,13 @@ const sortByState = (presenterID?: string) => {
|
|||
};
|
||||
};
|
||||
|
||||
export function getHandsRaised(participants: Dictionary<CallParticipant>) {
|
||||
return Object.values(participants).filter((p) => p.raisedHand);
|
||||
export function getHandsRaised(sessions: Dictionary<CallSession>) {
|
||||
return Object.values(sessions).filter((s) => s.raisedHand);
|
||||
}
|
||||
|
||||
export function getHandsRaisedNames(participants: CallParticipant[], currentUserId: string, locale: string, teammateNameDisplay: string, intl: IntlShape) {
|
||||
return participants.sort((a, b) => a.raisedHand - b.raisedHand).map((p) => {
|
||||
if (p.id === currentUserId) {
|
||||
export function getHandsRaisedNames(sessions: CallSession[], sessionId: string, locale: string, teammateNameDisplay: string, intl: IntlShape) {
|
||||
return sessions.sort((a, b) => a.raisedHand - b.raisedHand).map((p) => {
|
||||
if (p.sessionId === sessionId) {
|
||||
return intl.formatMessage({id: 'mobile.calls_you_2', defaultMessage: 'You'});
|
||||
}
|
||||
return displayUsername(p.userModel, locale, teammateNameDisplay);
|
||||
|
|
@ -87,6 +88,15 @@ export function isSupportedServerCalls(serverVersion?: string) {
|
|||
return false;
|
||||
}
|
||||
|
||||
export function isMultiSessionSupported(callsVersion: CallsVersion) {
|
||||
return isMinimumServerVersion(
|
||||
callsVersion.version,
|
||||
Calls.MultiSessionCallsVersion.MAJOR_VERSION,
|
||||
Calls.MultiSessionCallsVersion.MIN_VERSION,
|
||||
Calls.MultiSessionCallsVersion.PATCH_VERSION,
|
||||
);
|
||||
}
|
||||
|
||||
export function isCallsCustomMessage(post: PostModel | Post): boolean {
|
||||
return Boolean(post.type && post.type === Post.POST_TYPES.CUSTOM_CALLS);
|
||||
}
|
||||
|
|
@ -154,6 +164,34 @@ export function makeCallsTheme(theme: Theme): CallsTheme {
|
|||
return newTheme;
|
||||
}
|
||||
|
||||
interface HasUserId {
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export function userIds<T extends HasUserId>(hasUserId: T[]): string[] {
|
||||
const ids: string[] = [];
|
||||
const seen: Record<string, boolean> = {};
|
||||
for (const p of hasUserId) {
|
||||
if (!seen[p.userId]) {
|
||||
ids.push(p.userId);
|
||||
seen[p.userId] = true;
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
export function fillUserModels(sessions: Dictionary<CallSession>, models: UserModel[]) {
|
||||
const idToModel = models.reduce((accum, cur) => {
|
||||
accum[cur.id] = cur;
|
||||
return accum;
|
||||
}, {} as Dictionary<UserModel>);
|
||||
const next = {...sessions};
|
||||
for (const participant of Object.values(next)) {
|
||||
participant.userModel = idToModel[participant.userId];
|
||||
}
|
||||
return sessions;
|
||||
}
|
||||
|
||||
export function isCallsStartedMessage(payload?: NotificationData) {
|
||||
if (payload?.sub_type === NOTIFICATION_SUB_TYPE.CALLS) {
|
||||
return true;
|
||||
|
|
|
|||
8
package-lock.json
generated
8
package-lock.json
generated
|
|
@ -17,7 +17,7 @@
|
|||
"@formatjs/intl-pluralrules": "5.2.4",
|
||||
"@formatjs/intl-relativetimeformat": "11.2.4",
|
||||
"@gorhom/bottom-sheet": "4.4.7",
|
||||
"@mattermost/calls": "github:mattermost/calls-common#v0.17.0",
|
||||
"@mattermost/calls": "github:mattermost/calls-common#v0.21.0",
|
||||
"@mattermost/compass-icons": "0.1.38",
|
||||
"@mattermost/react-native-emm": "1.3.5",
|
||||
"@mattermost/react-native-network-client": "1.4.1",
|
||||
|
|
@ -3406,7 +3406,7 @@
|
|||
"node_modules/@mattermost/calls": {
|
||||
"name": "@calls/common",
|
||||
"version": "0.14.0",
|
||||
"resolved": "git+ssh://git@github.com/mattermost/calls-common.git#43011d8e9c7985b0542628329be098715a43dc03"
|
||||
"resolved": "git+ssh://git@github.com/mattermost/calls-common.git#4bca3651b2eb5d46fab9a29af000d21d9f56727c"
|
||||
},
|
||||
"node_modules/@mattermost/commonmark": {
|
||||
"version": "0.30.1-0",
|
||||
|
|
@ -25387,8 +25387,8 @@
|
|||
}
|
||||
},
|
||||
"@mattermost/calls": {
|
||||
"version": "git+ssh://git@github.com/mattermost/calls-common.git#43011d8e9c7985b0542628329be098715a43dc03",
|
||||
"from": "@mattermost/calls@github:mattermost/calls-common#v0.17.0"
|
||||
"version": "git+ssh://git@github.com/mattermost/calls-common.git#4bca3651b2eb5d46fab9a29af000d21d9f56727c",
|
||||
"from": "@mattermost/calls@github:mattermost/calls-common#v0.21.0"
|
||||
},
|
||||
"@mattermost/commonmark": {
|
||||
"version": "0.30.1-0",
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
"@formatjs/intl-pluralrules": "5.2.4",
|
||||
"@formatjs/intl-relativetimeformat": "11.2.4",
|
||||
"@gorhom/bottom-sheet": "4.4.7",
|
||||
"@mattermost/calls": "github:mattermost/calls-common#v0.17.0",
|
||||
"@mattermost/calls": "github:mattermost/calls-common#v0.21.0",
|
||||
"@mattermost/compass-icons": "0.1.38",
|
||||
"@mattermost/react-native-emm": "1.3.5",
|
||||
"@mattermost/react-native-network-client": "1.4.1",
|
||||
|
|
|
|||
Loading…
Reference in a new issue