diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/AudioIndicatorView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/AudioIndicatorView.kt
index 8edbcd1f03..73576787a7 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/AudioIndicatorView.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/AudioIndicatorView.kt
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.components.webrtc
import android.animation.ValueAnimator
import android.content.Context
+import android.content.res.ColorStateList
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
@@ -10,6 +11,7 @@ import android.util.AttributeSet
import android.view.View
import android.view.animation.DecelerateInterpolator
import android.widget.FrameLayout
+import androidx.core.content.ContextCompat
import org.signal.core.util.DimensionUnit
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.events.CallParticipant
@@ -31,9 +33,9 @@ class AudioIndicatorView(context: Context, attrs: AttributeSet) : FrameLayout(co
}
private val barRect = RectF()
- private val barWidth = DimensionUnit.DP.toPixels(4f)
+ private val barWidth = DimensionUnit.DP.toPixels(3f)
private val barRadius = DimensionUnit.DP.toPixels(32f)
- private val barPadding = DimensionUnit.DP.toPixels(4f)
+ private val barPadding = DimensionUnit.DP.toPixels(3f)
private var middleBarAnimation: ValueAnimator? = null
private var sideBarAnimation: ValueAnimator? = null
@@ -43,6 +45,9 @@ class AudioIndicatorView(context: Context, attrs: AttributeSet) : FrameLayout(co
init {
inflate(context, R.layout.audio_indicator_view, this)
setWillNotDraw(false)
+
+ setBackgroundResource(R.drawable.circle_tintable)
+ backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(context, R.color.transparent_black_70))
}
private val micMuted: View = findViewById(R.id.mic_muted)
@@ -55,11 +60,11 @@ class AudioIndicatorView(context: Context, attrs: AttributeSet) : FrameLayout(co
if (showAudioLevel) {
val scaleFactor = when (level!!) {
- CallParticipant.AudioLevel.LOWEST -> 0.2f
- CallParticipant.AudioLevel.LOW -> 0.4f
- CallParticipant.AudioLevel.MEDIUM -> 0.6f
- CallParticipant.AudioLevel.HIGH -> 0.8f
- CallParticipant.AudioLevel.HIGHEST -> 1.0f
+ CallParticipant.AudioLevel.LOWEST -> 0.1f
+ CallParticipant.AudioLevel.LOW -> 0.3f
+ CallParticipant.AudioLevel.MEDIUM -> 0.5f
+ CallParticipant.AudioLevel.HIGH -> 0.65f
+ CallParticipant.AudioLevel.HIGHEST -> 0.8f
}
middleBarAnimation?.end()
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantView.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantView.java
index dc26d5d2ae..7d4d7dbcec 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantView.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantView.java
@@ -9,12 +9,13 @@ import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewKt;
import androidx.core.widget.ImageViewCompat;
+import androidx.transition.Transition;
import androidx.transition.TransitionManager;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
@@ -37,6 +38,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.AvatarUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.webrtc.RendererCommon;
+import org.whispersystems.signalservice.api.util.Preconditions;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@@ -57,6 +59,8 @@ public class CallParticipantView extends ConstraintLayout {
private boolean infoMode;
private Runnable missingMediaKeysUpdater;
+ private SelfPipMode selfPipMode = SelfPipMode.NOT_SELF_PIP;
+
private AppCompatImageView backgroundAvatar;
private AvatarImageView avatar;
private BadgeImageView badge;
@@ -211,6 +215,85 @@ public class CallParticipantView extends ConstraintLayout {
pipBadge.setVisibility(shouldRenderInPip ? View.VISIBLE : View.GONE);
}
+ /**
+ * Adjust UI elements for the various self PIP positions. If called after a {@link TransitionManager#beginDelayedTransition(ViewGroup, Transition)},
+ * the changes to the UI elements will animate.
+ */
+ void setSelfPipMode(@NonNull SelfPipMode selfPipMode) {
+ Preconditions.checkArgument(selfPipMode != SelfPipMode.NOT_SELF_PIP);
+
+ if (this.selfPipMode == selfPipMode) {
+ return;
+ }
+
+ this.selfPipMode = selfPipMode;
+
+ ConstraintSet constraints = new ConstraintSet();
+ constraints.clone(this);
+
+ switch (selfPipMode) {
+ case NORMAL_SELF_PIP -> {
+ constraints.connect(
+ R.id.call_participant_audio_indicator,
+ ConstraintSet.START,
+ ConstraintSet.PARENT_ID,
+ ConstraintSet.START,
+ ViewUtil.dpToPx(6)
+ );
+ constraints.clear(
+ R.id.call_participant_audio_indicator,
+ ConstraintSet.END
+ );
+ constraints.setMargin(
+ R.id.call_participant_audio_indicator,
+ ConstraintSet.BOTTOM,
+ ViewUtil.dpToPx(6)
+ );
+ }
+ case EXPANDED_SELF_PIP -> {
+ constraints.connect(
+ R.id.call_participant_audio_indicator,
+ ConstraintSet.START,
+ ConstraintSet.PARENT_ID,
+ ConstraintSet.START,
+ ViewUtil.dpToPx(8)
+ );
+ constraints.clear(
+ R.id.call_participant_audio_indicator,
+ ConstraintSet.END
+ );
+ constraints.setMargin(
+ R.id.call_participant_audio_indicator,
+ ConstraintSet.BOTTOM,
+ ViewUtil.dpToPx(8)
+ );
+ }
+ case MINI_SELF_PIP -> {
+ constraints.connect(
+ R.id.call_participant_audio_indicator,
+ ConstraintSet.START,
+ ConstraintSet.PARENT_ID,
+ ConstraintSet.START,
+ 0
+ );
+ constraints.connect(
+ R.id.call_participant_audio_indicator,
+ ConstraintSet.END,
+ ConstraintSet.PARENT_ID,
+ ConstraintSet.END,
+ 0
+ );
+ constraints.setMargin(
+ R.id.call_participant_audio_indicator,
+ ConstraintSet.BOTTOM,
+ ViewUtil.dpToPx(6)
+ );
+ }
+ }
+
+ constraints.applyTo(this);
+ }
+
void hideAvatar() {
avatar.setAlpha(0f);
badge.setAlpha(0f);
@@ -302,4 +385,11 @@ public class CallParticipantView extends ConstraintLayout {
return photo;
}
}
+
+ public enum SelfPipMode {
+ NOT_SELF_PIP,
+ NORMAL_SELF_PIP,
+ EXPANDED_SELF_PIP,
+ MINI_SELF_PIP
+ }
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/PictureInPictureExpansionHelper.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/PictureInPictureExpansionHelper.java
index f211f4d586..593f5d1725 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/PictureInPictureExpansionHelper.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/PictureInPictureExpansionHelper.java
@@ -3,13 +3,14 @@ package org.thoughtcrime.securesms.components.webrtc;
import android.graphics.Point;
import android.view.View;
import android.view.ViewGroup;
-import android.view.animation.Animation;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
+import androidx.transition.AutoTransition;
+import androidx.transition.Transition;
+import androidx.transition.TransitionListenerAdapter;
+import androidx.transition.TransitionManager;
-import org.thoughtcrime.securesms.animation.ResizeAnimation;
-import org.thoughtcrime.securesms.mediasend.SimpleAnimationListener;
import org.thoughtcrime.securesms.util.ViewUtil;
/**
@@ -22,14 +23,22 @@ final class PictureInPictureExpansionHelper {
private static final int EXPANDED_PIP_WIDTH_DP = 170;
private static final int EXPANDED_PIP_HEIGHT_DP = 300;
- private final View selfPip;
- private final Point expandedDimensions;
+ public static final int NORMAL_PIP_WIDTH_DP = 90;
+ public static final int NORMAL_PIP_HEIGHT_DP = 160;
+
+ public static final int MINI_PIP_WIDTH_DP = 40;
+ public static final int MINI_PIP_HEIGHT_DP = 72;
+
+ private final View selfPip;
+ private final ViewGroup parent;
+ private final Point expandedDimensions;
private State state = State.IS_SHRUNKEN;
private Point defaultDimensions;
public PictureInPictureExpansionHelper(@NonNull View selfPip) {
this.selfPip = selfPip;
+ this.parent = (ViewGroup) selfPip.getParent();
this.defaultDimensions = new Point(selfPip.getLayoutParams().width, selfPip.getLayoutParams().height);
this.expandedDimensions = new Point(ViewUtil.dpToPx(EXPANDED_PIP_WIDTH_DP), ViewUtil.dpToPx(EXPANDED_PIP_HEIGHT_DP));
}
@@ -42,7 +51,11 @@ final class PictureInPictureExpansionHelper {
return state == State.IS_SHRUNKEN || state == State.IS_SHRINKING;
}
- public void setDefaultSize(@NonNull Point dimensions, @NonNull Callback callback) {
+ public boolean isMiniSize() {
+ return defaultDimensions.x < ViewUtil.dpToPx(NORMAL_PIP_WIDTH_DP);
+ }
+
+ public void startDefaultSizeTransition(@NonNull Point dimensions, @NonNull Callback callback) {
if (defaultDimensions.equals(dimensions)) {
return;
}
@@ -53,21 +66,15 @@ final class PictureInPictureExpansionHelper {
return;
}
- ViewGroup.LayoutParams layoutParams = selfPip.getLayoutParams();
- if (layoutParams.width == defaultDimensions.x && layoutParams.height == defaultDimensions.y) {
- callback.onAnimationHasFinished();
- return;
- }
-
- resizeSelfPip(defaultDimensions, callback);
+ beginResizeSelfPipTransition(defaultDimensions, callback);
}
- public void expand() {
+ public void beginExpandTransition() {
if (isExpandedOrExpanding()) {
return;
}
- resizeSelfPip(expandedDimensions, new Callback() {
+ beginResizeSelfPipTransition(expandedDimensions, new Callback() {
@Override
public void onAnimationWillStart() {
state = State.IS_EXPANDING;
@@ -80,12 +87,12 @@ final class PictureInPictureExpansionHelper {
});
}
- public void shrink() {
+ public void beginShrinkTransition() {
if (isShrunkenOrShrinking()) {
return;
}
- resizeSelfPip(defaultDimensions, new Callback() {
+ beginResizeSelfPipTransition(defaultDimensions, new Callback() {
@Override
public void onAnimationWillStart() {
state = State.IS_SHRINKING;
@@ -98,23 +105,30 @@ final class PictureInPictureExpansionHelper {
});
}
- private void resizeSelfPip(@NonNull Point dimension, @NonNull Callback callback) {
- ResizeAnimation resizeAnimation = new ResizeAnimation(selfPip, dimension);
- resizeAnimation.setDuration(PIP_RESIZE_DURATION_MS);
- resizeAnimation.setAnimationListener(new SimpleAnimationListener() {
+ private void beginResizeSelfPipTransition(@NonNull Point dimension, @NonNull Callback callback) {
+ TransitionManager.endTransitions(parent);
+
+ Transition transition = new AutoTransition().setDuration(PIP_RESIZE_DURATION_MS);
+ transition.addListener(new TransitionListenerAdapter() {
@Override
- public void onAnimationStart(Animation animation) {
+ public void onTransitionStart(@NonNull Transition transition) {
callback.onAnimationWillStart();
}
@Override
- public void onAnimationEnd(Animation animation) {
+ public void onTransitionEnd(@NonNull Transition transition) {
callback.onAnimationHasFinished();
}
});
- selfPip.clearAnimation();
- selfPip.startAnimation(resizeAnimation);
+ TransitionManager.beginDelayedTransition(parent, transition);
+
+ ViewGroup.LayoutParams params = selfPip.getLayoutParams();
+
+ params.width = dimension.x;
+ params.height = dimension.y;
+
+ selfPip.setLayoutParams(params);
}
enum State {
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 51c230bf0e..509ee41c68 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
@@ -497,21 +497,22 @@ public class WebRtcCallView extends InsetAwareConstraintLayout {
videoToggle.setChecked(localCallParticipant.isVideoEnabled(), false);
smallLocalRender.setRenderInPip(true);
+ smallLocalRender.setCallParticipant(localCallParticipant);
+ smallLocalRender.setMirror(localCallParticipant.getCameraDirection() == CameraState.Direction.FRONT);
if (state == WebRtcLocalRenderState.EXPANDED) {
- pictureInPictureExpansionHelper.expand();
+ pictureInPictureExpansionHelper.beginExpandTransition();
+ smallLocalRender.setSelfPipMode(CallParticipantView.SelfPipMode.EXPANDED_SELF_PIP);
return;
} else if ((state.isAnySmall() || state == WebRtcLocalRenderState.GONE) && pictureInPictureExpansionHelper.isExpandedOrExpanding()) {
- pictureInPictureExpansionHelper.shrink();
+ pictureInPictureExpansionHelper.beginShrinkTransition();
+ smallLocalRender.setSelfPipMode(pictureInPictureExpansionHelper.isMiniSize() ? CallParticipantView.SelfPipMode.MINI_SELF_PIP : CallParticipantView.SelfPipMode.NORMAL_SELF_PIP);
if (state != WebRtcLocalRenderState.GONE) {
return;
}
}
- smallLocalRender.setCallParticipant(localCallParticipant);
- smallLocalRender.setMirror(localCallParticipant.getCameraDirection() == CameraState.Direction.FRONT);
-
switch (state) {
case GONE:
largeLocalRender.attachBroadcastVideoSink(null);
@@ -806,26 +807,34 @@ public class WebRtcCallView extends InsetAwareConstraintLayout {
private void animatePipToLargeRectangle(boolean isLandscape) {
final Point dimens;
if (isLandscape) {
- dimens = new Point(ViewUtil.dpToPx(160), ViewUtil.dpToPx(90));
+ dimens = new Point(ViewUtil.dpToPx(PictureInPictureExpansionHelper.NORMAL_PIP_HEIGHT_DP),
+ ViewUtil.dpToPx(PictureInPictureExpansionHelper.NORMAL_PIP_WIDTH_DP));
} else {
- dimens = new Point(ViewUtil.dpToPx(90), ViewUtil.dpToPx(160));
+ dimens = new Point(ViewUtil.dpToPx(PictureInPictureExpansionHelper.NORMAL_PIP_WIDTH_DP),
+ ViewUtil.dpToPx(PictureInPictureExpansionHelper.NORMAL_PIP_HEIGHT_DP));
}
- pictureInPictureExpansionHelper.setDefaultSize(dimens, new PictureInPictureExpansionHelper.Callback() {
+ pictureInPictureExpansionHelper.startDefaultSizeTransition(dimens, new PictureInPictureExpansionHelper.Callback() {
@Override
public void onAnimationHasFinished() {
pictureInPictureGestureHelper.enableCorners();
}
});
+
+ smallLocalRender.setSelfPipMode(CallParticipantView.SelfPipMode.NORMAL_SELF_PIP);
}
private void animatePipToSmallRectangle() {
- pictureInPictureExpansionHelper.setDefaultSize(new Point(ViewUtil.dpToPx(54), ViewUtil.dpToPx(72)), new PictureInPictureExpansionHelper.Callback() {
- @Override
- public void onAnimationHasFinished() {
- pictureInPictureGestureHelper.lockToBottomEnd();
- }
- });
+ pictureInPictureExpansionHelper.startDefaultSizeTransition(new Point(ViewUtil.dpToPx(PictureInPictureExpansionHelper.MINI_PIP_WIDTH_DP),
+ ViewUtil.dpToPx(PictureInPictureExpansionHelper.MINI_PIP_HEIGHT_DP)),
+ new PictureInPictureExpansionHelper.Callback() {
+ @Override
+ public void onAnimationHasFinished() {
+ pictureInPictureGestureHelper.lockToBottomEnd();
+ }
+ });
+
+ smallLocalRender.setSelfPipMode(CallParticipantView.SelfPipMode.MINI_SELF_PIP);
}
private void toggleControls() {
diff --git a/app/src/main/res/drawable/symbol_mic_slash_fill_compact_16.xml b/app/src/main/res/drawable/symbol_mic_slash_fill_compact_16.xml
new file mode 100644
index 0000000000..9418c3e654
--- /dev/null
+++ b/app/src/main/res/drawable/symbol_mic_slash_fill_compact_16.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/audio_indicator_view.xml b/app/src/main/res/layout/audio_indicator_view.xml
index b2fe24d236..d44ea85194 100644
--- a/app/src/main/res/layout/audio_indicator_view.xml
+++ b/app/src/main/res/layout/audio_indicator_view.xml
@@ -5,8 +5,8 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/mic_muted"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="16dp"
+ android:layout_height="16dp"
android:layout_gravity="center"
- app:srcCompat="@drawable/ic_mic_off_solid_18"
+ app:srcCompat="@drawable/symbol_mic_slash_fill_compact_16"
app:tint="@color/core_white" />
diff --git a/app/src/main/res/layout/call_participant_item.xml b/app/src/main/res/layout/call_participant_item.xml
index d752b70879..d3007a370b 100644
--- a/app/src/main/res/layout/call_participant_item.xml
+++ b/app/src/main/res/layout/call_participant_item.xml
@@ -86,8 +86,8 @@
-
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 09b2d859d7..520668725b 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -196,7 +196,7 @@
18dp
48dp
- 14dp
+ 8dp
8dp
0dp