forked from Ivasoft/mattermost-mobile
MM-45746 - Handle call_end ws event and '/call end' slash command (#6542)
This commit is contained in:
committed by
GitHub
parent
4dfb9fb60c
commit
f61ea842af
@@ -11,11 +11,18 @@ import {fetchStatusByIds} from '@actions/remote/user';
|
||||
import {loadConfigAndCalls} from '@calls/actions/calls';
|
||||
import {
|
||||
handleCallChannelDisabled,
|
||||
handleCallChannelEnabled, handleCallScreenOff, handleCallScreenOn, handleCallStarted,
|
||||
handleCallChannelEnabled, handleCallEnded,
|
||||
handleCallScreenOff,
|
||||
handleCallScreenOn,
|
||||
handleCallStarted,
|
||||
handleCallUserConnected,
|
||||
handleCallUserDisconnected,
|
||||
handleCallUserMuted, handleCallUserRaiseHand,
|
||||
handleCallUserUnmuted, handleCallUserUnraiseHand, handleCallUserVoiceOff, handleCallUserVoiceOn,
|
||||
handleCallUserMuted,
|
||||
handleCallUserRaiseHand,
|
||||
handleCallUserUnmuted,
|
||||
handleCallUserUnraiseHand,
|
||||
handleCallUserVoiceOff,
|
||||
handleCallUserVoiceOn,
|
||||
} from '@calls/connection/websocket_event_handlers';
|
||||
import {isSupportedServerCalls} from '@calls/utils';
|
||||
import {Events, Screens, WebsocketEvents} from '@constants';
|
||||
@@ -402,6 +409,9 @@ export async function handleEvent(serverUrl: string, msg: WebSocketMessage) {
|
||||
case WebsocketEvents.CALLS_USER_UNRAISE_HAND:
|
||||
handleCallUserUnraiseHand(serverUrl, msg);
|
||||
break;
|
||||
case WebsocketEvents.CALLS_CALL_END:
|
||||
handleCallEnded(serverUrl, msg);
|
||||
break;
|
||||
|
||||
case WebsocketEvents.GROUP_RECEIVED:
|
||||
handleGroupReceivedEvent(serverUrl, msg);
|
||||
|
||||
@@ -3,13 +3,15 @@
|
||||
|
||||
import React, {useCallback, useEffect, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {DeviceEventEmitter} from 'react-native';
|
||||
import {Alert, DeviceEventEmitter} from 'react-native';
|
||||
|
||||
import {getChannelTimezones} from '@actions/remote/channel';
|
||||
import {executeCommand, handleGotoLocation} from '@actions/remote/command';
|
||||
import {createPost} from '@actions/remote/post';
|
||||
import {handleReactionToLatestPost} from '@actions/remote/reactions';
|
||||
import {setStatus} from '@actions/remote/user';
|
||||
import {canEndCall, endCall, getEndCallMessage} from '@calls/actions/calls';
|
||||
import ClientError from '@client/rest/error';
|
||||
import {Events, Screens} from '@constants';
|
||||
import {NOTIFY_ALL_MEMBERS} from '@constants/post_draft';
|
||||
import {useServerUrl} from '@context/server';
|
||||
@@ -128,7 +130,55 @@ export default function SendHandler({
|
||||
DraftUtils.alertChannelWideMention(intl, notifyAllMessage, doSubmitMessage, cancel);
|
||||
}, [intl, isTimezoneEnabled, channelTimezoneCount, doSubmitMessage]);
|
||||
|
||||
const handleEndCall = useCallback(async () => {
|
||||
const hasPermissions = await canEndCall(serverUrl, channelId);
|
||||
|
||||
if (!hasPermissions) {
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'mobile.calls_end_permission_title',
|
||||
defaultMessage: 'Error',
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'mobile.calls_end_permission_msg',
|
||||
defaultMessage: 'You do not have permission to end the call. Please ask the call owner to end the call.',
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
const message = await getEndCallMessage(serverUrl, channelId, currentUserId, intl);
|
||||
const title = intl.formatMessage({id: 'mobile.calls_end_call_title', defaultMessage: 'End call'});
|
||||
|
||||
Alert.alert(
|
||||
title,
|
||||
message,
|
||||
[
|
||||
{
|
||||
text: intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'}),
|
||||
},
|
||||
{
|
||||
text: title,
|
||||
onPress: async () => {
|
||||
try {
|
||||
await endCall(serverUrl, channelId);
|
||||
} catch (e) {
|
||||
const err = (e as ClientError).message || 'unable to complete command, see server logs';
|
||||
Alert.alert('Error', `Error: ${err}`);
|
||||
}
|
||||
},
|
||||
style: 'cancel',
|
||||
},
|
||||
],
|
||||
);
|
||||
}, [serverUrl, channelId, currentUserId, intl]);
|
||||
|
||||
const sendCommand = useCallback(async () => {
|
||||
if (value.trim() === '/call end') {
|
||||
await handleEndCall();
|
||||
|
||||
// NOTE: fallthrough because the server may want to handle the command as well
|
||||
}
|
||||
|
||||
const status = DraftUtils.getStatusFromSlashCommand(value);
|
||||
if (userIsOutOfOffice && status) {
|
||||
const updateStatus = (newStatus: string) => {
|
||||
@@ -163,7 +213,7 @@ export default function SendHandler({
|
||||
if (data?.goto_location && !value.startsWith('/leave')) {
|
||||
handleGotoLocation(serverUrl, intl, data.goto_location);
|
||||
}
|
||||
}, [userIsOutOfOffice, currentUserId, intl, value, serverUrl, channelId, rootId]);
|
||||
}, [userIsOutOfOffice, currentUserId, intl, value, serverUrl, channelId, rootId, handleEndCall]);
|
||||
|
||||
const sendMessage = useCallback(() => {
|
||||
const notificationsToChannel = enableConfirmNotificationsToChannel && useChannelMentions;
|
||||
|
||||
@@ -85,6 +85,7 @@ const addFakeCall = (serverUrl: string, channelId: string) => {
|
||||
startTime: (new Date()).getTime(),
|
||||
screenOn: '',
|
||||
threadId: 'abcd1234567',
|
||||
ownerId: 'xohi8cki9787fgiryne716u84o',
|
||||
} as Call;
|
||||
act(() => {
|
||||
State.callStarted(serverUrl, call);
|
||||
|
||||
@@ -6,7 +6,7 @@ import InCallManager from 'react-native-incall-manager';
|
||||
import {forceLogoutIfNecessary} from '@actions/remote/session';
|
||||
import {fetchUsersByIds} from '@actions/remote/user';
|
||||
import {
|
||||
getCallsConfig,
|
||||
getCallsConfig, getCallsState,
|
||||
myselfJoinedCall,
|
||||
myselfLeftCall,
|
||||
setCalls,
|
||||
@@ -16,19 +16,29 @@ import {
|
||||
setScreenShareURL,
|
||||
setSpeakerPhone,
|
||||
} from '@calls/state';
|
||||
import {
|
||||
import {General, Preferences} from '@constants';
|
||||
import Calls from '@constants/calls';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getTeammateNameDisplaySetting} from '@helpers/api/preference';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {getChannelById} from '@queries/servers/channel';
|
||||
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
|
||||
import {getCommonSystemValues} from '@queries/servers/system';
|
||||
import {getCurrentUser, getUserById} from '@queries/servers/user';
|
||||
import {displayUsername, getUserIdFromChannelName, isSystemAdmin} from '@utils/user';
|
||||
|
||||
import {newConnection} from '../connection/connection';
|
||||
|
||||
import type {
|
||||
ApiResp,
|
||||
Call,
|
||||
CallParticipant,
|
||||
CallsConnection,
|
||||
ServerChannelState,
|
||||
} from '@calls/types/calls';
|
||||
import Calls from '@constants/calls';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
|
||||
import {newConnection} from '../connection/connection';
|
||||
|
||||
import type {Client} from '@client/rest';
|
||||
import type ClientError from '@client/rest/error';
|
||||
import type {IntlShape} from 'react-intl';
|
||||
|
||||
let connection: CallsConnection | null = null;
|
||||
export const getConnectionForTesting = () => connection;
|
||||
@@ -101,6 +111,7 @@ export const loadCalls = async (serverUrl: string, userId: string) => {
|
||||
startTime: call.start_at,
|
||||
screenOn: call.screen_sharing_id,
|
||||
threadId: call.thread_id,
|
||||
ownerId: call.owner_id,
|
||||
};
|
||||
}
|
||||
enabledChannels[channel.channel_id] = channel.enabled;
|
||||
@@ -236,3 +247,79 @@ export const setSpeakerphoneOn = (speakerphoneOn: boolean) => {
|
||||
InCallManager.setSpeakerphoneOn(speakerphoneOn);
|
||||
setSpeakerPhone(speakerphoneOn);
|
||||
};
|
||||
|
||||
export const canEndCall = async (serverUrl: string, channelId: string) => {
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentUser = await getCurrentUser(database);
|
||||
if (!currentUser) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const call = getCallsState(serverUrl).calls[channelId];
|
||||
if (!call) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isSystemAdmin(currentUser.roles) || currentUser.id === call.ownerId;
|
||||
};
|
||||
|
||||
export const getEndCallMessage = async (serverUrl: string, channelId: string, currentUserId: string, intl: IntlShape) => {
|
||||
let msg = intl.formatMessage({
|
||||
id: 'mobile.calls_end_msg_channel_default',
|
||||
defaultMessage: 'Are you sure you want to end the call?',
|
||||
});
|
||||
|
||||
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
|
||||
if (!database) {
|
||||
return msg;
|
||||
}
|
||||
|
||||
const channel = await getChannelById(database, channelId);
|
||||
if (!channel) {
|
||||
return msg;
|
||||
}
|
||||
|
||||
const call = getCallsState(serverUrl).calls[channelId];
|
||||
if (!call) {
|
||||
return msg;
|
||||
}
|
||||
|
||||
const numParticipants = Object.keys(call.participants).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});
|
||||
|
||||
if (channel.type === General.DM_CHANNEL) {
|
||||
const otherID = getUserIdFromChannelName(currentUserId, channel.name);
|
||||
const otherUser = await getUserById(database, otherID);
|
||||
const {config, license} = await getCommonSystemValues(database);
|
||||
const preferences = await queryPreferencesByCategoryAndName(database, Preferences.CATEGORY_DISPLAY_SETTINGS, Preferences.NAME_NAME_FORMAT).fetch();
|
||||
const displaySetting = getTeammateNameDisplaySetting(preferences, config, license);
|
||||
msg = intl.formatMessage({
|
||||
id: 'mobile.calls_end_msg_dm',
|
||||
defaultMessage: 'Are you sure you want to end the call with {displayName}?',
|
||||
}, {displayName: displayUsername(otherUser, intl.locale, displaySetting)});
|
||||
}
|
||||
|
||||
return msg;
|
||||
};
|
||||
|
||||
export const endCall = async (serverUrl: string, channelId: string) => {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
|
||||
let data: ApiResp;
|
||||
try {
|
||||
data = await client.endCall(channelId);
|
||||
} catch (error) {
|
||||
await forceLogoutIfNecessary(serverUrl, error as ClientError);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {ServerChannelState, ServerCallsConfig} from '@calls/types/calls';
|
||||
import type {ServerChannelState, ServerCallsConfig, ApiResp} from '@calls/types/calls';
|
||||
|
||||
export interface ClientCallsMix {
|
||||
getEnabled: () => Promise<Boolean>;
|
||||
getCalls: () => Promise<ServerChannelState[]>;
|
||||
getCallsConfig: () => Promise<ServerCallsConfig>;
|
||||
enableChannelCalls: (channelId: string, enable: boolean) => Promise<ServerChannelState>;
|
||||
endCall: (channelId: string) => Promise<ApiResp>;
|
||||
}
|
||||
|
||||
const ClientCalls = (superclass: any) => class extends superclass {
|
||||
@@ -43,6 +44,13 @@ const ClientCalls = (superclass: any) => class extends superclass {
|
||||
{method: 'post', body: {enabled: enable}},
|
||||
);
|
||||
};
|
||||
|
||||
endCall = async (channelId: string) => {
|
||||
return this.doFetch(
|
||||
`${this.getCallsRoute()}/calls/${channelId}/end`,
|
||||
{method: 'post'},
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default ClientCalls;
|
||||
|
||||
@@ -30,7 +30,7 @@ const enhanced = withObservables(['serverUrl', 'channelId'], ({
|
||||
const callsState = observeCallsState(serverUrl);
|
||||
const participants = callsState.pipe(
|
||||
switchMap((state) => of$(state.calls[channelId])),
|
||||
distinctUntilChanged((prev, curr) => prev.participants === curr.participants), // Did the participants object ref change?
|
||||
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
|
||||
switchMap((ids) => (ids.length > 0 ? queryUsersById(database, ids).observeWithColumns(['last_picture_update']) : of$([]))),
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {IntlShape} from 'react-intl';
|
||||
import {Alert} from 'react-native';
|
||||
|
||||
import {hasMicrophonePermission, joinCall} from '@calls/actions';
|
||||
import {errorAlert} from '@calls/utils';
|
||||
|
||||
import type {IntlShape} from 'react-intl';
|
||||
|
||||
export default function leaveAndJoinWithAlert(
|
||||
intl: IntlShape,
|
||||
serverUrl: string,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
import {deflate} from 'pako/lib/deflate.js';
|
||||
import {DeviceEventEmitter, EmitterSubscription} from 'react-native';
|
||||
import InCallManager from 'react-native-incall-manager';
|
||||
import {
|
||||
MediaStream,
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
} from 'react-native-webrtc';
|
||||
|
||||
import {CallsConnection} from '@calls/types/calls';
|
||||
import {WebsocketEvents} from '@constants';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {logError} from '@utils/log';
|
||||
|
||||
@@ -26,6 +28,7 @@ export async function newConnection(serverUrl: string, channelID: string, closeC
|
||||
let voiceTrackAdded = false;
|
||||
let voiceTrack: MediaStreamTrack | null = null;
|
||||
let isClosed = false;
|
||||
let onCallEnd: EmitterSubscription | null = null;
|
||||
const streams: MediaStream[] = [];
|
||||
|
||||
try {
|
||||
@@ -53,6 +56,11 @@ export async function newConnection(serverUrl: string, channelID: string, closeC
|
||||
ws.close();
|
||||
}
|
||||
|
||||
if (onCallEnd) {
|
||||
onCallEnd.remove();
|
||||
onCallEnd = null;
|
||||
}
|
||||
|
||||
streams.forEach((s) => {
|
||||
s.getTracks().forEach((track: MediaStreamTrack) => {
|
||||
track.stop();
|
||||
@@ -71,6 +79,12 @@ export async function newConnection(serverUrl: string, channelID: string, closeC
|
||||
}
|
||||
};
|
||||
|
||||
onCallEnd = DeviceEventEmitter.addListener(WebsocketEvents.CALLS_CALL_END, ({channelId}: { channelId: string }) => {
|
||||
if (channelId === channelID) {
|
||||
disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
const mute = () => {
|
||||
if (!peer || peer.destroyed) {
|
||||
return;
|
||||
|
||||
@@ -5,9 +5,12 @@ import {DeviceEventEmitter} from 'react-native';
|
||||
|
||||
import {fetchUsersByIds} from '@actions/remote/user';
|
||||
import {
|
||||
callStarted, setCallScreenOff,
|
||||
callEnded,
|
||||
callStarted,
|
||||
setCallScreenOff,
|
||||
setCallScreenOn,
|
||||
setChannelEnabled, setRaisedHand,
|
||||
setChannelEnabled,
|
||||
setRaisedHand,
|
||||
setUserMuted,
|
||||
userJoinedCall,
|
||||
userLeftCall,
|
||||
@@ -60,6 +63,15 @@ export const handleCallStarted = (serverUrl: string, msg: WebSocketMessage) => {
|
||||
threadId: msg.data.thread_id,
|
||||
screenOn: '',
|
||||
participants: {},
|
||||
ownerId: msg.data.owner_id,
|
||||
});
|
||||
};
|
||||
|
||||
export const handleCallEnded = (serverUrl: string, msg: WebSocketMessage) => {
|
||||
callEnded(serverUrl, msg.broadcast.channel_id);
|
||||
|
||||
DeviceEventEmitter.emit(WebsocketEvents.CALLS_CALL_END, {
|
||||
channelId: msg.broadcast.channel_id,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -357,8 +357,17 @@ const CallScreen = ({componentId, currentCall, participantsDict, teammateNameDis
|
||||
});
|
||||
}, [insets, intl, theme]);
|
||||
|
||||
useEffect(() => {
|
||||
const listener = DeviceEventEmitter.addListener(WebsocketEvents.CALLS_CALL_END, ({channelId}) => {
|
||||
if (channelId === currentCall?.channelId) {
|
||||
popTopScreen(componentId);
|
||||
}
|
||||
});
|
||||
|
||||
return () => listener.remove();
|
||||
}, []);
|
||||
|
||||
if (!currentCall || !myParticipant) {
|
||||
// This should not be possible, but may happen until https://github.com/mattermost/mattermost-mobile/pull/6493 is merged.
|
||||
// TODO: will figure out a way to remove the need for this check: https://mattermost.atlassian.net/browse/MM-46050
|
||||
popTopScreen(componentId);
|
||||
return null;
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
userJoinedCall,
|
||||
userLeftCall,
|
||||
callStarted,
|
||||
callFinished,
|
||||
callEnded,
|
||||
setUserMuted,
|
||||
setCallScreenOn,
|
||||
setCallScreenOff,
|
||||
@@ -44,6 +44,7 @@ const call1 = {
|
||||
startTime: 123,
|
||||
screenOn: '',
|
||||
threadId: 'thread-1',
|
||||
ownerId: 'user-1',
|
||||
};
|
||||
const call2 = {
|
||||
participants: {
|
||||
@@ -54,6 +55,7 @@ const call2 = {
|
||||
startTime: 123,
|
||||
screenOn: '',
|
||||
threadId: 'thread-2',
|
||||
ownerId: 'user-3',
|
||||
};
|
||||
const call3 = {
|
||||
participants: {
|
||||
@@ -64,6 +66,7 @@ const call3 = {
|
||||
startTime: 123,
|
||||
screenOn: '',
|
||||
threadId: 'thread-3',
|
||||
ownerId: 'user-5',
|
||||
};
|
||||
|
||||
describe('useCallsState', () => {
|
||||
@@ -165,6 +168,7 @@ describe('useCallsState', () => {
|
||||
startTime: 123,
|
||||
screenOn: '',
|
||||
threadId: 'thread-1',
|
||||
ownerId: 'user-1',
|
||||
},
|
||||
};
|
||||
const expectedChannelsWithCallsState = initialChannelsWithCallsState;
|
||||
@@ -221,6 +225,7 @@ describe('useCallsState', () => {
|
||||
startTime: 123,
|
||||
screenOn: '',
|
||||
threadId: 'thread-1',
|
||||
ownerId: 'user-1',
|
||||
},
|
||||
};
|
||||
const expectedChannelsWithCallsState = initialChannelsWithCallsState;
|
||||
@@ -269,8 +274,7 @@ describe('useCallsState', () => {
|
||||
assert.deepEqual(result.current[2], null);
|
||||
});
|
||||
|
||||
// TODO: needs to be changed to callEnd when that ws event is implemented
|
||||
it('callFinished', () => {
|
||||
it('callEnded', () => {
|
||||
const initialCallsState = {
|
||||
...DefaultCallsState,
|
||||
calls: {'channel-1': call1, 'channel-2': call2},
|
||||
@@ -295,7 +299,7 @@ describe('useCallsState', () => {
|
||||
assert.deepEqual(result.current[2], null);
|
||||
|
||||
// test
|
||||
act(() => callFinished('server1', 'channel-1'));
|
||||
act(() => callEnded('server1', 'channel-1'));
|
||||
assert.deepEqual(result.current[0], expectedCallsState);
|
||||
assert.deepEqual(result.current[1], expectedChannelsWithCallsState);
|
||||
assert.deepEqual(result.current[2], null);
|
||||
@@ -409,6 +413,7 @@ describe('useCallsState', () => {
|
||||
startTime: 123,
|
||||
screenOn: false,
|
||||
threadId: 'thread-1',
|
||||
ownerId: 'user-1',
|
||||
},
|
||||
};
|
||||
const initialCurrentCallState = {
|
||||
|
||||
@@ -136,8 +136,7 @@ export const callStarted = (serverUrl: string, call: Call) => {
|
||||
setCurrentCall(nextCurrentCall);
|
||||
};
|
||||
|
||||
// TODO: should be called callEnded to match the ws event. Will fix when callEnded is implemented.
|
||||
export const callFinished = (serverUrl: string, channelId: string) => {
|
||||
export const callEnded = (serverUrl: string, channelId: string) => {
|
||||
const callsState = getCallsState(serverUrl);
|
||||
const nextCalls = {...callsState.calls};
|
||||
delete nextCalls[channelId];
|
||||
@@ -147,6 +146,11 @@ export const callFinished = (serverUrl: string, channelId: string) => {
|
||||
const nextChannelsWithCalls = {...channelsWithCalls};
|
||||
delete nextChannelsWithCalls[channelId];
|
||||
setChannelsWithCalls(serverUrl, nextChannelsWithCalls);
|
||||
|
||||
const currentCall = getCurrentCall();
|
||||
if (currentCall?.channelId === channelId) {
|
||||
setCurrentCall(null);
|
||||
}
|
||||
};
|
||||
|
||||
export const setUserMuted = (serverUrl: string, channelId: string, userId: string, muted: boolean) => {
|
||||
|
||||
@@ -23,6 +23,7 @@ export type Call = {
|
||||
startTime: number;
|
||||
screenOn: string;
|
||||
threadId: string;
|
||||
ownerId: string;
|
||||
}
|
||||
|
||||
export const DefaultCall = {
|
||||
@@ -72,6 +73,7 @@ export type ServerCallState = {
|
||||
states: ServerUserState[];
|
||||
thread_id: string;
|
||||
screen_sharing_id: string;
|
||||
owner_id: string;
|
||||
}
|
||||
|
||||
export type VoiceEventData = {
|
||||
@@ -109,3 +111,9 @@ export const DefaultCallsConfig = {
|
||||
DefaultEnabled: false,
|
||||
last_retrieved_at: 0,
|
||||
} as CallsConfig;
|
||||
|
||||
export type ApiResp = {
|
||||
message?: string;
|
||||
detailed_error?: string;
|
||||
status_code: number;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {IntlShape} from 'react-intl';
|
||||
import {Alert} from 'react-native';
|
||||
|
||||
import {CallParticipant} from '@calls/types/calls';
|
||||
@@ -11,6 +10,8 @@ import PostModel from '@typings/database/models/servers/post';
|
||||
import {isMinimumServerVersion} from '@utils/helpers';
|
||||
import {displayUsername} from '@utils/user';
|
||||
|
||||
import type {IntlShape} from 'react-intl';
|
||||
|
||||
export function sortParticipants(teammateNameDisplay: string, participants?: Dictionary<CallParticipant>, presenterID?: string): CallParticipant[] {
|
||||
if (!participants) {
|
||||
return [];
|
||||
|
||||
@@ -357,9 +357,16 @@
|
||||
"mobile.android.back_handler_exit": "Press back again to exit",
|
||||
"mobile.android.photos_permission_denied_description": "Upload photos to your server or save them to your device. Open Settings to grant {applicationName} Read and Write access to your photo library.",
|
||||
"mobile.android.photos_permission_denied_title": "{applicationName} would like to access your photos",
|
||||
"mobile.calls_call_screen": "Call",
|
||||
"mobile.calls_disable": "Disable Calls",
|
||||
"mobile.calls_enable": "Enable Calls",
|
||||
"mobile.calls_end_call_title": "End call",
|
||||
"mobile.calls_end_msg_channel": "Are you sure you want to end a call with {numParticipants} participants in {displayName}?",
|
||||
"mobile.calls_end_msg_dm": "Are you sure you want to end a call with {displayName}?",
|
||||
"mobile.calls_end_permission_msg": "You do not have permission to end the call. Please ask the call creator to end the call.",
|
||||
"mobile.calls_end_permission_title": "Error",
|
||||
"mobile.calls_error_message": "Error: {error}",
|
||||
"mobile.calls_error_permissions": "no permissions to microphone, unable to start call",
|
||||
"mobile.calls_error_title": "Error",
|
||||
"mobile.calls_join_call": "Join Call",
|
||||
"mobile.calls_leave_call": "Leave Call",
|
||||
|
||||
Reference in New Issue
Block a user