mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-24 10:51:27 +01:00
Implement initial support for foldables in calling.
This commit is contained in:
committed by
Greyson Parrelli
parent
927b6096c6
commit
5229e24397
@@ -20,6 +20,8 @@ import java.util.WeakHashMap;
|
||||
*/
|
||||
public class BroadcastVideoSink implements VideoSink {
|
||||
|
||||
public static final int DEVICE_ROTATION_IGNORE = -1;
|
||||
|
||||
private final EglBase eglBase;
|
||||
private final WeakHashMap<VideoSink, Boolean> sinks;
|
||||
private final WeakHashMap<Object, Point> requestingSizes;
|
||||
@@ -85,7 +87,10 @@ public class BroadcastVideoSink implements VideoSink {
|
||||
|
||||
@Override
|
||||
public synchronized void onFrame(@NonNull VideoFrame videoFrame) {
|
||||
if (videoFrame.getRotatedHeight() < videoFrame.getRotatedWidth() || forceRotate) {
|
||||
boolean isDeviceRotationIgnored = deviceOrientationDegrees == DEVICE_ROTATION_IGNORE;
|
||||
boolean isWideVideoFrame = videoFrame.getRotatedHeight() < videoFrame.getRotatedWidth();
|
||||
|
||||
if (!isDeviceRotationIgnored && (isWideVideoFrame || forceRotate)) {
|
||||
int rotation = calculateRotation();
|
||||
if (rotation > 0) {
|
||||
rotation += rotateWithDevice ? videoFrame.getRotation() : 0;
|
||||
|
||||
@@ -34,6 +34,7 @@ public class CallParticipantsLayout extends FlexboxLayout {
|
||||
private CallParticipant focusedParticipant = null;
|
||||
private boolean shouldRenderInPip;
|
||||
private boolean isPortrait;
|
||||
private LayoutStrategy layoutStrategy;
|
||||
|
||||
public CallParticipantsLayout(@NonNull Context context) {
|
||||
super(context);
|
||||
@@ -47,11 +48,18 @@ public class CallParticipantsLayout extends FlexboxLayout {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
void update(@NonNull List<CallParticipant> callParticipants, @NonNull CallParticipant focusedParticipant, boolean shouldRenderInPip, boolean isPortrait) {
|
||||
void update(@NonNull List<CallParticipant> callParticipants,
|
||||
@NonNull CallParticipant focusedParticipant,
|
||||
boolean shouldRenderInPip,
|
||||
boolean isPortrait,
|
||||
@NonNull LayoutStrategy layoutStrategy)
|
||||
{
|
||||
this.callParticipants = callParticipants;
|
||||
this.focusedParticipant = focusedParticipant;
|
||||
this.shouldRenderInPip = shouldRenderInPip;
|
||||
this.isPortrait = isPortrait;
|
||||
this.layoutStrategy = layoutStrategy;
|
||||
setFlexDirection(layoutStrategy.getFlexDirection());
|
||||
updateLayout();
|
||||
}
|
||||
|
||||
@@ -127,7 +135,7 @@ public class CallParticipantsLayout extends FlexboxLayout {
|
||||
callParticipantView.useLargeAvatar();
|
||||
}
|
||||
|
||||
setChildLayoutParams(view, index, getChildCount());
|
||||
layoutStrategy.setChildLayoutParams(view, index, getChildCount());
|
||||
}
|
||||
|
||||
private void addCallParticipantView() {
|
||||
@@ -139,17 +147,9 @@ public class CallParticipantsLayout extends FlexboxLayout {
|
||||
addView(view);
|
||||
}
|
||||
|
||||
private void setChildLayoutParams(@NonNull View child, int childPosition, int childCount) {
|
||||
FlexboxLayout.LayoutParams params = (FlexboxLayout.LayoutParams) child.getLayoutParams();
|
||||
if (childCount < 3) {
|
||||
params.setFlexBasisPercent(1f);
|
||||
} else {
|
||||
if ((childCount % 2) != 0 && childPosition == childCount - 1) {
|
||||
params.setFlexBasisPercent(1f);
|
||||
} else {
|
||||
params.setFlexBasisPercent(0.5f);
|
||||
}
|
||||
}
|
||||
child.setLayoutParams(params);
|
||||
public interface LayoutStrategy {
|
||||
int getFlexDirection();
|
||||
|
||||
void setChildLayoutParams(@NonNull View child, int childPosition, int childCount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.thoughtcrime.securesms.components.webrtc
|
||||
|
||||
import android.view.View
|
||||
import com.google.android.flexbox.FlexDirection
|
||||
import com.google.android.flexbox.FlexboxLayout
|
||||
|
||||
object CallParticipantsLayoutStrategies {
|
||||
|
||||
private object Portrait : CallParticipantsLayout.LayoutStrategy {
|
||||
override fun getFlexDirection(): Int = FlexDirection.ROW
|
||||
|
||||
override fun setChildLayoutParams(child: View, childPosition: Int, childCount: Int) {
|
||||
val params = child.layoutParams as FlexboxLayout.LayoutParams
|
||||
if (childCount < 3) {
|
||||
params.flexBasisPercent = 1f
|
||||
} else {
|
||||
if ((childCount % 2) != 0 && childPosition == childCount - 1) {
|
||||
params.flexBasisPercent = 1f
|
||||
} else {
|
||||
params.flexBasisPercent = 0.5f
|
||||
}
|
||||
}
|
||||
child.layoutParams = params
|
||||
}
|
||||
}
|
||||
|
||||
private object Landscape : CallParticipantsLayout.LayoutStrategy {
|
||||
override fun getFlexDirection() = FlexDirection.COLUMN
|
||||
|
||||
override fun setChildLayoutParams(child: View, childPosition: Int, childCount: Int) {
|
||||
val params = child.layoutParams as FlexboxLayout.LayoutParams
|
||||
if (childCount < 4) {
|
||||
params.flexBasisPercent = 1f
|
||||
} else {
|
||||
if ((childCount % 2) != 0 && childPosition == childCount - 1) {
|
||||
params.flexBasisPercent = 1f
|
||||
} else {
|
||||
params.flexBasisPercent = 0.5f
|
||||
}
|
||||
}
|
||||
child.layoutParams = params
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getStrategy(isPortrait: Boolean): CallParticipantsLayout.LayoutStrategy {
|
||||
return if (isPortrait) {
|
||||
Portrait
|
||||
} else {
|
||||
Landscape
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,8 @@ public final class CallParticipantsState {
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
OptionalLong.empty());
|
||||
OptionalLong.empty(),
|
||||
WebRtcControls.FoldableState.flat());
|
||||
|
||||
private final WebRtcViewModel.State callState;
|
||||
private final WebRtcViewModel.GroupCallState groupCallState;
|
||||
@@ -48,6 +49,7 @@ public final class CallParticipantsState {
|
||||
private final boolean showVideoForOutgoing;
|
||||
private final boolean isViewingFocusedParticipant;
|
||||
private final OptionalLong remoteDevicesCount;
|
||||
private final WebRtcControls.FoldableState foldableState;
|
||||
|
||||
public CallParticipantsState(@NonNull WebRtcViewModel.State callState,
|
||||
@NonNull WebRtcViewModel.GroupCallState groupCallState,
|
||||
@@ -58,7 +60,8 @@ public final class CallParticipantsState {
|
||||
boolean isInPipMode,
|
||||
boolean showVideoForOutgoing,
|
||||
boolean isViewingFocusedParticipant,
|
||||
OptionalLong remoteDevicesCount)
|
||||
OptionalLong remoteDevicesCount,
|
||||
@NonNull WebRtcControls.FoldableState foldableState)
|
||||
{
|
||||
this.callState = callState;
|
||||
this.groupCallState = groupCallState;
|
||||
@@ -70,6 +73,7 @@ public final class CallParticipantsState {
|
||||
this.showVideoForOutgoing = showVideoForOutgoing;
|
||||
this.isViewingFocusedParticipant = isViewingFocusedParticipant;
|
||||
this.remoteDevicesCount = remoteDevicesCount;
|
||||
this.foldableState = foldableState;
|
||||
}
|
||||
|
||||
public @NonNull WebRtcViewModel.State getCallState() {
|
||||
@@ -94,7 +98,10 @@ public final class CallParticipantsState {
|
||||
listParticipants.addAll(remoteParticipants.getListParticipants());
|
||||
}
|
||||
|
||||
listParticipants.add(CallParticipant.EMPTY);
|
||||
if (foldableState.isFlat()) {
|
||||
listParticipants.add(CallParticipant.EMPTY);
|
||||
}
|
||||
|
||||
Collections.reverse(listParticipants);
|
||||
|
||||
return listParticipants;
|
||||
@@ -155,6 +162,10 @@ public final class CallParticipantsState {
|
||||
return localRenderState;
|
||||
}
|
||||
|
||||
public boolean isFolded() {
|
||||
return foldableState.isFolded();
|
||||
}
|
||||
|
||||
public boolean isLargeVideoGroup() {
|
||||
return getAllRemoteParticipants().size() > SMALL_GROUP_MAX;
|
||||
}
|
||||
@@ -215,7 +226,8 @@ public final class CallParticipantsState {
|
||||
oldState.isInPipMode,
|
||||
newShowVideoForOutgoing,
|
||||
oldState.isViewingFocusedParticipant,
|
||||
webRtcViewModel.getRemoteDevicesCount());
|
||||
webRtcViewModel.getRemoteDevicesCount(),
|
||||
oldState.foldableState);
|
||||
}
|
||||
|
||||
public static @NonNull CallParticipantsState update(@NonNull CallParticipantsState oldState, boolean isInPip) {
|
||||
@@ -237,7 +249,8 @@ public final class CallParticipantsState {
|
||||
isInPip,
|
||||
oldState.showVideoForOutgoing,
|
||||
oldState.isViewingFocusedParticipant,
|
||||
oldState.remoteDevicesCount);
|
||||
oldState.remoteDevicesCount,
|
||||
oldState.foldableState);
|
||||
}
|
||||
|
||||
public static @NonNull CallParticipantsState setExpanded(@NonNull CallParticipantsState oldState, boolean expanded) {
|
||||
@@ -259,7 +272,8 @@ public final class CallParticipantsState {
|
||||
oldState.isInPipMode,
|
||||
oldState.showVideoForOutgoing,
|
||||
oldState.isViewingFocusedParticipant,
|
||||
oldState.remoteDevicesCount);
|
||||
oldState.remoteDevicesCount,
|
||||
oldState.foldableState);
|
||||
}
|
||||
|
||||
public static @NonNull CallParticipantsState update(@NonNull CallParticipantsState oldState, @NonNull SelectedPage selectedPage) {
|
||||
@@ -281,7 +295,31 @@ public final class CallParticipantsState {
|
||||
oldState.isInPipMode,
|
||||
oldState.showVideoForOutgoing,
|
||||
selectedPage == SelectedPage.FOCUSED,
|
||||
oldState.remoteDevicesCount);
|
||||
oldState.remoteDevicesCount,
|
||||
oldState.foldableState);
|
||||
}
|
||||
|
||||
public static @NonNull CallParticipantsState update(@NonNull CallParticipantsState oldState, @NonNull WebRtcControls.FoldableState foldableState) {
|
||||
WebRtcLocalRenderState localRenderState = determineLocalRenderMode(oldState.localParticipant,
|
||||
oldState.isInPipMode,
|
||||
oldState.showVideoForOutgoing,
|
||||
oldState.getGroupCallState().isNotIdle(),
|
||||
oldState.callState,
|
||||
oldState.getAllRemoteParticipants().size(),
|
||||
oldState.isViewingFocusedParticipant,
|
||||
oldState.getLocalRenderState() == WebRtcLocalRenderState.EXPANDED);
|
||||
|
||||
return new CallParticipantsState(oldState.callState,
|
||||
oldState.groupCallState,
|
||||
oldState.remoteParticipants,
|
||||
oldState.localParticipant,
|
||||
oldState.focusedParticipant,
|
||||
localRenderState,
|
||||
oldState.isInPipMode,
|
||||
oldState.showVideoForOutgoing,
|
||||
oldState.isViewingFocusedParticipant,
|
||||
oldState.remoteDevicesCount,
|
||||
foldableState);
|
||||
}
|
||||
|
||||
private static @NonNull WebRtcLocalRenderState determineLocalRenderMode(@NonNull CallParticipant localParticipant,
|
||||
|
||||
@@ -116,14 +116,16 @@ public class PictureInPictureGestureHelper extends GestureDetector.SimpleOnGestu
|
||||
}
|
||||
|
||||
public void clearVerticalBoundaries() {
|
||||
setVerticalBoundaries(parent.getTop(), parent.getMeasuredHeight() + parent.getTop());
|
||||
setTopVerticalBoundary(parent.getTop());
|
||||
setBottomVerticalBoundary(parent.getMeasuredHeight() + parent.getTop());
|
||||
}
|
||||
|
||||
public void setVerticalBoundaries(int topBoundary, int bottomBoundary) {
|
||||
extraPaddingTop = topBoundary - parent.getTop();
|
||||
extraPaddingBottom = parent.getMeasuredHeight() + parent.getTop() - bottomBoundary;
|
||||
public void setTopVerticalBoundary(int topBoundary) {
|
||||
extraPaddingTop = topBoundary - parent.getTop();
|
||||
}
|
||||
|
||||
adjustPip();
|
||||
public void setBottomVerticalBoundary(int bottomBoundary) {
|
||||
extraPaddingBottom = parent.getMeasuredHeight() + parent.getTop() - bottomBoundary;
|
||||
}
|
||||
|
||||
private boolean onGestureFinished(MotionEvent e) {
|
||||
|
||||
@@ -64,6 +64,10 @@ class WebRtcCallParticipantsPage {
|
||||
return isPortrait;
|
||||
}
|
||||
|
||||
public @NonNull CallParticipantsLayout.LayoutStrategy getLayoutStrategy() {
|
||||
return CallParticipantsLayoutStrategies.getStrategy(isPortrait);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
||||
@@ -86,7 +86,7 @@ class WebRtcCallParticipantsPagerAdapter extends ListAdapter<WebRtcCallParticipa
|
||||
|
||||
@Override
|
||||
void bind(WebRtcCallParticipantsPage page) {
|
||||
callParticipantsLayout.update(page.getCallParticipants(), page.getFocusedParticipant(), page.isRenderInPip(), page.isPortrait());
|
||||
callParticipantsLayout.update(page.getCallParticipants(), page.getFocusedParticipant(), page.isRenderInPip(), page.isPortrait(), page.getLayoutStrategy());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ import com.google.android.material.button.MaterialButton;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.animation.ResizeAnimation;
|
||||
import org.thoughtcrime.securesms.components.AccessibleToggleButton;
|
||||
import org.thoughtcrime.securesms.components.sensors.Orientation;
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
|
||||
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
@@ -46,6 +45,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.ringrtc.CameraState;
|
||||
import org.thoughtcrime.securesms.util.BlurTransformation;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.SetUtil;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.views.Stub;
|
||||
@@ -57,9 +57,7 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.thoughtcrime.securesms.components.sensors.Orientation.PORTRAIT_BOTTOM_EDGE;
|
||||
|
||||
public class WebRtcCallView extends FrameLayout {
|
||||
public class WebRtcCallView extends ConstraintLayout {
|
||||
|
||||
private static final long TRANSITION_DURATION_MILLIS = 250;
|
||||
private static final int SMALL_ONGOING_CALL_BUTTON_MARGIN_DP = 8;
|
||||
@@ -102,12 +100,15 @@ public class WebRtcCallView extends FrameLayout {
|
||||
private View errorButton;
|
||||
private int pagerBottomMarginDp;
|
||||
private boolean controlsVisible = true;
|
||||
private Guideline topFoldGuideline;
|
||||
private Guideline callScreenTopFoldGuideline;
|
||||
private View foldParticipantCountWrapper;
|
||||
private TextView foldParticipantCount;
|
||||
|
||||
private WebRtcCallParticipantsPagerAdapter pagerAdapter;
|
||||
private WebRtcCallParticipantsRecyclerAdapter recyclerAdapter;
|
||||
private PictureInPictureExpansionHelper pictureInPictureExpansionHelper;
|
||||
|
||||
|
||||
private final Set<View> incomingCallViews = new HashSet<>();
|
||||
private final Set<View> topViews = new HashSet<>();
|
||||
private final Set<View> visibleViewSet = new HashSet<>();
|
||||
@@ -161,6 +162,10 @@ public class WebRtcCallView extends FrameLayout {
|
||||
errorButton = findViewById(R.id.call_screen_error_cancel);
|
||||
groupCallSpeakerHint = new Stub<>(findViewById(R.id.call_screen_group_call_speaker_hint));
|
||||
groupCallFullStub = new Stub<>(findViewById(R.id.group_call_call_full_view));
|
||||
topFoldGuideline = findViewById(R.id.fold_top_guideline);
|
||||
callScreenTopFoldGuideline = findViewById(R.id.fold_top_call_screen_guideline);
|
||||
foldParticipantCountWrapper = findViewById(R.id.fold_show_participants_menu_counter_wrapper);
|
||||
foldParticipantCount = findViewById(R.id.fold_show_participants_menu_counter);
|
||||
|
||||
View topGradient = findViewById(R.id.call_screen_header_gradient);
|
||||
View decline = findViewById(R.id.call_screen_decline_call);
|
||||
@@ -279,10 +284,18 @@ public class WebRtcCallView extends FrameLayout {
|
||||
@Override
|
||||
public void onWindowSystemUiVisibilityChanged(int visible) {
|
||||
if ((visible & SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
|
||||
pictureInPictureGestureHelper.setVerticalBoundaries(toolbar.getBottom(), videoToggle.getTop());
|
||||
if (controls.adjustForFold()) {
|
||||
pictureInPictureGestureHelper.clearVerticalBoundaries();
|
||||
pictureInPictureGestureHelper.setTopVerticalBoundary(toolbar.getBottom());
|
||||
} else {
|
||||
pictureInPictureGestureHelper.setTopVerticalBoundary(toolbar.getBottom());
|
||||
pictureInPictureGestureHelper.setBottomVerticalBoundary(videoToggle.getTop());
|
||||
}
|
||||
} else {
|
||||
pictureInPictureGestureHelper.clearVerticalBoundaries();
|
||||
}
|
||||
|
||||
pictureInPictureGestureHelper.adjustPip();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -323,16 +336,22 @@ public class WebRtcCallView extends FrameLayout {
|
||||
}
|
||||
|
||||
if (state.getGroupCallState().isNotIdle() && participantCount != null) {
|
||||
participantCount.setText(state.getParticipantCount()
|
||||
.mapToObj(String::valueOf).orElse("\u2014"));
|
||||
participantCount.setEnabled(state.getParticipantCount().isPresent());
|
||||
String text = state.getParticipantCount()
|
||||
.mapToObj(String::valueOf).orElse("\u2014");
|
||||
boolean enabled = state.getParticipantCount().isPresent();
|
||||
|
||||
participantCount.setText(text);
|
||||
participantCount.setEnabled(enabled);
|
||||
|
||||
foldParticipantCount.setText(text);
|
||||
foldParticipantCount.setEnabled(enabled);
|
||||
}
|
||||
|
||||
pagerAdapter.submitList(pages);
|
||||
recyclerAdapter.submitList(state.getListParticipants());
|
||||
updateLocalCallParticipant(state.getLocalRenderState(), state.getLocalParticipant(), state.getFocusedParticipant());
|
||||
|
||||
if (state.isLargeVideoGroup() && !state.isInPipMode()) {
|
||||
if (state.isLargeVideoGroup() && !state.isInPipMode() && !state.isFolded()) {
|
||||
layoutParticipantsForLargeCount();
|
||||
} else {
|
||||
layoutParticipantsForSmallCount();
|
||||
@@ -423,7 +442,9 @@ public class WebRtcCallView extends FrameLayout {
|
||||
toolbar.inflateMenu(R.menu.group_call);
|
||||
|
||||
View showParticipants = toolbar.getMenu().findItem(R.id.menu_group_call_participants_list).getActionView();
|
||||
showParticipants.setAlpha(0);
|
||||
showParticipants.setOnClickListener(unused -> showParticipantsList());
|
||||
foldParticipantCountWrapper.setOnClickListener(unused -> showParticipantsList());
|
||||
|
||||
participantCount = showParticipants.findViewById(R.id.show_participants_menu_counter);
|
||||
}
|
||||
@@ -480,6 +501,20 @@ public class WebRtcCallView extends FrameLayout {
|
||||
|
||||
visibleViewSet.clear();
|
||||
|
||||
if (webRtcControls.adjustForFold()) {
|
||||
topFoldGuideline.setGuidelineEnd(webRtcControls.getFold());
|
||||
callScreenTopFoldGuideline.setGuidelineEnd(webRtcControls.getFold());
|
||||
|
||||
if (webRtcControls.displayGroupMembersButton()) {
|
||||
visibleViewSet.add(foldParticipantCountWrapper);
|
||||
}
|
||||
} else {
|
||||
topFoldGuideline.setGuidelineEnd(0);
|
||||
callScreenTopFoldGuideline.setGuidelineEnd(0);
|
||||
}
|
||||
|
||||
setShowParticipantsState(webRtcControls.adjustForFold());
|
||||
|
||||
if (webRtcControls.displayStartCallControls()) {
|
||||
visibleViewSet.add(footerGradient);
|
||||
visibleViewSet.add(startCallControls);
|
||||
@@ -577,10 +612,15 @@ public class WebRtcCallView extends FrameLayout {
|
||||
|
||||
controls = webRtcControls;
|
||||
|
||||
if (!controls.isFadeOutEnabled()) {
|
||||
controlsVisible = true;
|
||||
}
|
||||
|
||||
if (!visibleViewSet.equals(lastVisibleSet) || !controls.isFadeOutEnabled()) {
|
||||
fadeInNewUiState(lastVisibleSet, webRtcControls.displaySmallOngoingCallButtons());
|
||||
post(() -> pictureInPictureGestureHelper.setVerticalBoundaries(toolbar.getBottom(), videoToggle.getTop()));
|
||||
}
|
||||
|
||||
onWindowSystemUiVisibilityChanged(getWindowSystemUiVisibility());
|
||||
}
|
||||
|
||||
public @NonNull View getVideoTooltipTarget() {
|
||||
@@ -597,6 +637,18 @@ public class WebRtcCallView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
private void setShowParticipantsState(boolean isFolded) {
|
||||
MenuItem item = toolbar.getMenu().findItem(R.id.menu_group_call_participants_list);
|
||||
|
||||
if (item != null) {
|
||||
View showParticipants = item.getActionView();
|
||||
showParticipants.animate().alpha(isFolded ? 0f : 1f);
|
||||
showParticipants.setClickable(!isFolded);
|
||||
foldParticipantCountWrapper.animate().alpha(isFolded ? 1f : 0f);
|
||||
foldParticipantCount.setClickable(isFolded);
|
||||
}
|
||||
}
|
||||
|
||||
private void expandPip(@NonNull CallParticipant localCallParticipant, @NonNull CallParticipant focusedParticipant) {
|
||||
pictureInPictureExpansionHelper.expand(smallLocalRenderFrame, new PictureInPictureExpansionHelper.Callback() {
|
||||
@Override
|
||||
@@ -760,6 +812,8 @@ public class WebRtcCallView extends FrameLayout {
|
||||
constraintSet.setVisibility(view.getId(), visibility);
|
||||
}
|
||||
|
||||
adjustParticipantsRecycler(constraintSet);
|
||||
|
||||
constraintSet.applyTo(parent);
|
||||
|
||||
layoutParticipants();
|
||||
@@ -789,9 +843,21 @@ public class WebRtcCallView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
adjustParticipantsRecycler(constraintSet);
|
||||
|
||||
constraintSet.applyTo(parent);
|
||||
}
|
||||
|
||||
private void adjustParticipantsRecycler(@NonNull ConstraintSet constraintSet) {
|
||||
if (controlsVisible) {
|
||||
constraintSet.connect(R.id.call_screen_participants_recycler, ConstraintSet.BOTTOM, R.id.call_screen_video_toggle, ConstraintSet.TOP);
|
||||
} else {
|
||||
constraintSet.connect(R.id.call_screen_participants_recycler, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM);
|
||||
}
|
||||
|
||||
constraintSet.setHorizontalBias(R.id.call_screen_participants_recycler, controls.adjustForFold() ? 0.5f : 1f);
|
||||
}
|
||||
|
||||
private void scheduleFadeOut() {
|
||||
cancelFadeOut();
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.components.webrtc;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -14,6 +15,7 @@ import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor;
|
||||
import org.thoughtcrime.securesms.components.sensors.Orientation;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
@@ -41,7 +43,9 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
private final MutableLiveData<Boolean> microphoneEnabled = new MutableLiveData<>(true);
|
||||
private final MutableLiveData<Boolean> isInPipMode = new MutableLiveData<>(false);
|
||||
private final MutableLiveData<WebRtcControls> webRtcControls = new MutableLiveData<>(WebRtcControls.NONE);
|
||||
private final LiveData<WebRtcControls> realWebRtcControls = LiveDataUtil.combineLatest(isInPipMode, webRtcControls, this::getRealWebRtcControls);
|
||||
private final MutableLiveData<WebRtcControls.FoldableState> foldableState = new MutableLiveData<>(WebRtcControls.FoldableState.flat());
|
||||
private final LiveData<WebRtcControls> controlsWithFoldableState = LiveDataUtil.combineLatest(foldableState, webRtcControls, this::updateControlsFoldableState);
|
||||
private final LiveData<WebRtcControls> realWebRtcControls = LiveDataUtil.combineLatest(isInPipMode, controlsWithFoldableState, this::getRealWebRtcControls);
|
||||
private final SingleLiveEvent<Event> events = new SingleLiveEvent<Event>();
|
||||
private final MutableLiveData<Long> elapsed = new MutableLiveData<>(-1L);
|
||||
private final MutableLiveData<LiveRecipient> liveRecipient = new MutableLiveData<>(Recipient.UNKNOWN.live());
|
||||
@@ -53,6 +57,8 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
private final LiveData<List<GroupMemberEntry.FullMember>> groupMembers = LiveDataUtil.skip(Transformations.switchMap(groupRecipient, r -> Transformations.distinctUntilChanged(new LiveGroup(r.requireGroupId()).getFullMembers())), 1);
|
||||
private final LiveData<Boolean> shouldShowSpeakerHint = Transformations.map(participantsState, this::shouldShowSpeakerHint);
|
||||
private final LiveData<Orientation> orientation;
|
||||
private final MutableLiveData<Boolean> isLandscapeEnabled = new MutableLiveData<>();
|
||||
private final LiveData<Integer> controlsRotation;
|
||||
|
||||
private boolean canDisplayTooltipIfNeeded = true;
|
||||
private boolean hasEnabledLocalVideo = false;
|
||||
@@ -69,13 +75,24 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
private final WebRtcCallRepository repository = new WebRtcCallRepository(ApplicationDependencies.getApplication());
|
||||
|
||||
private WebRtcCallViewModel(@NonNull DeviceOrientationMonitor deviceOrientationMonitor) {
|
||||
orientation = deviceOrientationMonitor.getOrientation();
|
||||
orientation = deviceOrientationMonitor.getOrientation();
|
||||
controlsRotation = LiveDataUtil.combineLatest(Transformations.distinctUntilChanged(isLandscapeEnabled),
|
||||
Transformations.distinctUntilChanged(orientation),
|
||||
this::resolveRotation);
|
||||
}
|
||||
|
||||
public LiveData<Integer> getControlsRotation() {
|
||||
return controlsRotation;
|
||||
}
|
||||
|
||||
public LiveData<Orientation> getOrientation() {
|
||||
return Transformations.distinctUntilChanged(orientation);
|
||||
}
|
||||
|
||||
public LiveData<Pair<Orientation, Boolean>> getOrientationAndLandscapeEnabled() {
|
||||
return LiveDataUtil.combineLatest(orientation, isLandscapeEnabled, Pair::new);
|
||||
}
|
||||
|
||||
public LiveData<Boolean> getMicrophoneEnabled() {
|
||||
return Transformations.distinctUntilChanged(microphoneEnabled);
|
||||
}
|
||||
@@ -92,6 +109,12 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
liveRecipient.setValue(recipient.live());
|
||||
}
|
||||
|
||||
public void setFoldableState(@NonNull WebRtcControls.FoldableState foldableState) {
|
||||
this.foldableState.postValue(foldableState);
|
||||
|
||||
ThreadUtil.runOnMain(() -> participantsState.setValue(CallParticipantsState.update(participantsState.getValue(), foldableState)));
|
||||
}
|
||||
|
||||
public LiveData<Event> getEvents() {
|
||||
return events;
|
||||
}
|
||||
@@ -140,6 +163,10 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
participantsState.setValue(CallParticipantsState.update(participantsState.getValue(), isInPipMode));
|
||||
}
|
||||
|
||||
public void setIsLandscapeEnabled(boolean isLandscapeEnabled) {
|
||||
this.isLandscapeEnabled.postValue(isLandscapeEnabled);
|
||||
}
|
||||
|
||||
@MainThread
|
||||
public void setIsViewingFocusedParticipant(@NonNull CallParticipantsState.SelectedPage page) {
|
||||
if (page == CallParticipantsState.SelectedPage.FOCUSED) {
|
||||
@@ -239,6 +266,23 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
private int resolveRotation(boolean isLandscapeEnabled, @NonNull Orientation orientation) {
|
||||
if (isLandscapeEnabled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (orientation) {
|
||||
case LANDSCAPE_LEFT_EDGE:
|
||||
return 90;
|
||||
case LANDSCAPE_RIGHT_EDGE:
|
||||
return -90;
|
||||
case PORTRAIT_BOTTOM_EDGE:
|
||||
return 0;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean containsPlaceholders(@NonNull List<CallParticipant> callParticipants) {
|
||||
return Stream.of(callParticipants).anyMatch(p -> p.getCallParticipantId().getDemuxId() == CallParticipantId.DEFAULT_ID);
|
||||
}
|
||||
@@ -314,7 +358,12 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
callState,
|
||||
groupCallState,
|
||||
audioOutput,
|
||||
participantLimit));
|
||||
participantLimit,
|
||||
WebRtcControls.FoldableState.flat()));
|
||||
}
|
||||
|
||||
private @NonNull WebRtcControls updateControlsFoldableState(@NonNull WebRtcControls.FoldableState foldableState, @NonNull WebRtcControls controls) {
|
||||
return controls.withFoldableState(foldableState);
|
||||
}
|
||||
|
||||
private @NonNull WebRtcControls getRealWebRtcControls(boolean isInPipMode, @NonNull WebRtcControls controls) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.Px;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
@@ -11,7 +12,7 @@ import org.thoughtcrime.securesms.R;
|
||||
public final class WebRtcControls {
|
||||
|
||||
public static final WebRtcControls NONE = new WebRtcControls();
|
||||
public static final WebRtcControls PIP = new WebRtcControls(false, false, false, false, true, false, CallState.NONE, GroupCallState.NONE, WebRtcAudioOutput.HANDSET, null);
|
||||
public static final WebRtcControls PIP = new WebRtcControls(false, false, false, false, true, false, CallState.NONE, GroupCallState.NONE, WebRtcAudioOutput.HANDSET, null, FoldableState.flat());
|
||||
|
||||
private final boolean isRemoteVideoEnabled;
|
||||
private final boolean isLocalVideoEnabled;
|
||||
@@ -23,9 +24,10 @@ public final class WebRtcControls {
|
||||
private final GroupCallState groupCallState;
|
||||
private final WebRtcAudioOutput audioOutput;
|
||||
private final Long participantLimit;
|
||||
private final FoldableState foldableState;
|
||||
|
||||
private WebRtcControls() {
|
||||
this(false, false, false, false, false, false, CallState.NONE, GroupCallState.NONE, WebRtcAudioOutput.HANDSET, null);
|
||||
this(false, false, false, false, false, false, CallState.NONE, GroupCallState.NONE, WebRtcAudioOutput.HANDSET, null, FoldableState.flat());
|
||||
}
|
||||
|
||||
WebRtcControls(boolean isLocalVideoEnabled,
|
||||
@@ -37,7 +39,8 @@ public final class WebRtcControls {
|
||||
@NonNull CallState callState,
|
||||
@NonNull GroupCallState groupCallState,
|
||||
@NonNull WebRtcAudioOutput audioOutput,
|
||||
@Nullable Long participantLimit)
|
||||
@Nullable Long participantLimit,
|
||||
@NonNull FoldableState foldableState)
|
||||
{
|
||||
this.isLocalVideoEnabled = isLocalVideoEnabled;
|
||||
this.isRemoteVideoEnabled = isRemoteVideoEnabled;
|
||||
@@ -49,6 +52,21 @@ public final class WebRtcControls {
|
||||
this.groupCallState = groupCallState;
|
||||
this.audioOutput = audioOutput;
|
||||
this.participantLimit = participantLimit;
|
||||
this.foldableState = foldableState;
|
||||
}
|
||||
|
||||
public @NonNull WebRtcControls withFoldableState(FoldableState foldableState) {
|
||||
return new WebRtcControls(isLocalVideoEnabled,
|
||||
isRemoteVideoEnabled,
|
||||
isMoreThanOneCameraAvailable,
|
||||
isBluetoothAvailable,
|
||||
isInPipMode,
|
||||
hasAtLeastOneRemote,
|
||||
callState,
|
||||
groupCallState,
|
||||
audioOutput,
|
||||
participantLimit,
|
||||
foldableState);
|
||||
}
|
||||
|
||||
boolean displayErrorControls() {
|
||||
@@ -59,6 +77,14 @@ public final class WebRtcControls {
|
||||
return isPreJoin();
|
||||
}
|
||||
|
||||
boolean adjustForFold() {
|
||||
return foldableState.isFolded();
|
||||
}
|
||||
|
||||
@Px int getFold() {
|
||||
return foldableState.getFoldPoint();
|
||||
}
|
||||
|
||||
@StringRes int getStartCallButtonText() {
|
||||
if (isGroupCall()) {
|
||||
if (groupCallState == GroupCallState.FULL) {
|
||||
@@ -130,7 +156,7 @@ public final class WebRtcControls {
|
||||
}
|
||||
|
||||
boolean isFadeOutEnabled() {
|
||||
return isAtLeastOutgoing() && isRemoteVideoEnabled;
|
||||
return isAtLeastOutgoing() && isRemoteVideoEnabled && foldableState.isFlat();
|
||||
}
|
||||
|
||||
boolean displaySmallOngoingCallButtons() {
|
||||
@@ -199,4 +225,35 @@ public final class WebRtcControls {
|
||||
return compareTo(other) >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class FoldableState {
|
||||
|
||||
private static final int NOT_SET = -1;
|
||||
|
||||
private final int foldPoint;
|
||||
|
||||
public FoldableState(int foldPoint) {
|
||||
this.foldPoint = foldPoint;
|
||||
}
|
||||
|
||||
public boolean isFolded() {
|
||||
return foldPoint != NOT_SET;
|
||||
}
|
||||
|
||||
public boolean isFlat() {
|
||||
return foldPoint == NOT_SET;
|
||||
}
|
||||
|
||||
public int getFoldPoint() {
|
||||
return foldPoint;
|
||||
}
|
||||
|
||||
public static @NonNull FoldableState folded(int foldPoint) {
|
||||
return new FoldableState(foldPoint);
|
||||
}
|
||||
|
||||
public static @NonNull FoldableState flat() {
|
||||
return new FoldableState(NOT_SET);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user