[MM-58129] Calls: Host controls remove participant, view profile (#7938)
* host remove command, host removed feedback * add view profile control * i18n; dismissBottomSheet later * PR comments * remove confusing setPreviousCall(null) * our own separator, bit more space for Android * no need for previousCall due to changes on server * move host removed alert to an error * channel_id is not send via the msg.broadcast * channel_id is still sent via the msg.broadcast
This commit is contained in:
parent
4992a96891
commit
7fbd2e52e2
9 changed files with 150 additions and 10 deletions
|
|
@ -36,6 +36,7 @@ import {
|
|||
handleCallUserVoiceOn,
|
||||
handleHostLowerHand,
|
||||
handleHostMute,
|
||||
handleHostRemoved,
|
||||
handleUserDismissedNotification,
|
||||
} from '@calls/connection/websocket_event_handlers';
|
||||
import {isSupportedServerCalls} from '@calls/utils';
|
||||
|
|
@ -461,6 +462,9 @@ export async function handleEvent(serverUrl: string, msg: WebSocketMessage) {
|
|||
case WebsocketEvents.CALLS_HOST_LOWER_HAND:
|
||||
handleHostLowerHand(serverUrl, msg);
|
||||
break;
|
||||
case WebsocketEvents.CALLS_HOST_REMOVED:
|
||||
handleHostRemoved(serverUrl, msg);
|
||||
break;
|
||||
|
||||
case WebsocketEvents.GROUP_RECEIVED:
|
||||
handleGroupReceivedEvent(serverUrl, msg);
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ const WebsocketEvents = {
|
|||
CALLS_CAPTION: `custom_${Calls.PluginId}_caption`,
|
||||
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`,
|
||||
|
||||
GROUP_RECEIVED: 'received_group',
|
||||
GROUP_MEMBER_ADD: 'group_member_add',
|
||||
|
|
|
|||
|
|
@ -664,3 +664,13 @@ export const hostLowerHand = async (serverUrl: string, callId: string, sessionId
|
|||
}
|
||||
};
|
||||
|
||||
export const hostRemove = async (serverUrl: string, callId: string, sessionId: string) => {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
return client.hostRemove(callId, sessionId);
|
||||
} catch (error) {
|
||||
logDebug('error on hostRemove', getFullErrorMessage(error));
|
||||
await forceLogoutIfNecessary(serverUrl, error);
|
||||
return error;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
import {Alert} from 'react-native';
|
||||
|
||||
import {hasMicrophonePermission, joinCall, leaveCall, unmuteMyself} from '@calls/actions';
|
||||
import {dismissIncomingCall} from '@calls/actions/calls';
|
||||
import {dismissIncomingCall, hostRemove} from '@calls/actions/calls';
|
||||
import {hasBluetoothPermission} from '@calls/actions/permissions';
|
||||
import {userLeftChannelErr, userRemovedFromChannelErr} from '@calls/errors';
|
||||
import {hostRemovedErr, userLeftChannelErr, userRemovedFromChannelErr} from '@calls/errors';
|
||||
import {
|
||||
getCallsConfig,
|
||||
getCallsState,
|
||||
|
|
@ -19,6 +19,7 @@ import {errorAlert} from '@calls/utils';
|
|||
import DatabaseManager from '@database/manager';
|
||||
import {getChannelById} from '@queries/servers/channel';
|
||||
import {getCurrentUser} from '@queries/servers/user';
|
||||
import {dismissBottomSheet} from '@screens/navigation';
|
||||
import {isDMorGM} from '@utils/channel';
|
||||
import {getFullErrorMessage} from '@utils/errors';
|
||||
import {logError} from '@utils/log';
|
||||
|
|
@ -413,8 +414,55 @@ export const showErrorAlertOnClose = (err: Error, intl: IntlShape) => {
|
|||
}),
|
||||
);
|
||||
break;
|
||||
case hostRemovedErr:
|
||||
Alert.alert(
|
||||
intl.formatMessage({
|
||||
id: 'mobile.calls_removed_alert_title',
|
||||
defaultMessage: 'You were removed from the call',
|
||||
}),
|
||||
intl.formatMessage({
|
||||
id: 'mobile.calls_removed_alert_body',
|
||||
defaultMessage: 'The host removed you from the call.',
|
||||
}),
|
||||
[{
|
||||
text: intl.formatMessage({
|
||||
id: 'mobile.calls_dismiss',
|
||||
defaultMessage: 'Dismiss',
|
||||
}),
|
||||
}]);
|
||||
break;
|
||||
default:
|
||||
// Fallback with generic error
|
||||
errorAlert(getFullErrorMessage(err, intl), intl);
|
||||
}
|
||||
};
|
||||
|
||||
export const removeFromCall = (serverUrl: string, displayName: string, callId: string, sessionId: string, intl: IntlShape) => {
|
||||
const {formatMessage} = intl;
|
||||
|
||||
const title = formatMessage({
|
||||
id: 'mobile.calls_remove_alert_title',
|
||||
defaultMessage: 'Remove participant',
|
||||
});
|
||||
const body = formatMessage({
|
||||
id: 'mobile.calls_remove_alert_body',
|
||||
defaultMessage: 'Are you sure you want to remove {displayName} from the call? ',
|
||||
}, {displayName});
|
||||
|
||||
Alert.alert(title, body, [{
|
||||
text: formatMessage({
|
||||
id: 'mobile.post.cancel',
|
||||
defaultMessage: 'Cancel',
|
||||
}),
|
||||
}, {
|
||||
text: formatMessage({
|
||||
id: 'mobile.calls_remove',
|
||||
defaultMessage: 'Remove',
|
||||
}),
|
||||
onPress: () => {
|
||||
hostRemove(serverUrl, callId, sessionId);
|
||||
dismissBottomSheet();
|
||||
},
|
||||
style: 'destructive',
|
||||
}]);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export interface ClientCallsMix {
|
|||
hostMute: (callId: string, sessionId: string) => Promise<ApiResp>;
|
||||
hostScreenOff: (callId: string, sessionId: string) => Promise<ApiResp>;
|
||||
hostLowerHand: (callId: string, sessionId: string) => Promise<ApiResp>;
|
||||
hostRemove: (callId: string, sessionId: string) => Promise<ApiResp>;
|
||||
}
|
||||
|
||||
const ClientCalls = (superclass: any) => class extends superclass {
|
||||
|
|
@ -149,6 +150,16 @@ const ClientCalls = (superclass: any) => class extends superclass {
|
|||
},
|
||||
);
|
||||
};
|
||||
|
||||
hostRemove = async (callId: string, sessionId: string) => {
|
||||
return this.doFetch(
|
||||
`${this.getCallsRoute()}/calls/${callId}/host/remove`,
|
||||
{
|
||||
method: 'post',
|
||||
body: {session_id: sessionId},
|
||||
},
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default ClientCalls;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@
|
|||
import {DeviceEventEmitter} from 'react-native';
|
||||
|
||||
import {fetchUsersByIds} from '@actions/remote/user';
|
||||
import {muteMyself, unraiseHand} from '@calls/actions';
|
||||
import {leaveCall, muteMyself, unraiseHand} from '@calls/actions';
|
||||
import {hostRemovedErr} from '@calls/errors';
|
||||
import {
|
||||
callEnded,
|
||||
callStarted,
|
||||
|
|
@ -228,3 +229,14 @@ export const handleHostLowerHand = async (serverUrl: string, msg: WebSocketMessa
|
|||
|
||||
unraiseHand();
|
||||
};
|
||||
|
||||
export const handleHostRemoved = async (serverUrl: string, msg: WebSocketMessage<HostControlsMsgData>) => {
|
||||
const currentCall = getCurrentCall();
|
||||
if (currentCall?.serverUrl !== serverUrl ||
|
||||
currentCall?.channelId !== msg.data.channel_id ||
|
||||
currentCall?.mySessionId !== msg.data.session_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
leaveCall(hostRemovedErr);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@
|
|||
|
||||
export const userRemovedFromChannelErr = new Error('user was removed from channel');
|
||||
export const userLeftChannelErr = new Error('user has left channel');
|
||||
export const hostRemovedErr = new Error('user was removed by host');
|
||||
|
|
|
|||
|
|
@ -3,21 +3,28 @@
|
|||
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {StyleSheet} from 'react-native';
|
||||
import {Platform, View} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import {hostLowerHand, hostMake, hostMuteSession, hostStopScreenshare} from '@calls/actions/calls';
|
||||
import {removeFromCall} from '@calls/alerts';
|
||||
import {useHostMenus} from '@calls/hooks';
|
||||
import SlideUpPanelItem from '@components/slide_up_panel_item';
|
||||
import {Screens} from '@constants';
|
||||
import {useTheme} from '@context/theme';
|
||||
import BottomSheet from '@screens/bottom_sheet';
|
||||
import {dismissBottomSheet} from '@screens/navigation';
|
||||
import UserProfileTitle from '@screens/user_profile/title';
|
||||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {displayUsername} from '@utils/user';
|
||||
|
||||
import type {CallSession, CurrentCall} from '@calls/types/calls';
|
||||
|
||||
const TITLE_HEIGHT = 118;
|
||||
const ITEM_HEIGHT = 48;
|
||||
const SEPARATOR_HEIGHT = 17;
|
||||
const ANDROID_BUMP_HEIGHT = 20;
|
||||
|
||||
type Props = {
|
||||
currentCall: CurrentCall;
|
||||
|
|
@ -27,11 +34,18 @@ type Props = {
|
|||
hideGuestTags: boolean;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
const getStyleFromTheme = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
iconStyle: {
|
||||
marginRight: 6,
|
||||
},
|
||||
});
|
||||
separator: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.2),
|
||||
height: 1,
|
||||
width: '100%',
|
||||
alignSelf: 'center',
|
||||
marginVertical: 8,
|
||||
},
|
||||
}));
|
||||
|
||||
export const HostControls = ({
|
||||
currentCall,
|
||||
|
|
@ -41,7 +55,10 @@ export const HostControls = ({
|
|||
hideGuestTags,
|
||||
}: Props) => {
|
||||
const intl = useIntl();
|
||||
const theme = useTheme();
|
||||
const {bottom} = useSafeAreaInsets();
|
||||
const {openUserProfile} = useHostMenus();
|
||||
const styles = getStyleFromTheme(theme);
|
||||
|
||||
const sharingScreen = currentCall.screenOn === session.sessionId;
|
||||
|
||||
|
|
@ -63,20 +80,35 @@ export const HostControls = ({
|
|||
const stopScreensharePress = useCallback(async () => {
|
||||
hostStopScreenshare(currentCall.serverUrl, currentCall.channelId, session.sessionId);
|
||||
await dismissBottomSheet();
|
||||
}, [currentCall.serverUrl, currentCall.id, session.sessionId]);
|
||||
}, [currentCall.serverUrl, currentCall.channelId, session.sessionId]);
|
||||
|
||||
const profilePress = useCallback(async () => {
|
||||
await dismissBottomSheet();
|
||||
openUserProfile(session);
|
||||
}, [session]);
|
||||
|
||||
const removePress = useCallback(async () => {
|
||||
const displayName = displayUsername(session.userModel, intl.locale, teammateNameDisplay, true);
|
||||
removeFromCall(currentCall.serverUrl, displayName, currentCall.channelId, session.sessionId, intl);
|
||||
}, [session.userModel, intl, teammateNameDisplay, currentCall.serverUrl, currentCall.channelId, session.sessionId]);
|
||||
|
||||
const snapPoints = useMemo(() => {
|
||||
const items = 1 + (session.muted ? 0 : 1) + (sharingScreen ? 1 : 0) + (session.raisedHand ? 1 : 0);
|
||||
const items = 3 + (session.muted ? 0 : 1) + (sharingScreen ? 1 : 0) + (session.raisedHand ? 1 : 0);
|
||||
return [
|
||||
1,
|
||||
bottomSheetSnapPoint(items, ITEM_HEIGHT, bottom) + TITLE_HEIGHT,
|
||||
bottomSheetSnapPoint(items, ITEM_HEIGHT, bottom) + TITLE_HEIGHT + SEPARATOR_HEIGHT + (Platform.OS === 'android' ? ANDROID_BUMP_HEIGHT : 0),
|
||||
];
|
||||
}, [bottom, session.muted, sharingScreen, session.raisedHand]);
|
||||
|
||||
const makeHostText = intl.formatMessage({id: 'mobile.calls_make_host', defaultMessage: 'Make host'});
|
||||
const muteText = intl.formatMessage({id: 'mobile.calls_mute_participant', defaultMessage: 'Mute participant'});
|
||||
const lowerHandText = intl.formatMessage({id: 'mobile.calls_lower_hand', defaultMessage: 'Lower hand'});
|
||||
const stopScreenshareText = intl.formatMessage({id: 'mobile.calls_stop_screenshare', defaultMessage: 'Stop screen share'});
|
||||
const stopScreenshareText = intl.formatMessage({
|
||||
id: 'mobile.calls_stop_screenshare',
|
||||
defaultMessage: 'Stop screen share',
|
||||
});
|
||||
const profileText = intl.formatMessage({id: 'mobile.calls_view_profile', defaultMessage: 'View profile'});
|
||||
const removeText = intl.formatMessage({id: 'mobile.calls_remove_participant', defaultMessage: 'Remove from call'});
|
||||
|
||||
const renderContent = () => {
|
||||
if (!session?.userModel) {
|
||||
|
|
@ -125,6 +157,20 @@ export const HostControls = ({
|
|||
onPress={makeHostPress}
|
||||
text={makeHostText}
|
||||
/>
|
||||
<SlideUpPanelItem
|
||||
leftIcon={'account-outline'}
|
||||
leftIconStyles={styles.iconStyle}
|
||||
onPress={profilePress}
|
||||
text={profileText}
|
||||
/>
|
||||
<View style={styles.separator}/>
|
||||
<SlideUpPanelItem
|
||||
leftIcon={'minus-circle-outline'}
|
||||
leftIconStyles={styles.iconStyle}
|
||||
onPress={removePress}
|
||||
text={removeText}
|
||||
destructive={true}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -515,6 +515,12 @@
|
|||
"mobile.calls_recording_start_no_permissions": "You don't have permissions to start a recording. Please ask the call host to start a recording.",
|
||||
"mobile.calls_recording_stop_no_permissions": "You don't have permissions to stop the recording. Please ask the call host to stop the recording.",
|
||||
"mobile.calls_recording_stop_none_in_progress": "No recording is in progress.",
|
||||
"mobile.calls_remove": "Remove",
|
||||
"mobile.calls_remove_alert_body": "Are you sure you want to remove {displayName} from the call? ",
|
||||
"mobile.calls_remove_alert_title": "Remove participant",
|
||||
"mobile.calls_remove_participant": "Remove from call",
|
||||
"mobile.calls_removed_alert_body": "The host removed you from the call.",
|
||||
"mobile.calls_removed_alert_title": "You were removed from the call",
|
||||
"mobile.calls_request_message": "Calls are currently running in test mode and only system admins can start them. Reach out directly to your system admin for assistance",
|
||||
"mobile.calls_request_title": "Calls is not currently enabled",
|
||||
"mobile.calls_see_logs": "See server logs",
|
||||
|
|
@ -531,6 +537,7 @@
|
|||
"mobile.calls_user_left_channel_error_title": "You left the channel",
|
||||
"mobile.calls_user_removed_from_channel_error_message": "You have been removed from the channel, and have been disconnected from the call.",
|
||||
"mobile.calls_user_removed_from_channel_error_title": "You were removed from channel",
|
||||
"mobile.calls_view_profile": "View profile",
|
||||
"mobile.calls_viewing_screen": "You are viewing {name}'s screen",
|
||||
"mobile.calls_you": "(you)",
|
||||
"mobile.calls_you_2": "You",
|
||||
|
|
|
|||
Loading…
Reference in a new issue