diff --git a/app/actions/websocket/index.ts b/app/actions/websocket/index.ts
index 3484c6c99..96d173242 100644
--- a/app/actions/websocket/index.ts
+++ b/app/actions/websocket/index.ts
@@ -24,7 +24,10 @@ import {
handleCallRecordingState,
handleCallScreenOff,
handleCallScreenOn,
- handleCallStarted, handleCallUserConnected, handleCallUserDisconnected,
+ handleCallStarted,
+ handleCallState,
+ handleCallUserConnected,
+ handleCallUserDisconnected,
handleCallUserJoined,
handleCallUserLeft,
handleCallUserMuted,
@@ -450,6 +453,9 @@ export async function handleEvent(serverUrl: string, msg: WebSocketMessage) {
case WebsocketEvents.CALLS_HOST_REMOVED:
handleHostRemoved(serverUrl, msg);
break;
+ case WebsocketEvents.CALLS_CALL_STATE:
+ handleCallState(serverUrl, msg);
+ break;
case WebsocketEvents.GROUP_RECEIVED:
handleGroupReceivedEvent(serverUrl, msg);
diff --git a/app/components/post_list/post/post.tsx b/app/components/post_list/post/post.tsx
index bca30018a..a0c5ec02e 100644
--- a/app/components/post_list/post/post.tsx
+++ b/app/components/post_list/post/post.tsx
@@ -341,6 +341,12 @@ const Post = ({
);
} else {
diff --git a/app/constants/websocket.ts b/app/constants/websocket.ts
index 4b75b4631..100773417 100644
--- a/app/constants/websocket.ts
+++ b/app/constants/websocket.ts
@@ -91,6 +91,7 @@ const WebsocketEvents = {
CALLS_HOST_MUTE: `custom_${Calls.PluginId}_host_mute`,
CALLS_HOST_LOWER_HAND: `custom_${Calls.PluginId}_host_lower_hand`,
CALLS_HOST_REMOVED: `custom_${Calls.PluginId}_host_removed`,
+ CALLS_CALL_STATE: `custom_${Calls.PluginId}_call_state`,
GROUP_RECEIVED: 'received_group',
GROUP_MEMBER_ADD: 'group_member_add',
diff --git a/app/products/calls/actions/calls.ts b/app/products/calls/actions/calls.ts
index 86a0507b4..fb5f5b009 100644
--- a/app/products/calls/actions/calls.ts
+++ b/app/products/calls/actions/calls.ts
@@ -8,6 +8,7 @@ import {forceLogoutIfNecessary} from '@actions/remote/session';
import {updateThreadFollowing} from '@actions/remote/thread';
import {fetchUsersByIds} from '@actions/remote/user';
import {
+ endCallConfirmationAlert,
leaveAndJoinWithAlert,
needsRecordingErrorAlert,
needsRecordingWillBePostedAlert,
@@ -28,6 +29,7 @@ import {
setScreenShareURL,
setSpeakerPhone,
} from '@calls/state';
+import {type AudioDevice, type Call, type CallSession, type CallsConnection, EndCallReturn} from '@calls/types/calls';
import {General, Preferences} from '@constants';
import Calls from '@constants/calls';
import DatabaseManager from '@database/manager';
@@ -44,7 +46,6 @@ import {displayUsername, getUserIdFromChannelName, isSystemAdmin} from '@utils/u
import {newConnection} from '../connection/connection';
-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';
@@ -132,7 +133,7 @@ export const loadCallForChannel = async (serverUrl: string, channelId: string) =
fetchUsersByIds(serverUrl, Array.from(ids));
}
- setCallForChannel(serverUrl, channelId, resp.enabled, call);
+ setCallForChannel(serverUrl, channelId, call, resp.enabled);
return {data: {call, enabled: resp.enabled}};
};
@@ -160,11 +161,12 @@ const convertOldCallToNew = (call: CallState): CallState => {
};
};
-const createCallAndAddToIds = (channelId: string, call: CallState, ids: Set) => {
- return {
+export const createCallAndAddToIds = (channelId: string, call: CallState, ids?: Set) => {
+ // Don't cast so that we get alerted to missing types
+ const convertedCall: Call = {
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.user_id);
+ ids?.add(cur.user_id);
// Create the CallParticipant
accum[cur.session_id] = {
@@ -184,7 +186,9 @@ const createCallAndAddToIds = (channelId: string, call: CallState, ids: Set {
@@ -302,6 +306,29 @@ export const leaveCall = (err?: Error) => {
}
};
+export const leaveCallConfirmation = async (
+ intl: IntlShape,
+ otherParticipants: boolean,
+ isAdmin: boolean,
+ isHost: boolean,
+ serverUrl: string,
+ channelId: string,
+ leaveCb?: () => void) => {
+ const showHostControls = (isHost || isAdmin) && otherParticipants;
+ const ret = await endCallConfirmationAlert(intl, showHostControls) as EndCallReturn;
+ switch (ret) {
+ case EndCallReturn.Cancel:
+ return;
+ case EndCallReturn.LeaveCall:
+ leaveCall();
+ leaveCb?.();
+ return;
+ case EndCallReturn.EndCall:
+ endCall(serverUrl, channelId);
+ leaveCb?.();
+ }
+};
+
export const muteMyself = () => {
if (connection) {
connection.mute();
diff --git a/app/products/calls/alerts.ts b/app/products/calls/alerts.ts
index 308118a29..0811f7c30 100644
--- a/app/products/calls/alerts.ts
+++ b/app/products/calls/alerts.ts
@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
-import {Alert} from 'react-native';
+import {Alert, type AlertButton, Platform} from 'react-native';
import {hasMicrophonePermission, joinCall, leaveCall, unmuteMyself} from '@calls/actions';
import {dismissIncomingCall, hostRemove} from '@calls/actions/calls';
@@ -15,6 +15,7 @@ import {
removeIncomingCall,
setMicPermissionsGranted,
} from '@calls/state';
+import {EndCallReturn} from '@calls/types/calls';
import {errorAlert} from '@calls/utils';
import DatabaseManager from '@database/manager';
import {getChannelById} from '@queries/servers/channel';
@@ -132,7 +133,7 @@ export const leaveAndJoinWithAlert = async (
id: 'mobile.post.cancel',
defaultMessage: 'Cancel',
}),
- onPress: async () => resolve(false),
+ onPress: () => resolve(false),
style: 'destructive',
},
{
@@ -473,3 +474,61 @@ export const removeFromCall = (serverUrl: string, displayName: string, callId: s
style: 'destructive',
}]);
};
+
+export const endCallConfirmationAlert = (intl: IntlShape, showHostControls: boolean) => {
+ const {formatMessage} = intl;
+
+ const asyncAlert = async () => new Promise((resolve) => {
+ const buttons: AlertButton[] = [{
+ text: formatMessage({
+ id: 'mobile.calls_cancel',
+ defaultMessage: 'Cancel',
+ }),
+ onPress: () => resolve(EndCallReturn.Cancel),
+ style: 'cancel',
+ }, {
+ text: formatMessage({
+ id: 'mobile.calls_host_leave_confirm',
+ defaultMessage: 'Leave call',
+ }),
+ onPress: () => resolve(EndCallReturn.LeaveCall),
+ style: 'destructive',
+ }];
+ const questionMsg = formatMessage({
+ id: 'mobile.calls_host_leave_title',
+ defaultMessage: 'Are you sure you want to leave this call?',
+ });
+
+ if (showHostControls) {
+ const endCallButton: AlertButton = {
+ text: formatMessage({
+ id: 'mobile.calls_host_end_confirm',
+ defaultMessage: 'End call for everyone',
+ }),
+ onPress: () => resolve(EndCallReturn.EndCall),
+ style: 'destructive',
+ };
+ const leaveCallButton = {
+ text: formatMessage({
+ id: 'mobile.calls_host_leave_confirm',
+ defaultMessage: 'Leave call',
+ }),
+ onPress: () => resolve(EndCallReturn.LeaveCall),
+ };
+
+ if (Platform.OS === 'ios') {
+ buttons.splice(1, 1, endCallButton, leaveCallButton);
+ } else {
+ buttons.splice(1, 1, leaveCallButton, endCallButton);
+ }
+ }
+
+ if (Platform.OS === 'ios') {
+ Alert.alert(questionMsg, '', buttons);
+ } else {
+ Alert.alert('', questionMsg, buttons);
+ }
+ });
+
+ return asyncAlert();
+};
diff --git a/app/products/calls/components/calls_custom_message/calls_custom_message.tsx b/app/products/calls/components/calls_custom_message/calls_custom_message.tsx
index e233a18cd..9cdca0d2c 100644
--- a/app/products/calls/components/calls_custom_message/calls_custom_message.tsx
+++ b/app/products/calls/components/calls_custom_message/calls_custom_message.tsx
@@ -6,7 +6,7 @@ import React, {useCallback} from 'react';
import {useIntl} from 'react-intl';
import {Text, TouchableOpacity, View} from 'react-native';
-import {leaveCall} from '@calls/actions';
+import {leaveCallConfirmation} from '@calls/actions/calls';
import {leaveAndJoinWithAlert, showLimitRestrictedAlert} from '@calls/alerts';
import {setJoiningChannelId} from '@calls/state';
import CompassIcon from '@components/compass_icon';
@@ -26,11 +26,14 @@ import type UserModel from '@typings/database/models/servers/user';
type Props = {
post: PostModel;
- currentUser?: UserModel;
isMilitaryTime: boolean;
+ joiningChannelId: string | null;
+ otherParticipants: boolean;
+ isAdmin: boolean;
+ isHost: boolean;
+ currentUser?: UserModel;
limitRestrictedInfo?: LimitRestrictedInfo;
ccChannelId?: string;
- joiningChannelId: string | null;
}
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
@@ -131,6 +134,9 @@ export const CallsCustomMessage = ({
ccChannelId,
limitRestrictedInfo,
joiningChannelId,
+ otherParticipants,
+ isAdmin,
+ isHost,
}: Props) => {
const intl = useIntl();
const theme = useTheme();
@@ -154,9 +160,9 @@ export const CallsCustomMessage = ({
setJoiningChannelId(null);
}, [limitRestrictedInfo, intl, serverUrl, post.channelId]);
- const leaveHandler = useCallback(() => {
- leaveCall();
- }, []);
+ const leaveCallHandler = useCallback(() => {
+ leaveCallConfirmation(intl, otherParticipants, isAdmin, isHost, serverUrl, post.channelId);
+ }, [intl, otherParticipants, isAdmin, isHost, serverUrl, post.channelId]);
const title = post.props.title ? (
@@ -210,7 +216,7 @@ export const CallsCustomMessage = ({
const button = alreadyInTheCall ? (
void;
limitRestrictedInfo: LimitRestrictedInfo;
+ otherParticipants: boolean;
+ isAdmin: boolean;
+ isHost: boolean;
}
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
@@ -53,6 +56,9 @@ const ChannelInfoStartButton = ({
alreadyInCall,
dismissChannelInfo,
limitRestrictedInfo,
+ otherParticipants,
+ isAdmin,
+ isHost,
}: Props) => {
const intl = useIntl();
const theme = useTheme();
@@ -66,8 +72,7 @@ const ChannelInfoStartButton = ({
const toggleJoinLeave = useCallback(async () => {
if (alreadyInCall) {
- leaveCall();
- dismissChannelInfo();
+ await leaveCallConfirmation(intl, otherParticipants, isAdmin, isHost, serverUrl, channelId, dismissChannelInfo);
} else if (isLimitRestricted) {
showLimitRestrictedAlert(limitRestrictedInfo, intl);
dismissChannelInfo();
@@ -79,7 +84,7 @@ const ChannelInfoStartButton = ({
setConnecting(false);
dismissChannelInfo();
}
- }, [isLimitRestricted, alreadyInCall, dismissChannelInfo, intl, serverUrl, channelId, isACallInCurrentChannel]);
+ }, [isLimitRestricted, alreadyInCall, dismissChannelInfo, intl, serverUrl, channelId, isACallInCurrentChannel, otherParticipants]);
const [tryJoin, msgPostfix] = useTryCallsFunction(toggleJoinLeave);
diff --git a/app/products/calls/components/channel_info_start/index.ts b/app/products/calls/components/channel_info_start/index.ts
index 4a965ebe6..7d1fc6863 100644
--- a/app/products/calls/components/channel_info_start/index.ts
+++ b/app/products/calls/components/channel_info_start/index.ts
@@ -6,7 +6,7 @@ import {of as of$} from 'rxjs';
import {distinctUntilChanged, switchMap} from 'rxjs/operators';
import ChannelInfoStartButton from '@calls/components/channel_info_start/channel_info_start_button';
-import {observeIsCallLimitRestricted} from '@calls/observers';
+import {observeEndCallDetails, observeIsCallLimitRestricted} from '@calls/observers';
import {observeChannelsWithCalls, observeCurrentCall} from '@calls/state';
import type {WithDatabaseArgs} from '@typings/database/database';
@@ -33,6 +33,7 @@ const enhanced = withObservables([], ({serverUrl, channelId, database}: EnhanceP
confirmToJoin,
alreadyInCall,
limitRestrictedInfo: observeIsCallLimitRestricted(database, serverUrl, channelId),
+ ...observeEndCallDetails(),
};
});
diff --git a/app/products/calls/components/current_call_bar/current_call_bar.tsx b/app/products/calls/components/current_call_bar/current_call_bar.tsx
index b618dd94c..a8382a6f2 100644
--- a/app/products/calls/components/current_call_bar/current_call_bar.tsx
+++ b/app/products/calls/components/current_call_bar/current_call_bar.tsx
@@ -5,7 +5,8 @@ import React, {useCallback, useMemo} from 'react';
import {useIntl} from 'react-intl';
import {View, Text, Pressable, Platform} from 'react-native';
-import {leaveCall, muteMyself, unmuteMyself} from '@calls/actions';
+import {muteMyself, unmuteMyself} from '@calls/actions';
+import {leaveCallConfirmation} from '@calls/actions/calls';
import {recordingAlert, recordingWillBePostedAlert, recordingErrorAlert} from '@calls/alerts';
import CallAvatar from '@calls/components/call_avatar';
import CallDuration from '@calls/components/call_duration';
@@ -34,6 +35,9 @@ type Props = {
teammateNameDisplay: string;
micPermissionsGranted: boolean;
threadScreen?: boolean;
+ otherParticipants: boolean;
+ isAdmin: boolean;
+ isHost: boolean;
}
const getStyleSheet = makeStyleSheetFromTheme((theme: CallsTheme) => {
@@ -139,6 +143,9 @@ const CurrentCallBar = ({
teammateNameDisplay,
micPermissionsGranted,
threadScreen,
+ otherParticipants,
+ isAdmin,
+ isHost,
}: Props) => {
const theme = useTheme();
const serverUrl = useServerUrl();
@@ -168,8 +175,8 @@ const CurrentCallBar = ({
}, [formatMessage, threadScreen]);
const leaveCallHandler = useCallback(() => {
- leaveCall();
- }, []);
+ leaveCallConfirmation(intl, otherParticipants, isAdmin, isHost, serverUrl, currentCall?.channelId || '');
+ }, [intl, otherParticipants, isAdmin, isHost, serverUrl, currentCall?.channelId]);
const mySession = currentCall?.sessions[currentCall.mySessionId];
@@ -210,7 +217,6 @@ 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 === mySession?.userId);
if (currentCall?.recState?.start_at && !currentCall?.recState?.end_at) {
recordingAlert(isHost, EnableTranscriptions, intl);
}
diff --git a/app/products/calls/components/current_call_bar/index.ts b/app/products/calls/components/current_call_bar/index.ts
index 4c2426241..430f035a2 100644
--- a/app/products/calls/components/current_call_bar/index.ts
+++ b/app/products/calls/components/current_call_bar/index.ts
@@ -5,7 +5,7 @@ import {withObservables} from '@nozbe/watermelondb/react';
import {combineLatest, of as of$} from 'rxjs';
import {distinctUntilChanged, switchMap} from 'rxjs/operators';
-import {observeCurrentSessionsDict} from '@calls/observers';
+import {observeCurrentSessionsDict, observeEndCallDetails} from '@calls/observers';
import {observeCurrentCall, observeGlobalCallsState} from '@calls/state';
import DatabaseManager from '@database/manager';
import {observeChannel} from '@queries/servers/channel';
@@ -46,6 +46,7 @@ const enhanced = withObservables([], () => {
sessionsDict: observeCurrentSessionsDict(),
teammateNameDisplay,
micPermissionsGranted,
+ ...observeEndCallDetails(),
};
});
diff --git a/app/products/calls/connection/websocket_event_handlers.ts b/app/products/calls/connection/websocket_event_handlers.ts
index 6749ca0c6..9a0693dda 100644
--- a/app/products/calls/connection/websocket_event_handlers.ts
+++ b/app/products/calls/connection/websocket_event_handlers.ts
@@ -5,6 +5,7 @@ import {DeviceEventEmitter} from 'react-native';
import {fetchUsersByIds} from '@actions/remote/user';
import {leaveCall, muteMyself, unraiseHand} from '@calls/actions';
+import {createCallAndAddToIds} from '@calls/actions/calls';
import {hostRemovedErr} from '@calls/errors';
import {
callEnded,
@@ -13,6 +14,7 @@ import {
getCurrentCall,
receivedCaption,
removeIncomingCall,
+ setCallForChannel,
setCallScreenOff,
setCallScreenOn,
setCaptioningState,
@@ -38,6 +40,8 @@ import type {
CallJobState,
CallJobStateData,
CallStartData,
+ CallState,
+ CallStateData,
EmptyData,
LiveCaptionData,
UserConnectedData,
@@ -240,3 +244,11 @@ export const handleHostRemoved = async (serverUrl: string, msg: WebSocketMessage
leaveCall(hostRemovedErr);
};
+
+export const handleCallState = (serverUrl: string, msg: WebSocketMessage) => {
+ const callState: CallState = JSON.parse(msg.data.call);
+ const call = createCallAndAddToIds(msg.data.channel_id, callState);
+
+ setCallForChannel(serverUrl, msg.data.channel_id, call);
+};
+
diff --git a/app/products/calls/hooks.ts b/app/products/calls/hooks.ts
index c0f1f98f9..bab65f87f 100644
--- a/app/products/calls/hooks.ts
+++ b/app/products/calls/hooks.ts
@@ -10,7 +10,8 @@ import Permissions from 'react-native-permissions';
import {initializeVoiceTrack} from '@calls/actions/calls';
import {
- getCallsConfig, getCurrentCall,
+ getCallsConfig,
+ getCurrentCall,
setMicPermissionsGranted,
useCallsState,
useChannelsWithCalls,
@@ -18,6 +19,7 @@ import {
useGlobalCallsState,
useIncomingCalls,
} from '@calls/state';
+import {type CallSession} from '@calls/types/calls';
import {errorAlert, isHostControlsAllowed} from '@calls/utils';
import {Screens} from '@constants';
import {
@@ -37,7 +39,6 @@ import {openAsBottomSheet} from '@screens/navigation';
import {getFullErrorMessage} from '@utils/errors';
import {isSystemAdmin} from '@utils/user';
-import type {CallSession} from '@calls/types/calls';
import type {Client} from '@client/rest';
export const useTryCallsFunction = (fn: () => void) => {
diff --git a/app/products/calls/observers/index.ts b/app/products/calls/observers/index.ts
index b8bcce385..038bee530 100644
--- a/app/products/calls/observers/index.ts
+++ b/app/products/calls/observers/index.ts
@@ -17,6 +17,7 @@ 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 {isSystemAdmin} from '@utils/user';
import type {CallSession} from '@calls/types/calls';
import type {Database} from '@nozbe/watermelondb';
@@ -137,3 +138,25 @@ export const observeCallStateInChannel = (serverUrl: string, database: Database,
showIncomingCalls,
};
};
+
+export const observeEndCallDetails = () => {
+ const cc = observeCurrentCall();
+ const otherParticipants = cc.pipe(
+ switchMap((call) => of$(Object.keys(call?.sessions || {}).length > 1)),
+ distinctUntilChanged(),
+ );
+ const isAdmin = cc.pipe(
+ switchMap((call) => of$(isSystemAdmin(call?.sessions[call?.mySessionId || '']?.userModel?.roles || ''))),
+ distinctUntilChanged(),
+ );
+ const isHost = cc.pipe(
+ switchMap((call) => of$(call?.hostId === call?.myUserId)),
+ distinctUntilChanged(),
+ );
+
+ return {
+ otherParticipants,
+ isAdmin,
+ isHost,
+ };
+};
diff --git a/app/products/calls/screens/call_screen/call_screen.tsx b/app/products/calls/screens/call_screen/call_screen.tsx
index f39567257..a765cb163 100644
--- a/app/products/calls/screens/call_screen/call_screen.tsx
+++ b/app/products/calls/screens/call_screen/call_screen.tsx
@@ -22,8 +22,8 @@ import {Navigation} from 'react-native-navigation';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
import {RTCView} from 'react-native-webrtc';
-import {leaveCall, muteMyself, unmuteMyself} from '@calls/actions';
-import {startCallRecording, stopCallRecording} from '@calls/actions/calls';
+import {muteMyself, unmuteMyself} from '@calls/actions';
+import {leaveCallConfirmation, startCallRecording, stopCallRecording} from '@calls/actions/calls';
import {recordingAlert, recordingWillBePostedAlert, recordingErrorAlert} from '@calls/alerts';
import {AudioDeviceButton} from '@calls/components/audio_device_button';
import CallDuration from '@calls/components/call_duration';
@@ -88,6 +88,9 @@ export type Props = {
fromThreadScreen?: boolean;
displayName?: string;
isOwnDirectMessage: boolean;
+ otherParticipants: boolean;
+ isAdmin: boolean;
+ isHost: boolean;
}
const getStyleSheet = makeStyleSheetFromTheme((theme: CallsTheme) => ({
@@ -311,6 +314,9 @@ const CallScreen = ({
fromThreadScreen,
displayName,
isOwnDirectMessage,
+ otherParticipants,
+ isAdmin,
+ isHost,
}: Props) => {
const intl = useIntl();
const theme = useTheme();
@@ -380,9 +386,8 @@ const CallScreen = ({
}, []);
const leaveCallHandler = useCallback(() => {
- popTopScreen();
- leaveCall();
- }, []);
+ leaveCallConfirmation(intl, otherParticipants, isAdmin, isHost, serverUrl, currentCall?.channelId || '', popTopScreen);
+ }, [intl, otherParticipants, isAdmin, isHost, serverUrl, currentCall?.channelId]);
const muteUnmuteHandler = useCallback(() => {
if (mySession?.muted) {
@@ -453,7 +458,6 @@ 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 === mySession?.userId);
const recording = Boolean(currentCall?.recState?.start_at && !currentCall.recState.end_at);
if (recording) {
recordingAlert(isHost, EnableTranscriptions, intl);
diff --git a/app/products/calls/screens/call_screen/index.ts b/app/products/calls/screens/call_screen/index.ts
index b9d0e8a64..08a5cda7c 100644
--- a/app/products/calls/screens/call_screen/index.ts
+++ b/app/products/calls/screens/call_screen/index.ts
@@ -5,7 +5,7 @@ import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
import {of as of$, combineLatestWith} from 'rxjs';
import {distinctUntilChanged, switchMap} from 'rxjs/operators';
-import {observeCallDatabase, observeCurrentSessionsDict} from '@calls/observers';
+import {observeCallDatabase, observeCurrentSessionsDict, observeEndCallDetails} from '@calls/observers';
import CallScreen from '@calls/screens/call_screen/call_screen';
import {observeCurrentCall, observeGlobalCallsState} from '@calls/state';
import {General} from '@constants';
@@ -53,6 +53,7 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
teammateNameDisplay,
displayName,
isOwnDirectMessage,
+ ...observeEndCallDetails(),
};
});
diff --git a/app/products/calls/state/actions.ts b/app/products/calls/state/actions.ts
index 43064de98..7b3742875 100644
--- a/app/products/calls/state/actions.ts
+++ b/app/products/calls/state/actions.ts
@@ -184,15 +184,16 @@ export const removeIncomingCall = (serverUrl: string, callId: string, channelId?
setCallsState(serverUrl, {...callsState, calls: nextCalls});
};
-export const setCallForChannel = (serverUrl: string, channelId: string, enabled?: boolean, call?: Call) => {
+export const setCallForChannel = (serverUrl: string, channelId: string, call?: Call, enabled?: boolean) => {
const callsState = getCallsState(serverUrl);
let nextEnabled = callsState.enabled;
if (typeof enabled !== 'undefined') {
nextEnabled = {...callsState.enabled, [channelId]: enabled};
}
- const nextCalls = {...callsState.calls};
+ let nextCalls = callsState.calls;
if (call) {
+ nextCalls = {...callsState.calls};
nextCalls[channelId] = call;
// In case we got a complete update on the currentCall
diff --git a/app/products/calls/types/calls.ts b/app/products/calls/types/calls.ts
index 5f455b9d4..f4dfbab9c 100644
--- a/app/products/calls/types/calls.ts
+++ b/app/products/calls/types/calls.ts
@@ -249,3 +249,9 @@ export type HostControlsLowerHandMsgData = HostControlsMsgData & {
call_id: string;
host_id: string;
}
+
+export enum EndCallReturn {
+ Cancel,
+ LeaveCall,
+ EndCall,
+}
diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json
index 34a4b89b8..c2a2b3a67 100644
--- a/assets/base/i18n/en.json
+++ b/assets/base/i18n/en.json
@@ -445,6 +445,7 @@
"mobile.calls_call_ended": "Call ended",
"mobile.calls_call_screen": "Call",
"mobile.calls_call_thread": "Call Thread",
+ "mobile.calls_cancel": "Cancel",
"mobile.calls_captions": "Captions",
"mobile.calls_captions_turned_on": "Live captions turned on",
"mobile.calls_disable": "Disable calls",
@@ -463,6 +464,9 @@
"mobile.calls_hide_cc": "Hide live captions",
"mobile.calls_host": "host",
"mobile.calls_host_controls": "Host controls",
+ "mobile.calls_host_end_confirm": "End call for everyone",
+ "mobile.calls_host_leave_confirm": "Leave call",
+ "mobile.calls_host_leave_title": "Are you sure you want to leave this call?",
"mobile.calls_host_rec": "You are recording this meeting. Consider letting everyone know that this meeting is being recorded.",
"mobile.calls_host_rec_error": "Please try to record again. You can also contact your system admin for troubleshooting help.",
"mobile.calls_host_rec_error_title": "Something went wrong with the recording",