diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantsState.kt index e807d4a8f41..326145ae373 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantsState.kt @@ -48,7 +48,7 @@ data class CallParticipantsState( val allRemoteParticipants: List = remoteParticipants.allParticipants val isFolded: Boolean = foldableState.isFolded - val isLargeVideoGroup: Boolean = allRemoteParticipants.size > SMALL_GROUP_MAX + val isLargeVideoGroup: Boolean = allRemoteParticipants.size > SMALL_GROUP_MAX && !isInPipMode && !isFolded val isIncomingRing: Boolean = callState == WebRtcViewModel.State.CALL_INCOMING val raisedHands: List diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallView.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallView.java index 335f51f8afb..ff54db5339d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallView.java @@ -113,6 +113,7 @@ public class WebRtcCallView extends InsetAwareConstraintLayout { private View errorButton; private boolean controlsVisible = true; private Guideline showParticipantsGuideline; + private Guideline aboveControlsGuideline; private Guideline topFoldGuideline; private Guideline callScreenTopFoldGuideline; private AvatarImageView largeHeaderAvatar; @@ -193,6 +194,7 @@ protected void onFinishInflate() { groupCallSpeakerHint = new Stub<>(findViewById(R.id.call_screen_group_call_speaker_hint)); groupCallFullStub = new Stub<>(findViewById(R.id.group_call_call_full_view)); showParticipantsGuideline = findViewById(R.id.call_screen_show_participants_guideline); + aboveControlsGuideline = findViewById(R.id.call_screen_above_controls_guideline); topFoldGuideline = findViewById(R.id.fold_top_guideline); callScreenTopFoldGuideline = findViewById(R.id.fold_top_call_screen_guideline); largeHeaderAvatar = findViewById(R.id.call_screen_header_avatar); @@ -462,9 +464,11 @@ public void updateCallParticipants(@NonNull CallParticipantsViewState callPartic updateLocalCallParticipant(state.getLocalRenderState(), state.getLocalParticipant(), displaySmallSelfPipInLandscape); - if (state.isLargeVideoGroup() && !state.isInPipMode() && !state.isFolded()) { + if (state.isLargeVideoGroup()) { + moveSnackbarAboveParticipantRail(true); adjustLayoutForLargeCount(); } else { + moveSnackbarAboveParticipantRail(state.isViewingFocusedParticipant()); adjustLayoutForSmallCount(); } } @@ -836,11 +840,26 @@ private void adjustLayoutPositions(@NonNull LayoutPositions layoutPositions) { ConstraintSet.TOP, ViewUtil.dpToPx(layoutPositions.reactionBottomMargin)); + constraintSet.applyTo(this); + } + + private void moveSnackbarAboveParticipantRail(boolean aboveRail) { + if (aboveRail) { + updateSnackbarBottomConstraint(callParticipantsRecycler); + } else { + updateSnackbarBottomConstraint(aboveControlsGuideline); + } + } + + private void updateSnackbarBottomConstraint(View anchor) { + ConstraintSet constraintSet = new ConstraintSet(); + constraintSet.clone(this); + constraintSet.connect(R.id.call_screen_raise_hand_view, ConstraintSet.BOTTOM, - layoutPositions.reactionBottomViewId, + anchor.getId(), ConstraintSet.TOP, - ViewUtil.dpToPx(layoutPositions.reactionBottomMargin)); + ViewUtil.dpToPx(8)); constraintSet.applyTo(this); } @@ -913,8 +932,15 @@ public void enableRingGroup(boolean enabled) { ringToggle.setActivated(enabled); } - public void onControlTopChanged(int top) { - pictureInPictureGestureHelper.setBottomVerticalBoundary(top); + public void onControlTopChanged(int guidelineTop, int snackBarHeight) { + int offset = 0; + if (lastState != null) { + CallParticipantsState state = lastState.getCallParticipantsState(); + if (!state.isViewingFocusedParticipant() && !state.isLargeVideoGroup()) { + offset = snackBarHeight; + } + pictureInPictureGestureHelper.setBottomVerticalBoundary(guidelineTop - offset); + } } public interface ControlsListener { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcControls.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcControls.java index 38a48c4b847..724536de66b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcControls.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcControls.java @@ -173,7 +173,7 @@ public boolean displayAudioToggle() { } public boolean displayCameraToggle() { - return (isPreJoin() || (isAtLeastOutgoing() && !hasAtLeastOneRemote)) && isLocalVideoEnabled && isMoreThanOneCameraAvailable; + return (isPreJoin() || (isAtLeastOutgoing() && !hasAtLeastOneRemote)) && isLocalVideoEnabled && isMoreThanOneCameraAvailable && !isInPipMode; } public boolean displayRemoteVideoRecycler() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/ControlsAndInfoController.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/ControlsAndInfoController.kt index 6ee67f83e48..3dd4c62810e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/ControlsAndInfoController.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/ControlsAndInfoController.kt @@ -125,12 +125,11 @@ class ControlsAndInfoController( BottomSheetBehaviorHack.setNestedScrollingChild(behavior, callInfoComposeView) coordinator.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> - val guidelineTop = max(frame.top, coordinator.height - behavior.peekHeight) - webRtcCallView.post { onControlTopChanged(guidelineTop) } + webRtcCallView.post { onControlTopChanged() } } raiseHandComposeView.addOnLayoutChangeListener { _, _, top, _, bottom, _, _, _, _ -> - onControlTopChanged(guidelineTop = aboveControlsGuideline.top, composeViewSize = bottom - top) + onControlTopChanged(composeViewSize = bottom - top) } callControls.viewTreeObserver.addOnGlobalLayoutListener { @@ -141,8 +140,7 @@ class ControlsAndInfoController( frame.minimumHeight = coordinator.height / 2 behavior.maxHeight = (coordinator.height.toFloat() * 0.66f).toInt() - val guidelineTop = max(frame.top, coordinator.height - behavior.peekHeight) - webRtcCallView.post { onControlTopChanged(guidelineTop) } + webRtcCallView.post { onControlTopChanged() } } } @@ -168,7 +166,7 @@ class ControlsAndInfoController( callInfoComposeView.alpha = alphaCallInfo(slideOffset) callInfoComposeView.translationY = infoTranslationDistance - (infoTranslationDistance * callInfoComposeView.alpha) - onControlTopChanged(max(frame.top, coordinator.height - behavior.peekHeight)) + onControlTopChanged() } }) @@ -185,9 +183,10 @@ class ControlsAndInfoController( } } - fun onControlTopChanged(guidelineTop: Int, composeViewSize: Int = raiseHandComposeView.height) { + fun onControlTopChanged(composeViewSize: Int = raiseHandComposeView.height) { + val guidelineTop = max(frame.top, coordinator.height - behavior.peekHeight) aboveControlsGuideline.setGuidelineBegin(guidelineTop) - webRtcCallView.onControlTopChanged(guidelineTop - composeViewSize) + webRtcCallView.onControlTopChanged(guidelineTop, composeViewSize) } fun addVisibilityListener(listener: BottomSheetVisibilityListener): Boolean { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/RaiseHandSnackbar.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/RaiseHandSnackbar.kt index b2b85ff2e12..cee9975ca0f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/RaiseHandSnackbar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/RaiseHandSnackbar.kt @@ -16,9 +16,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.shape.RoundedCornerShape @@ -67,31 +65,31 @@ object RaiseHandSnackbar { @Composable fun View(webRtcCallViewModel: WebRtcCallViewModel, showCallInfoListener: () -> Unit, modifier: Modifier = Modifier) { - var isExpanded by remember { mutableStateOf(ExpansionState(isExpanded = false, forced = false)) } + var expansionState by remember { mutableStateOf(ExpansionState(shouldExpand = false, forced = false)) } val webRtcState by webRtcCallViewModel.callParticipantsState .toFlowable(BackpressureStrategy.LATEST) .map { state -> val raisedHands = state.raisedHands.sortedByDescending { it.timestamp } val shouldExpand = RaiseHandState.shouldExpand(raisedHands) - if (!isExpanded.forced) { - isExpanded = ExpansionState(shouldExpand, false) + if (!expansionState.forced) { + expansionState = ExpansionState(shouldExpand, false) } raisedHands }.subscribeAsState(initial = emptyList()) val state by remember { derivedStateOf { - RaiseHandState(raisedHands = webRtcState, expansionState = isExpanded) + RaiseHandState(raisedHands = webRtcState, expansionState = expansionState) } } - LaunchedEffect(isExpanded) { + LaunchedEffect(expansionState) { delay(COLLAPSE_DELAY_MS) - isExpanded = ExpansionState(isExpanded = false, forced = false) + expansionState = ExpansionState(shouldExpand = false, forced = false) } - RaiseHand(state, modifier, { isExpanded = ExpansionState(isExpanded = true, forced = true) }, showCallInfoListener = showCallInfoListener) + RaiseHand(state, modifier, { expansionState = ExpansionState(shouldExpand = true, forced = true) }, showCallInfoListener = showCallInfoListener) } } @@ -123,20 +121,19 @@ private fun RaiseHand( .padding(horizontal = 16.dp) .clip(shape = RoundedCornerShape(16.dp, 16.dp, 16.dp, 16.dp)) .background(MaterialTheme.colorScheme.surface) - .height(48.dp) .animateContentSize() ) { val boxModifier = modifier .padding(horizontal = 16.dp) .clickable( - !state.expansionState.isExpanded, + !state.isExpanded, stringResource(id = R.string.CallOverflowPopupWindow__expand_snackbar_accessibility_label), Role.Button ) { setExpanded(true) } Box( contentAlignment = Alignment.CenterStart, - modifier = if (state.expansionState.isExpanded) { + modifier = if (state.isExpanded) { boxModifier.fillMaxWidth() } else { boxModifier.wrapContentWidth() @@ -146,27 +143,35 @@ private fun RaiseHand( Icon( imageVector = ImageVector.vectorResource(id = R.drawable.symbol_raise_hand_24), contentDescription = null, - modifier = Modifier.align(Alignment.CenterVertically) + modifier = Modifier.align(Alignment.CenterVertically).padding(vertical = 8.dp) ) Text( text = getSnackbarText(state), color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier.padding(start = 16.dp) + modifier = Modifier + .padding(start = 16.dp) + .weight(1f, fill = state.isExpanded) + .wrapContentWidth(Alignment.Start) + .padding(vertical = 16.dp) ) - if (state.expansionState.isExpanded && state.raisedHands.isNotEmpty()) { - Spacer(modifier = Modifier.weight(1f)) - + if (state.isExpanded) { if (state.raisedHands.first().sender.isSelf) { val context = LocalContext.current - TextButton(onClick = { - showLowerHandDialog(context) - }) { - Text(text = stringResource(id = R.string.CallOverflowPopupWindow__lower_hand)) + TextButton( + onClick = { + showLowerHandDialog(context) + }, + modifier = Modifier.wrapContentWidth(Alignment.End) + ) { + Text(text = stringResource(id = R.string.CallOverflowPopupWindow__lower_hand), maxLines = 1) } } else { - TextButton(onClick = showCallInfoListener) { - Text(text = stringResource(id = R.string.CallOverflowPopupWindow__view)) + TextButton( + onClick = showCallInfoListener, + modifier = Modifier.wrapContentWidth(Alignment.End) + ) { + Text(text = stringResource(id = R.string.CallOverflowPopupWindow__view), maxLines = 1) } } } @@ -189,10 +194,10 @@ private fun showLowerHandDialog(context: Context) { @Composable private fun getSnackbarText(state: RaiseHandState): String { - if (state.isEmpty()) { + if (state.isEmpty) { return "" } - return if (!state.expansionState.isExpanded) { + return if (!state.isExpanded) { pluralStringResource(id = R.plurals.CallRaiseHandSnackbar_raised_hands, count = state.raisedHands.size, getShortDisplayName(state.raisedHands), state.raisedHands.size - 1) } else { if (state.raisedHands.size == 1 && state.raisedHands.first().sender.isSelf) { @@ -215,12 +220,11 @@ private fun getShortDisplayName(raisedHands: List): Str private data class RaiseHandState( val raisedHands: List = emptyList(), - val expansionState: ExpansionState = ExpansionState(isExpanded = false, forced = false) + val expansionState: ExpansionState = ExpansionState(shouldExpand = false, forced = false) ) { + val isExpanded = expansionState.shouldExpand && raisedHands.isNotEmpty() - fun isEmpty(): Boolean { - return raisedHands.isEmpty() - } + val isEmpty = raisedHands.isEmpty() companion object { @JvmStatic @@ -232,6 +236,6 @@ private data class RaiseHandState( } private data class ExpansionState( - val isExpanded: Boolean, + val shouldExpand: Boolean, val forced: Boolean ) diff --git a/app/src/main/res/layout/webrtc_call_view.xml b/app/src/main/res/layout/webrtc_call_view.xml index 3001913e0e9..74bbdaaf740 100644 --- a/app/src/main/res/layout/webrtc_call_view.xml +++ b/app/src/main/res/layout/webrtc_call_view.xml @@ -21,7 +21,6 @@ android:orientation="horizontal" tools:layout_constraintGuide_end="200dp" /> -