diff --git a/app/products/calls/actions/calls.ts b/app/products/calls/actions/calls.ts index 8dccf148c..d4d1359fa 100644 --- a/app/products/calls/actions/calls.ts +++ b/app/products/calls/actions/calls.ts @@ -642,6 +642,17 @@ export const hostMuteSession = async (serverUrl: string, callId: string, session } }; +export const hostMuteOthers = async (serverUrl: string, callId: string) => { + try { + const client = NetworkManager.getClient(serverUrl); + return client.hostMuteOthers(callId); + } catch (error) { + logDebug('error on hostMuteOthers', getFullErrorMessage(error)); + await forceLogoutIfNecessary(serverUrl, error); + return error; + } +}; + export const hostStopScreenshare = async (serverUrl: string, callId: string, sessionId: string) => { try { const client = NetworkManager.getClient(serverUrl); diff --git a/app/products/calls/client/rest.ts b/app/products/calls/client/rest.ts index 4c3c0800b..22eb1153d 100644 --- a/app/products/calls/client/rest.ts +++ b/app/products/calls/client/rest.ts @@ -19,6 +19,7 @@ export interface ClientCallsMix { dismissCall: (channelId: string) => Promise; hostMake: (callId: string, newHostId: string) => Promise; hostMute: (callId: string, sessionId: string) => Promise; + hostMuteOthers: (callId: string) => Promise; hostScreenOff: (callId: string, sessionId: string) => Promise; hostLowerHand: (callId: string, sessionId: string) => Promise; hostRemove: (callId: string, sessionId: string) => Promise; @@ -131,6 +132,13 @@ const ClientCalls = (superclass: any) => class extends superclass { ); }; + hostMuteOthers = async (callId: string) => { + return this.doFetch( + `${this.getCallsRoute()}/calls/${callId}/host/mute-others`, + {method: 'post'}, + ); + }; + hostScreenOff = async (callId: string, sessionId: string) => { return this.doFetch( `${this.getCallsRoute()}/calls/${callId}/host/screen-off`, diff --git a/app/products/calls/screens/participants_list/index.ts b/app/products/calls/screens/participants_list/index.ts index 2e881e8c7..8cec1f1b5 100644 --- a/app/products/calls/screens/participants_list/index.ts +++ b/app/products/calls/screens/participants_list/index.ts @@ -7,6 +7,7 @@ import {distinctUntilChanged, switchMap} from 'rxjs/operators'; import {observeCallDatabase, observeCurrentSessionsDict} from '@calls/observers'; import {ParticipantsList} from '@calls/screens/participants_list/participants_list'; +import {observeCurrentCall} from '@calls/state'; import {observeTeammateNameDisplay} from '@queries/servers/user'; const enhanced = withObservables([], () => { @@ -14,10 +15,22 @@ const enhanced = withObservables([], () => { switchMap((db) => (db ? observeTeammateNameDisplay(db) : of$(''))), distinctUntilChanged(), ); + const callServerUrl = observeCurrentCall().pipe( + switchMap((call) => of$(call?.serverUrl)), + ); + const callChannelId = observeCurrentCall().pipe( + switchMap((call) => of$(call?.channelId)), + ); + const callUserId = observeCurrentCall().pipe( + switchMap((call) => of$(call?.myUserId)), + ); return { sessionsDict: observeCurrentSessionsDict(), teammateNameDisplay, + callServerUrl, + callChannelId, + callUserId, }; }); diff --git a/app/products/calls/screens/participants_list/participants_list.tsx b/app/products/calls/screens/participants_list/participants_list.tsx index f0200d0e4..e4a86ca2c 100644 --- a/app/products/calls/screens/participants_list/participants_list.tsx +++ b/app/products/calls/screens/participants_list/participants_list.tsx @@ -4,14 +4,16 @@ import {BottomSheetFlatList} from '@gorhom/bottom-sheet'; import React, {useCallback, useMemo} from 'react'; import {useIntl} from 'react-intl'; -import {type ListRenderItemInfo, useWindowDimensions, View} from 'react-native'; +import {type ListRenderItemInfo, Text, TouchableOpacity, useWindowDimensions, View} from 'react-native'; import {FlatList} from 'react-native-gesture-handler'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; -import {useHostMenus} from '@calls/hooks'; +import {hostMuteOthers} from '@calls/actions/calls'; +import {useHostControlsAvailable, useHostMenus} from '@calls/hooks'; import {Participant} from '@calls/screens/participants_list/participant'; import Pill from '@calls/screens/participants_list/pill'; import {sortSessions} from '@calls/utils'; +import CompassIcon from '@components/compass_icon'; import FormattedText from '@components/formatted_text'; import {Screens} from '@constants'; import {useTheme} from '@context/theme'; @@ -31,11 +33,15 @@ type Props = { closeButtonId: string; sessionsDict: Dictionary; teammateNameDisplay: string; + callServerUrl?: string; + callChannelId?: string; + callUserId?: string; } const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ header: { paddingBottom: 12, + paddingRight: 2, flexDirection: 'row', gap: 8, alignItems: 'center', @@ -44,12 +50,35 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({ color: theme.centerChannelColor, ...typography('Heading', 600, 'SemiBold'), }, + muteButton: { + flexDirection: 'row', + padding: 6, + gap: 6, + alignItems: 'center', + marginLeft: 'auto', + }, + muteIcon: { + color: theme.buttonBg, + }, + muteText: { + color: theme.buttonBg, + ...typography('Body', 100, 'SemiBold'), + marginTop: -2, + }, })); -export const ParticipantsList = ({closeButtonId, sessionsDict, teammateNameDisplay}: Props) => { +export const ParticipantsList = ({ + closeButtonId, + sessionsDict, + teammateNameDisplay, + callServerUrl, + callChannelId, + callUserId, +}: Props) => { const intl = useIntl(); const theme = useTheme(); const {onPress} = useHostMenus(); + const hostControlsAvailable = useHostControlsAvailable(); const {bottom} = useSafeAreaInsets(); const {height} = useWindowDimensions(); const isTablet = useIsTablet(); @@ -63,6 +92,14 @@ export const ParticipantsList = ({closeButtonId, sessionsDict, teammateNameDispl if (sessions.length > MIN_ROWS && snapPoint1 < snapPoint2) { snapPoints.push(snapPoint2); } + const otherUnMuted = useMemo(() => sessions.some((s) => !s.muted && s.userId !== callUserId), [sessions, callUserId]); + + const muteOthersPress = useCallback(async () => { + if (!callServerUrl || !callChannelId) { + return; + } + hostMuteOthers(callServerUrl, callChannelId); + }, [callServerUrl, callChannelId]); const renderItem = useCallback(({item}: ListRenderItemInfo) => ( + {hostControlsAvailable && otherUnMuted && + + + + {intl.formatMessage({id: 'mobile.calls_mute_others', defaultMessage: 'Mute others'})} + + + }