Skip to content

Commit 1f2a71c

Browse files
authored
[MM-58581] Calls: End call confirmations (#8004)
* end call confirmations * i18n * remove unneeded asyncs * better alert for android
1 parent 8cef881 commit 1f2a71c

File tree

19 files changed

+209
-38
lines changed

19 files changed

+209
-38
lines changed

app/actions/websocket/index.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ import {
2424
handleCallRecordingState,
2525
handleCallScreenOff,
2626
handleCallScreenOn,
27-
handleCallStarted, handleCallUserConnected, handleCallUserDisconnected,
27+
handleCallStarted,
28+
handleCallState,
29+
handleCallUserConnected,
30+
handleCallUserDisconnected,
2831
handleCallUserJoined,
2932
handleCallUserLeft,
3033
handleCallUserMuted,
@@ -450,6 +453,9 @@ export async function handleEvent(serverUrl: string, msg: WebSocketMessage) {
450453
case WebsocketEvents.CALLS_HOST_REMOVED:
451454
handleHostRemoved(serverUrl, msg);
452455
break;
456+
case WebsocketEvents.CALLS_CALL_STATE:
457+
handleCallState(serverUrl, msg);
458+
break;
453459

454460
case WebsocketEvents.GROUP_RECEIVED:
455461
handleGroupReceivedEvent(serverUrl, msg);

app/components/post_list/post/post.tsx

+6
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,12 @@ const Post = ({
341341
<CallsCustomMessage
342342
serverUrl={serverUrl}
343343
post={post}
344+
345+
// Note: the below are provided by the index, but typescript seems to be having problems.
346+
otherParticipants={false}
347+
isAdmin={false}
348+
isHost={false}
349+
joiningChannelId={null}
344350
/>
345351
);
346352
} else {

app/constants/websocket.ts

+1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ const WebsocketEvents = {
9191
CALLS_HOST_MUTE: `custom_${Calls.PluginId}_host_mute`,
9292
CALLS_HOST_LOWER_HAND: `custom_${Calls.PluginId}_host_lower_hand`,
9393
CALLS_HOST_REMOVED: `custom_${Calls.PluginId}_host_removed`,
94+
CALLS_CALL_STATE: `custom_${Calls.PluginId}_call_state`,
9495

9596
GROUP_RECEIVED: 'received_group',
9697
GROUP_MEMBER_ADD: 'group_member_add',

app/products/calls/actions/calls.ts

+33-6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {forceLogoutIfNecessary} from '@actions/remote/session';
88
import {updateThreadFollowing} from '@actions/remote/thread';
99
import {fetchUsersByIds} from '@actions/remote/user';
1010
import {
11+
endCallConfirmationAlert,
1112
leaveAndJoinWithAlert,
1213
needsRecordingErrorAlert,
1314
needsRecordingWillBePostedAlert,
@@ -28,6 +29,7 @@ import {
2829
setScreenShareURL,
2930
setSpeakerPhone,
3031
} from '@calls/state';
32+
import {type AudioDevice, type Call, type CallSession, type CallsConnection, EndCallReturn} from '@calls/types/calls';
3133
import {General, Preferences} from '@constants';
3234
import Calls from '@constants/calls';
3335
import DatabaseManager from '@database/manager';
@@ -44,7 +46,6 @@ import {displayUsername, getUserIdFromChannelName, isSystemAdmin} from '@utils/u
4446

4547
import {newConnection} from '../connection/connection';
4648

47-
import type {AudioDevice, Call, CallSession, CallsConnection} from '@calls/types/calls';
4849
import type {CallChannelState, CallState, EmojiData, SessionState} from '@mattermost/calls/lib/types';
4950
import type {IntlShape} from 'react-intl';
5051

@@ -132,7 +133,7 @@ export const loadCallForChannel = async (serverUrl: string, channelId: string) =
132133
fetchUsersByIds(serverUrl, Array.from(ids));
133134
}
134135

135-
setCallForChannel(serverUrl, channelId, resp.enabled, call);
136+
setCallForChannel(serverUrl, channelId, call, resp.enabled);
136137

137138
return {data: {call, enabled: resp.enabled}};
138139
};
@@ -160,11 +161,12 @@ const convertOldCallToNew = (call: CallState): CallState => {
160161
};
161162
};
162163

163-
const createCallAndAddToIds = (channelId: string, call: CallState, ids: Set<string>) => {
164-
return {
164+
export const createCallAndAddToIds = (channelId: string, call: CallState, ids?: Set<string>) => {
165+
// Don't cast so that we get alerted to missing types
166+
const convertedCall: Call = {
165167
sessions: Object.values(call.sessions).reduce((accum, cur) => {
166168
// Add the id to the set of UserModels we want to ensure are loaded.
167-
ids.add(cur.user_id);
169+
ids?.add(cur.user_id);
168170

169171
// Create the CallParticipant
170172
accum[cur.session_id] = {
@@ -184,7 +186,9 @@ const createCallAndAddToIds = (channelId: string, call: CallState, ids: Set<stri
184186
hostId: call.host_id,
185187
recState: call.recording,
186188
dismissed: call.dismissed_notification || {},
187-
} as Call;
189+
};
190+
191+
return convertedCall;
188192
};
189193

190194
export const loadConfigAndCalls = async (serverUrl: string, userId: string) => {
@@ -302,6 +306,29 @@ export const leaveCall = (err?: Error) => {
302306
}
303307
};
304308

309+
export const leaveCallConfirmation = async (
310+
intl: IntlShape,
311+
otherParticipants: boolean,
312+
isAdmin: boolean,
313+
isHost: boolean,
314+
serverUrl: string,
315+
channelId: string,
316+
leaveCb?: () => void) => {
317+
const showHostControls = (isHost || isAdmin) && otherParticipants;
318+
const ret = await endCallConfirmationAlert(intl, showHostControls) as EndCallReturn;
319+
switch (ret) {
320+
case EndCallReturn.Cancel:
321+
return;
322+
case EndCallReturn.LeaveCall:
323+
leaveCall();
324+
leaveCb?.();
325+
return;
326+
case EndCallReturn.EndCall:
327+
endCall(serverUrl, channelId);
328+
leaveCb?.();
329+
}
330+
};
331+
305332
export const muteMyself = () => {
306333
if (connection) {
307334
connection.mute();

app/products/calls/alerts.ts

+61-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
22
// See LICENSE.txt for license information.
33

4-
import {Alert} from 'react-native';
4+
import {Alert, type AlertButton, Platform} from 'react-native';
55

66
import {hasMicrophonePermission, joinCall, leaveCall, unmuteMyself} from '@calls/actions';
77
import {dismissIncomingCall, hostRemove} from '@calls/actions/calls';
@@ -15,6 +15,7 @@ import {
1515
removeIncomingCall,
1616
setMicPermissionsGranted,
1717
} from '@calls/state';
18+
import {EndCallReturn} from '@calls/types/calls';
1819
import {errorAlert} from '@calls/utils';
1920
import DatabaseManager from '@database/manager';
2021
import {getChannelById} from '@queries/servers/channel';
@@ -132,7 +133,7 @@ export const leaveAndJoinWithAlert = async (
132133
id: 'mobile.post.cancel',
133134
defaultMessage: 'Cancel',
134135
}),
135-
onPress: async () => resolve(false),
136+
onPress: () => resolve(false),
136137
style: 'destructive',
137138
},
138139
{
@@ -473,3 +474,61 @@ export const removeFromCall = (serverUrl: string, displayName: string, callId: s
473474
style: 'destructive',
474475
}]);
475476
};
477+
478+
export const endCallConfirmationAlert = (intl: IntlShape, showHostControls: boolean) => {
479+
const {formatMessage} = intl;
480+
481+
const asyncAlert = async () => new Promise((resolve) => {
482+
const buttons: AlertButton[] = [{
483+
text: formatMessage({
484+
id: 'mobile.calls_cancel',
485+
defaultMessage: 'Cancel',
486+
}),
487+
onPress: () => resolve(EndCallReturn.Cancel),
488+
style: 'cancel',
489+
}, {
490+
text: formatMessage({
491+
id: 'mobile.calls_host_leave_confirm',
492+
defaultMessage: 'Leave call',
493+
}),
494+
onPress: () => resolve(EndCallReturn.LeaveCall),
495+
style: 'destructive',
496+
}];
497+
const questionMsg = formatMessage({
498+
id: 'mobile.calls_host_leave_title',
499+
defaultMessage: 'Are you sure you want to leave this call?',
500+
});
501+
502+
if (showHostControls) {
503+
const endCallButton: AlertButton = {
504+
text: formatMessage({
505+
id: 'mobile.calls_host_end_confirm',
506+
defaultMessage: 'End call for everyone',
507+
}),
508+
onPress: () => resolve(EndCallReturn.EndCall),
509+
style: 'destructive',
510+
};
511+
const leaveCallButton = {
512+
text: formatMessage({
513+
id: 'mobile.calls_host_leave_confirm',
514+
defaultMessage: 'Leave call',
515+
}),
516+
onPress: () => resolve(EndCallReturn.LeaveCall),
517+
};
518+
519+
if (Platform.OS === 'ios') {
520+
buttons.splice(1, 1, endCallButton, leaveCallButton);
521+
} else {
522+
buttons.splice(1, 1, leaveCallButton, endCallButton);
523+
}
524+
}
525+
526+
if (Platform.OS === 'ios') {
527+
Alert.alert(questionMsg, '', buttons);
528+
} else {
529+
Alert.alert('', questionMsg, buttons);
530+
}
531+
});
532+
533+
return asyncAlert();
534+
};

app/products/calls/components/calls_custom_message/calls_custom_message.tsx

+13-7
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import React, {useCallback} from 'react';
66
import {useIntl} from 'react-intl';
77
import {Text, TouchableOpacity, View} from 'react-native';
88

9-
import {leaveCall} from '@calls/actions';
9+
import {leaveCallConfirmation} from '@calls/actions/calls';
1010
import {leaveAndJoinWithAlert, showLimitRestrictedAlert} from '@calls/alerts';
1111
import {setJoiningChannelId} from '@calls/state';
1212
import CompassIcon from '@components/compass_icon';
@@ -26,11 +26,14 @@ import type UserModel from '@typings/database/models/servers/user';
2626

2727
type Props = {
2828
post: PostModel;
29-
currentUser?: UserModel;
3029
isMilitaryTime: boolean;
30+
joiningChannelId: string | null;
31+
otherParticipants: boolean;
32+
isAdmin: boolean;
33+
isHost: boolean;
34+
currentUser?: UserModel;
3135
limitRestrictedInfo?: LimitRestrictedInfo;
3236
ccChannelId?: string;
33-
joiningChannelId: string | null;
3437
}
3538

3639
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
@@ -131,6 +134,9 @@ export const CallsCustomMessage = ({
131134
ccChannelId,
132135
limitRestrictedInfo,
133136
joiningChannelId,
137+
otherParticipants,
138+
isAdmin,
139+
isHost,
134140
}: Props) => {
135141
const intl = useIntl();
136142
const theme = useTheme();
@@ -154,9 +160,9 @@ export const CallsCustomMessage = ({
154160
setJoiningChannelId(null);
155161
}, [limitRestrictedInfo, intl, serverUrl, post.channelId]);
156162

157-
const leaveHandler = useCallback(() => {
158-
leaveCall();
159-
}, []);
163+
const leaveCallHandler = useCallback(() => {
164+
leaveCallConfirmation(intl, otherParticipants, isAdmin, isHost, serverUrl, post.channelId);
165+
}, [intl, otherParticipants, isAdmin, isHost, serverUrl, post.channelId]);
160166

161167
const title = post.props.title ? (
162168
<Text style={style.title}>
@@ -210,7 +216,7 @@ export const CallsCustomMessage = ({
210216
const button = alreadyInTheCall ? (
211217
<TouchableOpacity
212218
style={[style.callButton, style.leaveCallButton]}
213-
onPress={leaveHandler}
219+
onPress={leaveCallHandler}
214220
>
215221
<CompassIcon
216222
name='phone-hangup'

app/products/calls/components/calls_custom_message/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {of as of$} from 'rxjs';
66
import {distinctUntilChanged, switchMap} from 'rxjs/operators';
77

88
import {CallsCustomMessage} from '@calls/components/calls_custom_message/calls_custom_message';
9-
import {observeIsCallLimitRestricted} from '@calls/observers';
9+
import {observeEndCallDetails, observeIsCallLimitRestricted} from '@calls/observers';
1010
import {observeCurrentCall, observeGlobalCallsState} from '@calls/state';
1111
import {Preferences} from '@constants';
1212
import {getDisplayNamePreferenceAsBool} from '@helpers/api/preference';
@@ -52,6 +52,7 @@ const enhanced = withObservables(['post'], ({serverUrl, post, database}: OwnProp
5252
limitRestrictedInfo: observeIsCallLimitRestricted(database, serverUrl, post.channelId),
5353
ccChannelId,
5454
joiningChannelId,
55+
...observeEndCallDetails(),
5556
};
5657
});
5758

app/products/calls/components/channel_info_start/channel_info_start_button.tsx

+9-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import React, {useCallback, useState} from 'react';
55
import {useIntl} from 'react-intl';
66

7-
import {leaveCall} from '@calls/actions';
7+
import {leaveCallConfirmation} from '@calls/actions/calls';
88
import {leaveAndJoinWithAlert, showLimitRestrictedAlert} from '@calls/alerts';
99
import {useTryCallsFunction} from '@calls/hooks';
1010
import Loading from '@components/loading';
@@ -23,6 +23,9 @@ export interface Props {
2323
alreadyInCall: boolean;
2424
dismissChannelInfo: () => void;
2525
limitRestrictedInfo: LimitRestrictedInfo;
26+
otherParticipants: boolean;
27+
isAdmin: boolean;
28+
isHost: boolean;
2629
}
2730

2831
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
@@ -53,6 +56,9 @@ const ChannelInfoStartButton = ({
5356
alreadyInCall,
5457
dismissChannelInfo,
5558
limitRestrictedInfo,
59+
otherParticipants,
60+
isAdmin,
61+
isHost,
5662
}: Props) => {
5763
const intl = useIntl();
5864
const theme = useTheme();
@@ -66,8 +72,7 @@ const ChannelInfoStartButton = ({
6672

6773
const toggleJoinLeave = useCallback(async () => {
6874
if (alreadyInCall) {
69-
leaveCall();
70-
dismissChannelInfo();
75+
await leaveCallConfirmation(intl, otherParticipants, isAdmin, isHost, serverUrl, channelId, dismissChannelInfo);
7176
} else if (isLimitRestricted) {
7277
showLimitRestrictedAlert(limitRestrictedInfo, intl);
7378
dismissChannelInfo();
@@ -79,7 +84,7 @@ const ChannelInfoStartButton = ({
7984
setConnecting(false);
8085
dismissChannelInfo();
8186
}
82-
}, [isLimitRestricted, alreadyInCall, dismissChannelInfo, intl, serverUrl, channelId, isACallInCurrentChannel]);
87+
}, [isLimitRestricted, alreadyInCall, dismissChannelInfo, intl, serverUrl, channelId, isACallInCurrentChannel, otherParticipants]);
8388

8489
const [tryJoin, msgPostfix] = useTryCallsFunction(toggleJoinLeave);
8590

app/products/calls/components/channel_info_start/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {of as of$} from 'rxjs';
66
import {distinctUntilChanged, switchMap} from 'rxjs/operators';
77

88
import ChannelInfoStartButton from '@calls/components/channel_info_start/channel_info_start_button';
9-
import {observeIsCallLimitRestricted} from '@calls/observers';
9+
import {observeEndCallDetails, observeIsCallLimitRestricted} from '@calls/observers';
1010
import {observeChannelsWithCalls, observeCurrentCall} from '@calls/state';
1111

1212
import type {WithDatabaseArgs} from '@typings/database/database';
@@ -33,6 +33,7 @@ const enhanced = withObservables([], ({serverUrl, channelId, database}: EnhanceP
3333
confirmToJoin,
3434
alreadyInCall,
3535
limitRestrictedInfo: observeIsCallLimitRestricted(database, serverUrl, channelId),
36+
...observeEndCallDetails(),
3637
};
3738
});
3839

0 commit comments

Comments
 (0)