From 1f2a71c4997e12003f803ccb880ed90cfc554c5a Mon Sep 17 00:00:00 2001 From: Christopher Poile Date: Tue, 18 Jun 2024 10:02:24 -0400 Subject: [PATCH] [MM-58581] Calls: End call confirmations (#8004) * end call confirmations * i18n * remove unneeded asyncs * better alert for android --- app/actions/websocket/index.ts | 8 ++- app/components/post_list/post/post.tsx | 6 ++ app/constants/websocket.ts | 1 + app/products/calls/actions/calls.ts | 39 ++++++++++-- app/products/calls/alerts.ts | 63 ++++++++++++++++++- .../calls_custom_message.tsx | 20 +++--- .../components/calls_custom_message/index.ts | 3 +- .../channel_info_start_button.tsx | 13 ++-- .../components/channel_info_start/index.ts | 3 +- .../current_call_bar/current_call_bar.tsx | 14 +++-- .../components/current_call_bar/index.ts | 3 +- .../connection/websocket_event_handlers.ts | 12 ++++ app/products/calls/hooks.ts | 5 +- app/products/calls/observers/index.ts | 23 +++++++ .../calls/screens/call_screen/call_screen.tsx | 16 +++-- .../calls/screens/call_screen/index.ts | 3 +- app/products/calls/state/actions.ts | 5 +- app/products/calls/types/calls.ts | 6 ++ assets/base/i18n/en.json | 4 ++ 19 files changed, 209 insertions(+), 38 deletions(-) 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",