diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/CallInfoView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/CallInfoView.kt index 02db8eef5b..7e18d60095 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/CallInfoView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/CallInfoView.kt @@ -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(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 } ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallParticipantsPager.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallParticipantsPager.kt index 61ea695014..05d39cc1e5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallParticipantsPager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallParticipantsPager.kt @@ -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 } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallScreen.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallScreen.kt index da81552958..7706a46834 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallScreen.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/CallScreen.kt @@ -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(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 = { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/ComposeCallScreenMediator.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/ComposeCallScreenMediator.kt index baec5b4d4e..98d74a3c5d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/ComposeCallScreenMediator.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/ComposeCallScreenMediator.kt @@ -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,