Skip to content

Commit

Permalink
Add animations to call screen.
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-signal authored and greyson-signal committed Sep 23, 2020
1 parent 0c73ddc commit e05f137
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 39 deletions.
Expand Up @@ -7,6 +7,7 @@
import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.ringrtc.CameraState;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

Expand All @@ -17,7 +18,9 @@
*/
public final class CallParticipantsState {

public static final CallParticipantsState STARTING_STATE = new CallParticipantsState(WebRtcViewModel.State.CALL_DISCONNECTED,
private static final int SMALL_GROUP_MAX = 6;

public static final CallParticipantsState STARTING_STATE = new CallParticipantsState(WebRtcViewModel.State.CALL_DISCONNECTED,
Collections.emptyList(),
CallParticipant.createLocal(CameraState.UNKNOWN, new BroadcastVideoSink(null), false),
null,
Expand Down Expand Up @@ -55,21 +58,29 @@ public CallParticipantsState(@NonNull WebRtcViewModel.State callState,
}

public @NonNull List<CallParticipant> getGridParticipants() {
if (getAllRemoteParticipants().size() > 6) {
return getAllRemoteParticipants().subList(0, 6);
if (getAllRemoteParticipants().size() > SMALL_GROUP_MAX) {
return getAllRemoteParticipants().subList(0, SMALL_GROUP_MAX);
} else {
return getAllRemoteParticipants();
}
}

public @NonNull List<CallParticipant> getListParticipants() {
List<CallParticipant> listParticipants = new ArrayList<>();

if (isViewingFocusedParticipant && getAllRemoteParticipants().size() > 1) {
return getAllRemoteParticipants().subList(1, getAllRemoteParticipants().size());
} else if (getAllRemoteParticipants().size() > 6) {
return getAllRemoteParticipants().subList(6, getAllRemoteParticipants().size());
listParticipants.addAll(getAllRemoteParticipants().subList(1, getAllRemoteParticipants().size()));
} else if (getAllRemoteParticipants().size() > SMALL_GROUP_MAX) {
listParticipants.addAll(getAllRemoteParticipants().subList(SMALL_GROUP_MAX, getAllRemoteParticipants().size()));
} else {
return Collections.emptyList();
}

listParticipants.add(CallParticipant.EMPTY);

Collections.reverse(listParticipants);

return listParticipants;
}

public @NonNull List<CallParticipant> getAllRemoteParticipants() {
Expand All @@ -88,6 +99,10 @@ public CallParticipantsState(@NonNull WebRtcViewModel.State callState,
return localRenderState;
}

public boolean isLargeVideoGroup() {
return getAllRemoteParticipants().size() > SMALL_GROUP_MAX;
}

public boolean isInPipMode() {
return isInPipMode;
}
Expand Down
Expand Up @@ -9,14 +9,14 @@
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;

import androidx.annotation.NonNull;
import androidx.core.view.GestureDetectorCompat;

import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.views.TouchInterceptingFrameLayout;

import java.util.Arrays;
Expand All @@ -26,11 +26,14 @@

public class PictureInPictureGestureHelper extends GestureDetector.SimpleOnGestureListener {

private static final float DECELERATION_RATE = 0.99f;
private static final float DECELERATION_RATE = 0.99f;
private static final Interpolator FLING_INTERPOLATOR = new ViscousFluidInterpolator();
private static final Interpolator ADJUST_INTERPOLATOR = new AccelerateDecelerateInterpolator();

private final ViewGroup parent;
private final View child;
private final int framePadding;
private final ViewGroup parent;
private final View child;
private final int framePadding;
private final Queue<Runnable> runAfterFling;

private int pipWidth;
private int pipHeight;
Expand All @@ -46,7 +49,7 @@ public class PictureInPictureGestureHelper extends GestureDetector.SimpleOnGestu
private VelocityTracker velocityTracker;
private int maximumFlingVelocity;
private boolean isLockedToBottomEnd;
private Queue<Runnable> runAfterFling;
private Interpolator interpolator;

@SuppressLint("ClickableViewAccessibility")
public static PictureInPictureGestureHelper applyTo(@NonNull View child) {
Expand Down Expand Up @@ -101,6 +104,7 @@ private PictureInPictureGestureHelper(@NonNull ViewGroup parent, @NonNull View c
this.pipHeight = child.getResources().getDimensionPixelSize(R.dimen.picture_in_picture_gesture_helper_pip_height);
this.maximumFlingVelocity = ViewConfiguration.get(child.getContext()).getScaledMaximumFlingVelocity();
this.runAfterFling = new LinkedList<>();
this.interpolator = ADJUST_INTERPOLATOR;
}

public void clearVerticalBoundaries() {
Expand Down Expand Up @@ -130,8 +134,12 @@ public void adjustPip() {
pipHeight = child.getMeasuredHeight();

if (isAnimating) {
interpolator = ADJUST_INTERPOLATOR;

fling();
} else if (!isDragging) {
interpolator = ADJUST_INTERPOLATOR;

onFling(null, null, 0, 0);
}
}
Expand Down Expand Up @@ -160,6 +168,7 @@ public boolean onDown(MotionEvent e) {
isDragging = true;
pipWidth = child.getMeasuredWidth();
pipHeight = child.getMeasuredHeight();
interpolator = FLING_INTERPOLATOR;

return true;
}
Expand Down Expand Up @@ -216,7 +225,7 @@ private void fling() {
.translationX(getTranslationXForPoint(nearestCornerPosition))
.translationY(getTranslationYForPoint(nearestCornerPosition))
.setDuration(250)
.setInterpolator(new ViscousFluidInterpolator())
.setInterpolator(interpolator)
.setListener(new AnimationCompleteListener() {
@Override
public void onAnimationEnd(Animator animation) {
Expand Down
Expand Up @@ -14,29 +14,50 @@

class WebRtcCallParticipantsRecyclerAdapter extends ListAdapter<CallParticipant, WebRtcCallParticipantsRecyclerAdapter.ViewHolder> {

private static final int PARTICIPANT = 0;
private static final int EMPTY = 1;

protected WebRtcCallParticipantsRecyclerAdapter() {
super(new DiffCallback());
}

@Override
public @NonNull ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.webrtc_call_participant_recycler_item, parent, false));
if (viewType == PARTICIPANT) {
return new ParticipantViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.webrtc_call_participant_recycler_item, parent, false));
} else {
return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.webrtc_call_participant_recycler_empty_item, parent, false));
}
}

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
holder.bind(getItem(position));
}

@Override
public int getItemViewType(int position) {
return getItem(position) == CallParticipant.EMPTY ? EMPTY : PARTICIPANT;
}

static class ViewHolder extends RecyclerView.ViewHolder {
ViewHolder(@NonNull View itemView) {
super(itemView);
}

void bind(@NonNull CallParticipant callParticipant) {}
}

private static class ParticipantViewHolder extends ViewHolder {

private final CallParticipantView callParticipantView;

ViewHolder(@NonNull View itemView) {
ParticipantViewHolder(@NonNull View itemView) {
super(itemView);
callParticipantView = itemView.findViewById(R.id.call_participant);
}

@Override
void bind(@NonNull CallParticipant callParticipant) {
callParticipantView.setCallParticipant(callParticipant);
}
Expand Down
Expand Up @@ -20,8 +20,10 @@
import androidx.core.util.Consumer;
import androidx.recyclerview.widget.RecyclerView;
import androidx.transition.AutoTransition;
import androidx.transition.ChangeBounds;
import androidx.transition.Transition;
import androidx.transition.TransitionManager;
import androidx.transition.TransitionSet;
import androidx.viewpager2.widget.MarginPageTransformer;
import androidx.viewpager2.widget.ViewPager2;

Expand Down Expand Up @@ -51,6 +53,7 @@ public class WebRtcCallView extends FrameLayout {

public static final int FADE_OUT_DELAY = 5000;
public static final int PIP_RESIZE_DURATION = 300;
public static final int CONTROLS_HEIGHT = 98;

private WebRtcAudioOutputToggleButton audioToggle;
private AccessibleToggleButton videoToggle;
Expand All @@ -62,6 +65,7 @@ public class WebRtcCallView extends FrameLayout {
private TextView recipientName;
private TextView status;
private ConstraintLayout parent;
private ConstraintLayout participantsParent;
private ControlsListener controlsListener;
private RecipientId recipientId;
private ImageView answer;
Expand All @@ -75,6 +79,8 @@ public class WebRtcCallView extends FrameLayout {
private ViewPager2 callParticipantsPager;
private RecyclerView callParticipantsRecycler;
private Toolbar toolbar;
private int pagerBottomMarginDp;
private boolean controlsVisible = true;

private WebRtcCallParticipantsPagerAdapter pagerAdapter;
private WebRtcCallParticipantsRecyclerAdapter recyclerAdapter;
Expand Down Expand Up @@ -114,6 +120,7 @@ protected void onFinishInflate() {
recipientName = findViewById(R.id.call_screen_recipient_name);
status = findViewById(R.id.call_screen_status);
parent = findViewById(R.id.call_screen);
participantsParent = findViewById(R.id.call_screen_participants_parent);
answer = findViewById(R.id.call_screen_answer_call);
cameraDirectionToggle = findViewById(R.id.call_screen_camera_direction_toggle);
hangup = findViewById(R.id.call_screen_end_call);
Expand Down Expand Up @@ -229,6 +236,12 @@ public void updateCallParticipants(@NonNull CallParticipantsState state) {
pagerAdapter.submitList(pages);
recyclerAdapter.submitList(state.getListParticipants());
updateLocalCallParticipant(state.getLocalRenderState(), state.getLocalParticipant());

if (state.isLargeVideoGroup()) {
layoutParticipantsForLargeCount();
} else {
layoutParticipantsForSmallCount();
}
}

public void updateLocalCallParticipant(@NonNull WebRtcLocalRenderState state, @NonNull CallParticipant localCallParticipant) {
Expand Down Expand Up @@ -371,6 +384,12 @@ public void setWebRtcControls(@NonNull WebRtcControls webRtcControls) {
updateButtonStateForLargeButtons();
}

if (webRtcControls.displayRemoteVideoRecycler()) {
callParticipantsRecycler.setVisibility(View.VISIBLE);
} else {
callParticipantsRecycler.setVisibility(View.GONE);
}

if (webRtcControls.isFadeOutEnabled()) {
if (!controls.isFadeOutEnabled()) {
scheduleFadeOut();
Expand Down Expand Up @@ -443,9 +462,44 @@ private void fadeInControls() {
scheduleFadeOut();
}

private void fadeControls(int visibility) {
private void layoutParticipantsForSmallCount() {
pagerBottomMarginDp = 0;

layoutParticipants();
}

private void layoutParticipantsForLargeCount() {
pagerBottomMarginDp = 104;

layoutParticipants();
}

private int withControlsHeight(int margin) {
if (margin == 0) {
return 0;
}

return controlsVisible ? margin + CONTROLS_HEIGHT : margin;
}

private void layoutParticipants() {
Transition transition = new AutoTransition().setDuration(TRANSITION_DURATION_MILLIS);

TransitionManager.beginDelayedTransition(participantsParent, transition);

ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(participantsParent);

constraintSet.setMargin(R.id.call_screen_participants_pager, ConstraintSet.BOTTOM, ViewUtil.dpToPx(withControlsHeight(pagerBottomMarginDp)));
constraintSet.applyTo(participantsParent);
}

private void fadeControls(int visibility) {
controlsVisible = visibility == VISIBLE;

Transition transition = new AutoTransition().setOrdering(TransitionSet.ORDERING_TOGETHER)
.setDuration(TRANSITION_DURATION_MILLIS);

TransitionManager.beginDelayedTransition(parent, transition);

ConstraintSet constraintSet = new ConstraintSet();
Expand All @@ -456,6 +510,8 @@ private void fadeControls(int visibility) {
}

constraintSet.applyTo(parent);

layoutParticipants();
}

private void fadeInNewUiState(@NonNull Set<View> previouslyVisibleViewSet, boolean useSmallMargins) {
Expand Down
Expand Up @@ -147,6 +147,18 @@ private void updateWebRtcControls(@NonNull WebRtcViewModel.State state,
callState = WebRtcControls.CallState.INCOMING;
answerWithVideoAvailable = isRemoteVideoOffer;
break;
case CALL_OUTGOING:
case CALL_RINGING:
callState = WebRtcControls.CallState.OUTGOING;
break;
case CALL_ACCEPTED_ELSEWHERE:
case CALL_DECLINED_ELSEWHERE:
case CALL_ONGOING_ELSEWHERE:
case CALL_NEEDS_PERMISSION:
case CALL_BUSY:
case CALL_DISCONNECTED:
callState = WebRtcControls.CallState.ENDING;
break;
default:
callState = WebRtcControls.CallState.ONGOING;
}
Expand Down

0 comments on commit e05f137

Please sign in to comment.