Enable remote mute for external users.

This commit is contained in:
Greyson Parrelli
2026-04-17 16:52:11 -04:00
parent 8730e28282
commit cd01d5f0b7
4 changed files with 46 additions and 36 deletions

View File

@@ -69,7 +69,6 @@ import org.thoughtcrime.securesms.events.GroupCallRaiseHandEvent
import org.thoughtcrime.securesms.events.WebRtcViewModel
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.RemoteConfig
/**
* Renders information about a call (1:1, group, or call link) and provides actions available for
@@ -120,7 +119,6 @@ object CallInfoView {
onContactDetails = callbacks::onContactDetails,
onViewSafetyNumber = callbacks::onViewSafetyNumber,
onGoToChat = callbacks::onGoToChat,
isInternalUser = RemoteConfig.internalUser,
modifier = modifier
)
}
@@ -169,7 +167,6 @@ private fun CallInfo(
onContactDetails: (CallParticipant) -> Unit = {},
onViewSafetyNumber: (CallParticipant) -> Unit = {},
onGoToChat: (CallParticipant) -> Unit = {},
isInternalUser: Boolean = false,
modifier: Modifier = Modifier
) {
var selectedParticipant by remember { mutableStateOf<CallParticipant?>(null) }
@@ -278,14 +275,10 @@ private fun CallInfo(
isSelfAdmin = controlAndInfoState.isSelfAdmin() && !participantsState.inCallLobby,
isCallLink = controlAndInfoState.callLink != null,
onBlockClicked = onBlock,
onParticipantClicked = if (isInternalUser) {
{ participant ->
if (!participant.recipient.isSelf) {
selectedParticipant = participant
}
onParticipantClicked = { participant ->
if (!participant.recipient.isSelf) {
selectedParticipant = participant
}
} else {
null
}
)
}

View File

@@ -14,11 +14,17 @@ import androidx.compose.foundation.pager.VerticalPager
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.movableContentOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInRoot
import org.signal.core.ui.compose.AllNightPreviews
import org.signal.core.ui.compose.Previews
import org.thoughtcrime.securesms.conversation.colors.ChatColorsPalette
@@ -33,7 +39,7 @@ fun CallParticipantsPager(
pagerState: PagerState,
modifier: Modifier = Modifier,
onTap: (() -> Unit)? = null,
onParticipantLongPress: ((CallParticipant) -> Unit)? = null
onParticipantLongPress: ((CallParticipant, Offset) -> Unit)? = null
) {
if (callParticipantsPagerState.focusedParticipant == null) {
return
@@ -57,12 +63,15 @@ fun CallParticipantsPager(
itemKey = { it.callParticipantId }
) { participant, itemModifier ->
val longPressModifier = if (!participant.recipient.isSelf && currentOnLongPress.value != null) {
itemModifier.pointerInput(participant.callParticipantId) {
detectTapGestures(
onTap = { currentOnTap.value?.invoke() },
onLongPress = { currentOnLongPress.value?.invoke(participant) }
)
}
var itemWindowOrigin by remember(participant.callParticipantId) { mutableStateOf(Offset.Zero) }
itemModifier
.onGloballyPositioned { coords -> itemWindowOrigin = coords.positionInRoot() }
.pointerInput(participant.callParticipantId) {
detectTapGestures(
onTap = { currentOnTap.value?.invoke() },
onLongPress = { local -> currentOnLongPress.value?.invoke(participant, itemWindowOrigin + local) }
)
}
} else {
itemModifier
}

View File

@@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
@@ -47,6 +48,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onSizeChanged
@@ -55,6 +57,7 @@ import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -127,7 +130,6 @@ fun CallScreen(
onWifiToCellularPopupDismissed: () -> Unit = {},
onSwipeToSpeakerHintDismissed: () -> Unit = {},
onRemoteMuteToastDismissed: () -> Unit = {},
isInternalUser: Boolean = false,
isSelfAdmin: Boolean = false,
isCallLink: Boolean = false,
onMuteAudio: (CallParticipant) -> Unit = {},
@@ -329,13 +331,20 @@ fun CallScreen(
}
} else if (webRtcCallState.isPassedPreJoin) {
var longPressedParticipantId by remember { mutableStateOf<CallParticipantId?>(null) }
var longPressWindowOffset by remember { mutableStateOf(Offset.Zero) }
var anchorWindowOrigin by remember { mutableStateOf(Offset.Zero) }
val longPressedParticipant = longPressedParticipantId?.let { id ->
callParticipantsPagerState.callParticipants.find { it.callParticipantId == id }
}
val density = LocalDensity.current
val contextMenuAnchorOffset = remember(longPressWindowOffset, anchorWindowOrigin, density) {
val local = longPressWindowOffset - anchorWindowOrigin
with(density) { IntOffset(local.x.toInt(), local.y.toInt()) }
}
CallElementsLayout(
callGridSlot = {
Box {
Box(modifier = Modifier.onGloballyPositioned { anchorWindowOrigin = it.positionInRoot() }) {
CallParticipantsPager(
callParticipantsPagerState = callParticipantsPagerState,
pagerState = callScreenController.callParticipantsVerticalPagerState,
@@ -356,24 +365,25 @@ fun CallScreen(
}
}
},
onParticipantLongPress = if (isInternalUser) {
{ participant -> longPressedParticipantId = participant.callParticipantId }
} else {
null
onParticipantLongPress = { participant, windowOffset ->
longPressedParticipantId = participant.callParticipantId
longPressWindowOffset = windowOffset
}
)
ParticipantContextMenu(
participant = longPressedParticipant,
isSelfAdmin = isSelfAdmin,
isCallLink = isCallLink,
onDismiss = { longPressedParticipantId = null },
onMuteAudio = onMuteAudio,
onRemoveFromCall = onRemoveFromCall,
onContactDetails = onContactDetails,
onViewSafetyNumber = onViewSafetyNumber,
onGoToChat = onGoToChat
)
Box(modifier = Modifier.offset { contextMenuAnchorOffset }) {
ParticipantContextMenu(
participant = longPressedParticipant,
isSelfAdmin = isSelfAdmin,
isCallLink = isCallLink,
onDismiss = { longPressedParticipantId = null },
onMuteAudio = onMuteAudio,
onRemoveFromCall = onRemoveFromCall,
onContactDetails = onContactDetails,
onViewSafetyNumber = onViewSafetyNumber,
onGoToChat = onGoToChat
)
}
}
},
pictureInPictureSlot = {

View File

@@ -49,7 +49,6 @@ import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDial
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.service.webrtc.links.UpdateCallLinkResult
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcEphemeralState
import org.thoughtcrime.securesms.util.RemoteConfig
import org.thoughtcrime.securesms.util.WindowUtil
import org.thoughtcrime.securesms.webrtc.CallParticipantsViewState
import kotlin.time.Duration.Companion.seconds
@@ -221,7 +220,6 @@ class ComposeCallScreenMediator(private val activity: WebRtcCallActivity, viewMo
onSwipeToSpeakerHintDismissed = { callScreenViewModel.callScreenState.update { it.copy(displaySwipeToSpeakerHint = false) } },
onRemoteMuteToastDismissed = { callScreenViewModel.callScreenState.update { it.copy(remoteMuteToastMessage = null) } },
callParticipantUpdatePopupController = callParticipantUpdatePopupController,
isInternalUser = RemoteConfig.internalUser,
isSelfAdmin = controlAndInfoState.isSelfAdmin(),
isCallLink = controlAndInfoState.callLink != null,
onMuteAudio = callInfoCallbacks::onMuteAudio,