Send remote mute requests in group calls and call links

This commit is contained in:
ayumi-signal
2026-02-27 10:36:15 -08:00
committed by GitHub
parent b155aa1cfb
commit 54e5b64ab0
25 changed files with 226 additions and 48 deletions

View File

@@ -130,6 +130,7 @@ import {
} from '../../textsecure/WebAPI.preload.js';
import { itemStorage } from '../../textsecure/Storage.preload.js';
import type { SizeCallbackType } from '../../calling/VideoSupport.preload.js';
import type { NoopActionType } from './noop.std.js';
const { omit } = lodash;
@@ -1287,6 +1288,27 @@ function blockClient(
};
}
function sendRemoteMute(
demuxId: number
): ThunkAction<void, RootStateType, unknown, NoopActionType> {
return (dispatch, getState) => {
const state = getState();
const activeCall = getActiveCall(state.calling);
if (!isGroupOrAdhocCallState(activeCall)) {
log.warn(
'sendRemoteMute: Trying to remote mute without active group or adhoc call'
);
return;
}
calling.sendRemoteMute(activeCall.conversationId, demuxId);
dispatch({
type: 'NOOP',
payload: null,
});
};
}
function callStateChange(
payload: CallStateChangeType
): ThunkAction<
@@ -3071,6 +3093,7 @@ export const actions = {
returnToActiveCall,
sendGroupCallRaiseHand,
sendGroupCallReaction,
sendRemoteMute,
selectPresentingSource,
setGroupCallVideoRequest,
setIsCallActive,

View File

@@ -22,6 +22,7 @@ import type { StateType as RootStateType } from '../reducer.preload.js';
import * as SingleServePromise from '../../services/singleServePromise.std.js';
import { isKeyTransparencyAvailable } from '../../services/keyTransparency.preload.js';
import * as Stickers from '../../types/Stickers.preload.js';
import type { ContactModalStateType } from '../../types/globalModals.std.js';
import { UsernameOnboardingState } from '../../types/globalModals.std.js';
import { createLogger } from '../../logging/log.std.js';
import {
@@ -257,11 +258,6 @@ const HIDE_LOW_DISK_SPACE_BACKUP_IMPORT_MODAL =
'globalModals/HIDE_LOW_DISK_SPACE_BACKUP_IMPORT_MODAL';
const TOGGLE_PIN_MESSAGE_DIALOG = 'globalModals/TOGGLE_PIN_MESSAGE_DIALOG';
export type ContactModalStateType = ReadonlyDeep<{
contactId: string;
conversationId?: string;
}>;
export type UserNotFoundModalStateType = ReadonlyDeep<
| {
type: 'phoneNumber';
@@ -738,13 +734,19 @@ function hideContactModal(): HideContactModalActionType {
};
}
function showContactModal(
contactId: string,
conversationId?: string
): ShowContactModalActionType {
function showContactModal({
activeCallDemuxId,
contactId,
conversationId,
}: {
contactId: string;
conversationId?: string;
activeCallDemuxId?: number;
}): ShowContactModalActionType {
return {
type: SHOW_CONTACT_MODAL,
payload: {
activeCallDemuxId,
contactId,
conversationId,
},

View File

@@ -12,6 +12,7 @@ import type {
DirectCallStateType,
GroupCallStateType,
ActiveCallStateType,
GroupCallParticipantInfoType,
} from '../ducks/calling.preload.js';
import { getRingingCall as getRingingCallHelper } from '../ducks/callingHelpers.std.js';
import type { PresentedSource } from '../../types/Calling.std.js';
@@ -23,6 +24,7 @@ import {
import { getUserACI } from './user.std.js';
import { getOwn } from '../../util/getOwn.std.js';
import type { AciString } from '../../types/ServiceId.std.js';
import { isGroupOrAdhocCallState } from '../../util/isGroupOrAdhocCall.std.js';
export type CallStateType = DirectCallStateType | GroupCallStateType;
@@ -183,3 +185,21 @@ export const getPresentingSource = createSelector(
(activeCallState): PresentedSource | undefined =>
activeCallState?.presentingSource
);
type ParticipantByDemuxIdInCallSelectorType = (
demuxId: number | undefined
) => GroupCallParticipantInfoType | undefined;
export const getParticipantInActiveCall = createSelector(
getActiveCall,
(call: CallStateType | undefined): ParticipantByDemuxIdInCallSelectorType =>
(demuxId: number | undefined): GroupCallParticipantInfoType | undefined => {
if (demuxId == null || !isGroupOrAdhocCallState(call)) {
return undefined;
}
return call.remoteParticipants.find(
participant => participant.demuxId === demuxId
);
}
);

View File

@@ -15,6 +15,7 @@ import { getHasStoriesSelector } from '../selectors/stories2.dom.js';
import {
getActiveCallState,
isInFullScreenCall as getIsInFullScreenCall,
getParticipantInActiveCall,
} from '../selectors/calling.std.js';
import { useStoriesActions } from '../ducks/stories.preload.js';
import { useConversationsActions } from '../ducks/conversations.preload.js';
@@ -26,11 +27,18 @@ import { strictAssert } from '../../util/assert.std.js';
export const SmartContactModal = memo(function SmartContactModal() {
const i18n = useSelector(getIntl);
const theme = useSelector(getTheme);
const { conversationId, contactId } = useSelector(getContactModalState) ?? {};
const { conversationId, contactId, activeCallDemuxId } =
useSelector(getContactModalState) ?? {};
const conversationSelector = useSelector(getConversationSelector);
const hasStoriesSelector = useSelector(getHasStoriesSelector);
const activeCallState = useSelector(getActiveCallState);
const isInFullScreenCall = useSelector(getIsInFullScreenCall);
const getCallParticipant = useSelector(getParticipantInActiveCall);
const callParticipant = getCallParticipant(activeCallDemuxId);
const isRemoteMuteVisible = Boolean(callParticipant);
const isMuted = !callParticipant?.hasRemoteAudio;
const badgesSelector = useSelector(getBadgesSelector);
const areWeASubscriber = useSelector(getAreWeASubscriber);
@@ -78,6 +86,7 @@ export const SmartContactModal = memo(function SmartContactModal() {
onOutgoingVideoCallInConversation,
onOutgoingAudioCallInConversation,
togglePip,
sendRemoteMute,
} = useCallingActions();
const handleOpenEditNicknameAndNoteModal = useCallback(() => {
@@ -103,10 +112,14 @@ export const SmartContactModal = memo(function SmartContactModal() {
isAdmin={isAdmin}
isInFullScreenCall={isInFullScreenCall}
isMember={isMember}
isMuted={isMuted}
isRemoteMuteVisible={isRemoteMuteVisible}
activeCallDemuxId={activeCallDemuxId}
onOpenEditNicknameAndNoteModal={handleOpenEditNicknameAndNoteModal}
onOutgoingAudioCallInConversation={onOutgoingAudioCallInConversation}
onOutgoingVideoCallInConversation={onOutgoingVideoCallInConversation}
removeMemberFromGroup={removeMemberFromGroup}
sendRemoteMute={sendRemoteMute}
showConversation={showConversation}
startAvatarDownload={() => startAvatarDownload(contact.id)}
theme={theme}

View File

@@ -17,7 +17,7 @@ export const SmartContactName = memo(function SmartContactName({
}: ExternalProps) {
const i18n = useSelector(getIntl);
const getConversation = useSelector(getConversationSelector);
const currentConversationId = useSelector(getSelectedConversationId);
const conversationId = useSelector(getSelectedConversationId);
const { showContactModal } = useGlobalModalActions();
@@ -26,8 +26,8 @@ export const SmartContactName = memo(function SmartContactName({
}, [getConversation, contactId]);
const handleClick = useCallback(() => {
showContactModal(contactId, currentConversationId);
}, [showContactModal, contactId, currentConversationId]);
showContactModal({ contactId, conversationId });
}, [showContactModal, contactId, conversationId]);
return (
<ContactName