From 2eb8adb3f673569fe7807f025133f17218af8802 Mon Sep 17 00:00:00 2001 From: Christopher Poile Date: Mon, 6 May 2024 15:14:40 -0400 Subject: [PATCH] [MM-57958] Calls: Host controls: stop screenshare, mute, lower hand (#7929) * add muteSession & stopScreenshare controls; obey mute host ctrl * lower hand host control; obey lower hand ws msg * no need to await host api call --- app/actions/websocket/index.ts | 8 +++ app/constants/websocket.ts | 2 + app/products/calls/actions/calls.ts | 40 +++++++++++++- app/products/calls/client/rest.ts | 37 ++++++++++++- .../connection/websocket_event_handlers.ts | 26 ++++++++- .../screens/host_controls/host_controls.tsx | 55 +++++++++++++++++-- app/products/calls/types/calls.ts | 11 ++++ assets/base/i18n/en.json | 2 + 8 files changed, 170 insertions(+), 11 deletions(-) diff --git a/app/actions/websocket/index.ts b/app/actions/websocket/index.ts index df48780be..29ff75e65 100644 --- a/app/actions/websocket/index.ts +++ b/app/actions/websocket/index.ts @@ -34,6 +34,8 @@ import { handleCallUserUnraiseHand, handleCallUserVoiceOff, handleCallUserVoiceOn, + handleHostLowerHand, + handleHostMute, handleUserDismissedNotification, } from '@calls/connection/websocket_event_handlers'; import {isSupportedServerCalls} from '@calls/utils'; @@ -453,6 +455,12 @@ export async function handleEvent(serverUrl: string, msg: WebSocketMessage) { case WebsocketEvents.CALLS_CAPTION: handleCallCaption(serverUrl, msg); break; + case WebsocketEvents.CALLS_HOST_MUTE: + handleHostMute(serverUrl, msg); + break; + case WebsocketEvents.CALLS_HOST_LOWER_HAND: + handleHostLowerHand(serverUrl, msg); + break; case WebsocketEvents.GROUP_RECEIVED: handleGroupReceivedEvent(serverUrl, msg); diff --git a/app/constants/websocket.ts b/app/constants/websocket.ts index 906ae15ea..d1594b9ff 100644 --- a/app/constants/websocket.ts +++ b/app/constants/websocket.ts @@ -88,6 +88,8 @@ const WebsocketEvents = { CALLS_HOST_CHANGED: `custom_${Calls.PluginId}_call_host_changed`, CALLS_USER_DISMISSED_NOTIFICATION: `custom_${Calls.PluginId}_user_dismissed_notification`, CALLS_CAPTION: `custom_${Calls.PluginId}_caption`, + CALLS_HOST_MUTE: `custom_${Calls.PluginId}_host_mute`, + CALLS_HOST_LOWER_HAND: `custom_${Calls.PluginId}_host_lower_hand`, 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 3677ae428..db970f933 100644 --- a/app/products/calls/actions/calls.ts +++ b/app/products/calls/actions/calls.ts @@ -620,13 +620,47 @@ const handleEndCall = async (serverUrl: string, channelId: string, currentUserId ); }; -export const makeHost = async (serverUrl: string, callId: string, newHostId: string) => { +export const hostMake = async (serverUrl: string, callId: string, newHostId: string) => { try { const client = NetworkManager.getClient(serverUrl); - return client.makeHost(callId, newHostId); + return client.hostMake(callId, newHostId); } catch (error) { - logDebug('error on makeHost', getFullErrorMessage(error)); + logDebug('error on hostMake', getFullErrorMessage(error)); await forceLogoutIfNecessary(serverUrl, error); return error; } }; + +export const hostMuteSession = async (serverUrl: string, callId: string, sessionId: string) => { + try { + const client = NetworkManager.getClient(serverUrl); + return client.hostMute(callId, sessionId); + } catch (error) { + logDebug('error on hostMute', getFullErrorMessage(error)); + await forceLogoutIfNecessary(serverUrl, error); + return error; + } +}; + +export const hostStopScreenshare = async (serverUrl: string, callId: string, sessionId: string) => { + try { + const client = NetworkManager.getClient(serverUrl); + return client.hostScreenOff(callId, sessionId); + } catch (error) { + logDebug('error on hostStopScreenshare', getFullErrorMessage(error)); + await forceLogoutIfNecessary(serverUrl, error); + return error; + } +}; + +export const hostLowerHand = async (serverUrl: string, callId: string, sessionId: string) => { + try { + const client = NetworkManager.getClient(serverUrl); + return client.hostLowerHand(callId, sessionId); + } catch (error) { + logDebug('error on hostLowerHand', getFullErrorMessage(error)); + await forceLogoutIfNecessary(serverUrl, error); + return error; + } +}; + diff --git a/app/products/calls/client/rest.ts b/app/products/calls/client/rest.ts index 8d914983e..19a4a235e 100644 --- a/app/products/calls/client/rest.ts +++ b/app/products/calls/client/rest.ts @@ -17,7 +17,10 @@ export interface ClientCallsMix { startCallRecording: (callId: string) => Promise; stopCallRecording: (callId: string) => Promise; dismissCall: (channelId: string) => Promise; - makeHost: (callId: string, newHostId: string) => Promise; + hostMake: (callId: string, newHostId: string) => Promise; + hostMute: (callId: string, sessionId: string) => Promise; + hostScreenOff: (callId: string, sessionId: string) => Promise; + hostLowerHand: (callId: string, sessionId: string) => Promise; } const ClientCalls = (superclass: any) => class extends superclass { @@ -107,7 +110,7 @@ const ClientCalls = (superclass: any) => class extends superclass { ); }; - makeHost = async (callId: string, newHostId: string) => { + hostMake = async (callId: string, newHostId: string) => { return this.doFetch( `${this.getCallsRoute()}/calls/${callId}/host/make`, { @@ -116,6 +119,36 @@ const ClientCalls = (superclass: any) => class extends superclass { }, ); }; + + hostMute = async (callId: string, sessionId: string) => { + return this.doFetch( + `${this.getCallsRoute()}/calls/${callId}/host/mute`, + { + method: 'post', + body: {session_id: sessionId}, + }, + ); + }; + + hostScreenOff = async (callId: string, sessionId: string) => { + return this.doFetch( + `${this.getCallsRoute()}/calls/${callId}/host/screen-off`, + { + method: 'post', + body: {session_id: sessionId}, + }, + ); + }; + + hostLowerHand = async (callId: string, sessionId: string) => { + return this.doFetch( + `${this.getCallsRoute()}/calls/${callId}/host/lower-hand`, + { + method: 'post', + body: {session_id: sessionId}, + }, + ); + }; }; export default ClientCalls; diff --git a/app/products/calls/connection/websocket_event_handlers.ts b/app/products/calls/connection/websocket_event_handlers.ts index 6986ef6a2..a7938d9df 100644 --- a/app/products/calls/connection/websocket_event_handlers.ts +++ b/app/products/calls/connection/websocket_event_handlers.ts @@ -4,10 +4,12 @@ import {DeviceEventEmitter} from 'react-native'; import {fetchUsersByIds} from '@actions/remote/user'; +import {muteMyself, unraiseHand} from '@calls/actions'; import { callEnded, callStarted, getCallsConfig, + getCurrentCall, receivedCaption, removeIncomingCall, setCallScreenOff, @@ -29,7 +31,7 @@ import Calls from '@constants/calls'; import DatabaseManager from '@database/manager'; import {getCurrentUserId} from '@queries/servers/system'; -import type {CallRecordingStateData} from '@calls/types/calls'; +import type {CallRecordingStateData, HostControlsLowerHandMsgData, HostControlsMsgData} from '@calls/types/calls'; import type { CallHostChangedData, CallJobState, @@ -204,3 +206,25 @@ export const handleUserDismissedNotification = async (serverUrl: string, msg: We export const handleCallCaption = (serverUrl: string, msg: WebSocketMessage) => { receivedCaption(serverUrl, msg.data); }; + +export const handleHostMute = async (serverUrl: string, msg: WebSocketMessage) => { + const currentCall = getCurrentCall(); + if (currentCall?.serverUrl !== serverUrl || + currentCall?.channelId !== msg.data.channel_id || + currentCall?.mySessionId !== msg.data.session_id) { + return; + } + + muteMyself(); +}; + +export const handleHostLowerHand = async (serverUrl: string, msg: WebSocketMessage) => { + const currentCall = getCurrentCall(); + if (currentCall?.serverUrl !== serverUrl || + currentCall?.channelId !== msg.data.channel_id || + currentCall?.mySessionId !== msg.data.session_id) { + return; + } + + unraiseHand(); +}; diff --git a/app/products/calls/screens/host_controls/host_controls.tsx b/app/products/calls/screens/host_controls/host_controls.tsx index 743ce419b..fd750816d 100644 --- a/app/products/calls/screens/host_controls/host_controls.tsx +++ b/app/products/calls/screens/host_controls/host_controls.tsx @@ -6,7 +6,7 @@ import {useIntl} from 'react-intl'; import {StyleSheet} from 'react-native'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; -import {makeHost} from '@calls/actions/calls'; +import {hostLowerHand, hostMake, hostMuteSession, hostStopScreenshare} from '@calls/actions/calls'; import SlideUpPanelItem from '@components/slide_up_panel_item'; import {Screens} from '@constants'; import BottomSheet from '@screens/bottom_sheet'; @@ -43,19 +43,40 @@ export const HostControls = ({ const intl = useIntl(); const {bottom} = useSafeAreaInsets(); + const sharingScreen = currentCall.screenOn === session.sessionId; + const makeHostPress = useCallback(async () => { - await makeHost(currentCall.serverUrl, currentCall.channelId, session.userId); + hostMake(currentCall.serverUrl, currentCall.channelId, session.userId); await dismissBottomSheet(); - }, [currentCall, session.userId]); + }, [currentCall.serverUrl, currentCall.channelId, session.userId]); + + const mutePress = useCallback(async () => { + hostMuteSession(currentCall.serverUrl, currentCall.channelId, session.sessionId); + await dismissBottomSheet(); + }, [currentCall.serverUrl, currentCall.channelId, session.sessionId]); + + const lowerHandPress = useCallback(async () => { + hostLowerHand(currentCall.serverUrl, currentCall.channelId, session.sessionId); + await dismissBottomSheet(); + }, [currentCall.serverUrl, currentCall.channelId, session.sessionId]); + + const stopScreensharePress = useCallback(async () => { + hostStopScreenshare(currentCall.serverUrl, currentCall.channelId, session.sessionId); + await dismissBottomSheet(); + }, [currentCall.serverUrl, currentCall.id, session.sessionId]); const snapPoints = useMemo(() => { + const items = 1 + (session.muted ? 0 : 1) + (sharingScreen ? 1 : 0) + (session.raisedHand ? 1 : 0); return [ 1, - bottomSheetSnapPoint(1, ITEM_HEIGHT, bottom) + TITLE_HEIGHT, + bottomSheetSnapPoint(items, ITEM_HEIGHT, bottom) + TITLE_HEIGHT, ]; - }, [bottom]); + }, [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 renderContent = () => { if (!session?.userModel) { @@ -74,6 +95,30 @@ export const HostControls = ({ user={session.userModel} hideGuestTags={hideGuestTags} /> + {!session.muted && + + } + {Boolean(session.raisedHand) && + + } + {sharingScreen && + + }