From d0ce4ff03296cced4272eb2172e1bb9d2107018b Mon Sep 17 00:00:00 2001 From: Alan Evans Date: Thu, 29 Aug 2019 11:57:41 -0400 Subject: [PATCH] Lottie play pause animation. --- build.gradle | 6 +- .../bottom_pause_to_play_animation.xml | 8 - .../bottom_play_to_pause_animation.xml | 8 - .../upper_pause_to_play_animation.xml | 8 - .../upper_play_to_pause_animation.xml | 8 - res/drawable/pause_to_play_animation.xml | 18 -- res/drawable/play_to_pause_animation.xml | 18 -- res/layout/audio_view.xml | 56 +++-- res/raw/lottie_play_pause.json | 1 + res/values/strings.xml | 3 +- .../securesms/components/AnimatingToggle.java | 21 +- .../securesms/components/AudioView.java | 193 +++++++++--------- 12 files changed, 140 insertions(+), 208 deletions(-) delete mode 100644 res/animator/bottom_pause_to_play_animation.xml delete mode 100644 res/animator/bottom_play_to_pause_animation.xml delete mode 100644 res/animator/upper_pause_to_play_animation.xml delete mode 100644 res/animator/upper_play_to_pause_animation.xml delete mode 100644 res/drawable/pause_to_play_animation.xml delete mode 100644 res/drawable/play_to_pause_animation.xml create mode 100644 res/raw/lottie_play_pause.json diff --git a/build.gradle b/build.gradle index d70191f2da..b452211b61 100644 --- a/build.gradle +++ b/build.gradle @@ -126,6 +126,9 @@ dependencies { exclude group: 'com.android.support', module: 'appcompat-v7' exclude group: 'com.android.support', module: 'recyclerview-v7' } + + implementation 'com.airbnb.android:lottie:3.0.7' + implementation 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4' implementation 'com.github.dmytrodanylyk.circular-progress-button:library:1.1.3-S2' implementation 'org.signal:android-database-sqlcipher:3.5.9-S3' @@ -171,6 +174,7 @@ dependencyVerification { 'cn.carbswang.android:NumberPickerView:18b3c316d62c7c277978a8d4ed57a5b8f4e943762264960f579a8a549c756729', 'com.tomergoldst.android:tooltips:4c56697dd1ad64b8066535c61f961a6d901e7ae5d97ae27084ba40ad620349b6', 'com.takisoft.fix:colorpicker:f5d0dbabe406a1800498ca9c1faf34db36e021d8488bf10360f29961fe3ab0d1', + 'com.airbnb.android:lottie:6819ff968eb768096133c7873d63351705fd4ac424a0917d86c4145f5035097d', 'com.codewaves.stickyheadergrid:stickyheadergrid:5b4aa6a52a957cfd55f60f4220c11c0c371385a3cb9786cae03c260dcdef5794', 'androidx.appcompat:appcompat:49ad229add44f822fcb3c8405c3fddbd72660da6a839ce29e13158f5980514fd', 'com.melnykov:floatingactionbutton:15d58d4fac0f7a288d0e5301bbaf501a146f5b3f5921277811bf99bd3b397263', @@ -271,6 +275,7 @@ dependencyVerification { 'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a', 'com.klinkerapps:logger:177e325259a8b111ad6745ec10db5861723c99f402222b80629f576f49408541', 'com.google.android:flexbox:a9989fd13ae2ee42765dfc515fe362edf4f326e74925d02a10369df8092a4935', + 'com.squareup.okio:okio:d78fac588458fc099e6c82e91fe5f0375c67434626451a3a77772c65d9eee85b', 'org.jsoup:jsoup:abeaf34795a4de70f72aed6de5966d2955ec7eb348eeb813324f23c999575473', 'org.whispersystems:signal-protocol-android:c80aac5f93114da2810e2e89437831f79fcbc8bece652f64aeab313a651cba85', 'org.signal:signal-metadata-java:2ce71cc4ec5dacfbaef4a265fceef61b8a09696b541994106a22a946762cbdcc', @@ -283,7 +288,6 @@ dependencyVerification { 'org.whispersystems:curve25519-android:b502bcf83efe001f09a7a9efda6f0fa772c43ed5924e97816296ed3503caa092', 'com.fasterxml.jackson.core:jackson-annotations:45d32ac61ef8a744b464c54c2b3414be571016dd46bfc2bec226761cf7ae457a', 'com.fasterxml.jackson.core:jackson-core:3083079be6088db2ed0a0c6ff92204e0aa48fa1de9db5b59c468f35acf882c2c', - 'com.squareup.okio:okio:693fa319a7e8843300602b204023b7674f106ebcb577f2dd5807212b66118bd2', 'org.whispersystems:curve25519-java:0aadd43cf01d11e9b58f867b3c4f25c3194e8b0623d1953d32dfbfbee009e38d', ] } diff --git a/res/animator/bottom_pause_to_play_animation.xml b/res/animator/bottom_pause_to_play_animation.xml deleted file mode 100644 index f5b474bb70..0000000000 --- a/res/animator/bottom_pause_to_play_animation.xml +++ /dev/null @@ -1,8 +0,0 @@ - - \ No newline at end of file diff --git a/res/animator/bottom_play_to_pause_animation.xml b/res/animator/bottom_play_to_pause_animation.xml deleted file mode 100644 index 4f2778d685..0000000000 --- a/res/animator/bottom_play_to_pause_animation.xml +++ /dev/null @@ -1,8 +0,0 @@ - - \ No newline at end of file diff --git a/res/animator/upper_pause_to_play_animation.xml b/res/animator/upper_pause_to_play_animation.xml deleted file mode 100644 index 880c7b0b83..0000000000 --- a/res/animator/upper_pause_to_play_animation.xml +++ /dev/null @@ -1,8 +0,0 @@ - - \ No newline at end of file diff --git a/res/animator/upper_play_to_pause_animation.xml b/res/animator/upper_play_to_pause_animation.xml deleted file mode 100644 index ffa933231c..0000000000 --- a/res/animator/upper_play_to_pause_animation.xml +++ /dev/null @@ -1,8 +0,0 @@ - - \ No newline at end of file diff --git a/res/drawable/pause_to_play_animation.xml b/res/drawable/pause_to_play_animation.xml deleted file mode 100644 index 4d19dc4f9f..0000000000 --- a/res/drawable/pause_to_play_animation.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/res/drawable/play_to_pause_animation.xml b/res/drawable/play_to_pause_animation.xml deleted file mode 100644 index b8bc7934fe..0000000000 --- a/res/drawable/play_to_pause_animation.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/res/layout/audio_view.xml b/res/layout/audio_view.xml index 79dfe4c884..be31665670 100644 --- a/res/layout/audio_view.xml +++ b/res/layout/audio_view.xml @@ -7,6 +7,7 @@ - + + + app:matProg_spinSpeed="0.333" /> - + - + Contact photo - Play - Pause + Play ... Pause Download diff --git a/src/org/thoughtcrime/securesms/components/AnimatingToggle.java b/src/org/thoughtcrime/securesms/components/AnimatingToggle.java index f2b6a3f76e..41676c6a52 100644 --- a/src/org/thoughtcrime/securesms/components/AnimatingToggle.java +++ b/src/org/thoughtcrime/securesms/components/AnimatingToggle.java @@ -1,9 +1,6 @@ package org.thoughtcrime.securesms.components; import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.interpolator.view.animation.FastOutSlowInInterpolator; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; @@ -11,6 +8,10 @@ import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.FrameLayout; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.interpolator.view.animation.FastOutSlowInInterpolator; + import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.util.ViewUtil; @@ -41,13 +42,15 @@ public class AnimatingToggle extends FrameLayout { public void addView(@NonNull View child, int index, ViewGroup.LayoutParams params) { super.addView(child, index, params); - if (getChildCount() == 1) { - current = child; - child.setVisibility(View.VISIBLE); - } else { - child.setVisibility(View.GONE); + if (!isInEditMode()) { + if (getChildCount() == 1) { + current = child; + child.setVisibility(View.VISIBLE); + } else { + child.setVisibility(View.GONE); + } + child.setClickable(false); } - child.setClickable(false); } public void display(@Nullable View view) { diff --git a/src/org/thoughtcrime/securesms/components/AudioView.java b/src/org/thoughtcrime/securesms/components/AudioView.java index 2cb0b51863..80d9730248 100644 --- a/src/org/thoughtcrime/securesms/components/AudioView.java +++ b/src/org/thoughtcrime/securesms/components/AudioView.java @@ -1,15 +1,9 @@ package org.thoughtcrime.securesms.components; -import android.annotation.TargetApi; import android.content.Context; -import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.PorterDuff; -import android.graphics.drawable.AnimatedVectorDrawable; -import android.os.Build; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; @@ -19,6 +13,14 @@ import android.widget.ImageView; import android.widget.SeekBar; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.airbnb.lottie.LottieAnimationView; +import com.airbnb.lottie.LottieProperty; +import com.airbnb.lottie.SimpleColorFilter; +import com.airbnb.lottie.model.KeyPath; +import com.airbnb.lottie.value.LottieValueCallback; import com.pnikosis.materialishprogress.ProgressWheel; import org.greenrobot.eventbus.EventBus; @@ -36,23 +38,27 @@ import java.io.IOException; import java.util.Locale; import java.util.concurrent.TimeUnit; - -public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener { +public final class AudioView extends FrameLayout implements AudioSlidePlayer.Listener { private static final String TAG = AudioView.class.getSimpleName(); - private final @NonNull AnimatingToggle controlToggle; - private final @NonNull ViewGroup container; - private final @NonNull ImageView playButton; - private final @NonNull ImageView pauseButton; - private final @NonNull ImageView downloadButton; - private final @NonNull ProgressWheel downloadProgress; - private final @NonNull SeekBar seekBar; - private final @NonNull TextView timestamp; + private static final int FORWARDS = 1; + private static final int REVERSE = -1; - private @Nullable SlideClickListener downloadListener; - private @Nullable AudioSlidePlayer audioSlidePlayer; - private int backwardsCounter; + @NonNull private final AnimatingToggle controlToggle; + @NonNull private final ViewGroup container; + @NonNull private final View progressAndPlay; + @NonNull private final LottieAnimationView playPauseButton; + @NonNull private final ImageView downloadButton; + @NonNull private final ProgressWheel downloadProgress; + @NonNull private final SeekBar seekBar; + @NonNull private final TextView timestamp; + + @Nullable private SlideClickListener downloadListener; + @Nullable private AudioSlidePlayer audioSlidePlayer; + private int backwardsCounter; + private int lottieDirection; + private boolean isPlaying; public AudioView(Context context) { this(context, null); @@ -66,26 +72,19 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener super(context, attrs, defStyleAttr); inflate(context, R.layout.audio_view, this); - this.container = (ViewGroup) findViewById(R.id.audio_widget_container); - this.controlToggle = (AnimatingToggle) findViewById(R.id.control_toggle); - this.playButton = (ImageView) findViewById(R.id.play); - this.pauseButton = (ImageView) findViewById(R.id.pause); - this.downloadButton = (ImageView) findViewById(R.id.download); - this.downloadProgress = (ProgressWheel) findViewById(R.id.download_progress); - this.seekBar = (SeekBar) findViewById(R.id.seek); - this.timestamp = (TextView) findViewById(R.id.timestamp); + this.container = findViewById(R.id.audio_widget_container); + this.controlToggle = findViewById(R.id.control_toggle); + this.playPauseButton = findViewById(R.id.play); + this.progressAndPlay = findViewById(R.id.progress_and_play); + this.downloadButton = findViewById(R.id.download); + this.downloadProgress = findViewById(R.id.download_progress); + this.seekBar = findViewById(R.id.seek); + this.timestamp = findViewById(R.id.timestamp); - this.playButton.setOnClickListener(new PlayClickedListener()); - this.pauseButton.setOnClickListener(new PauseClickedListener()); + lottieDirection = REVERSE; + this.playPauseButton.setOnClickListener(new PlayPauseClickedListener()); this.seekBar.setOnSeekBarChangeListener(new SeekBarModifiedListener()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - this.playButton.setImageDrawable(context.getDrawable(R.drawable.play_icon)); - this.pauseButton.setImageDrawable(context.getDrawable(R.drawable.pause_icon)); - this.playButton.setBackground(context.getDrawable(R.drawable.ic_circle_fill_white_48dp)); - this.pauseButton.setBackground(context.getDrawable(R.drawable.ic_circle_fill_white_48dp)); - } - if (attrs != null) { TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.AudioView, 0, 0); setTint(typedArray.getColor(R.styleable.AudioView_foregroundTintColor, Color.WHITE), @@ -117,20 +116,23 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener downloadButton.setOnClickListener(new DownloadClickedListener(audio)); if (downloadProgress.isSpinning()) downloadProgress.stopSpinning(); } else if (showControls && audio.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_STARTED) { - controlToggle.displayQuick(downloadProgress); + controlToggle.displayQuick(progressAndPlay); seekBar.setEnabled(false); downloadProgress.spin(); } else { - controlToggle.displayQuick(playButton); seekBar.setEnabled(true); if (downloadProgress.isSpinning()) downloadProgress.stopSpinning(); + showPlayButton(); + lottieDirection = REVERSE; + playPauseButton.cancelAnimation(); + playPauseButton.setFrame(0); } this.audioSlidePlayer = AudioSlidePlayer.createFor(getContext(), audio, this); } public void cleanup() { - if (this.audioSlidePlayer != null && pauseButton.getVisibility() == View.VISIBLE) { + if (this.audioSlidePlayer != null && isPlaying) { this.audioSlidePlayer.stop(); } } @@ -141,16 +143,14 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener @Override public void onStart() { - if (this.pauseButton.getVisibility() != View.VISIBLE) { - togglePlayToPause(); - } + isPlaying = true; + togglePlayToPause(); } @Override public void onStop() { - if (this.playButton.getVisibility() != View.VISIBLE) { - togglePauseToPlay(); - } + isPlaying = false; + togglePauseToPlay(); if (seekBar.getProgress() + 5 >= seekBar.getMax()) { backwardsCounter = 4; @@ -161,8 +161,7 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener @Override public void setFocusable(boolean focusable) { super.setFocusable(focusable); - this.playButton.setFocusable(focusable); - this.pauseButton.setFocusable(focusable); + this.playPauseButton.setFocusable(focusable); this.seekBar.setFocusable(focusable); this.seekBar.setFocusableInTouchMode(focusable); this.downloadButton.setFocusable(focusable); @@ -171,8 +170,7 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener @Override public void setClickable(boolean clickable) { super.setClickable(clickable); - this.playButton.setClickable(clickable); - this.pauseButton.setClickable(clickable); + this.playPauseButton.setClickable(clickable); this.seekBar.setClickable(clickable); this.seekBar.setOnTouchListener(clickable ? null : new TouchIgnoringListener()); this.downloadButton.setClickable(clickable); @@ -181,8 +179,7 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); - this.playButton.setEnabled(enabled); - this.pauseButton.setEnabled(enabled); + this.playPauseButton.setEnabled(enabled); this.seekBar.setEnabled(enabled); this.downloadButton.setEnabled(enabled); } @@ -203,15 +200,9 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener } public void setTint(int foregroundTint, int backgroundTint) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - this.playButton.setBackgroundTintList(ColorStateList.valueOf(foregroundTint)); - this.playButton.setImageTintList(ColorStateList.valueOf(backgroundTint)); - this.pauseButton.setBackgroundTintList(ColorStateList.valueOf(foregroundTint)); - this.pauseButton.setImageTintList(ColorStateList.valueOf(backgroundTint)); - } else { - this.playButton.setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN); - this.pauseButton.setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN); - } + this.playPauseButton.addValueCallback(new KeyPath("**"), + LottieProperty.COLOR_FILTER, + new LottieValueCallback<>(new SimpleColorFilter(foregroundTint))); this.downloadButton.setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN); this.downloadProgress.setBarColor(foregroundTint); @@ -230,49 +221,53 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener } private void togglePlayToPause() { - controlToggle.displayQuick(pauseButton); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - AnimatedVectorDrawable playToPauseDrawable = (AnimatedVectorDrawable)getContext().getDrawable(R.drawable.play_to_pause_animation); - pauseButton.setImageDrawable(playToPauseDrawable); - playToPauseDrawable.start(); - } + startLottieAnimation(FORWARDS); } private void togglePauseToPlay() { - controlToggle.displayQuick(playButton); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - AnimatedVectorDrawable pauseToPlayDrawable = (AnimatedVectorDrawable)getContext().getDrawable(R.drawable.pause_to_play_animation); - playButton.setImageDrawable(pauseToPlayDrawable); - pauseToPlayDrawable.start(); - } + startLottieAnimation(REVERSE); } - private class PlayClickedListener implements View.OnClickListener { - @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private void startLottieAnimation(int direction) { + showPlayButton(); + + if (lottieDirection == direction) { + return; + } + lottieDirection = direction; + + playPauseButton.pauseAnimation(); + playPauseButton.setSpeed(direction * 2); + playPauseButton.resumeAnimation(); + } + + private void showPlayButton() { + downloadProgress.setInstantProgress(1); + downloadProgress.setVisibility(VISIBLE); + playPauseButton.setVisibility(VISIBLE); + controlToggle.displayQuick(progressAndPlay); + } + + private class PlayPauseClickedListener implements View.OnClickListener { + @Override public void onClick(View v) { - try { - Log.d(TAG, "playbutton onClick"); - if (audioSlidePlayer != null) { - togglePlayToPause(); - audioSlidePlayer.play(getProgress()); + if (lottieDirection == REVERSE) { + try { + Log.d(TAG, "playbutton onClick"); + if (audioSlidePlayer != null) { + togglePlayToPause(); + audioSlidePlayer.play(getProgress()); + } + } catch (IOException e) { + Log.w(TAG, e); + } + } else { + Log.d(TAG, "pausebutton onClick"); + if (audioSlidePlayer != null) { + togglePauseToPlay(); + audioSlidePlayer.stop(); } - } catch (IOException e) { - Log.w(TAG, e); - } - } - } - - private class PauseClickedListener implements View.OnClickListener { - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - @Override - public void onClick(View v) { - Log.d(TAG, "pausebutton onClick"); - if (audioSlidePlayer != null) { - togglePauseToPlay(); - audioSlidePlayer.stop(); } } } @@ -291,12 +286,16 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener } private class SeekBarModifiedListener implements SeekBar.OnSeekBarChangeListener { + + private boolean wasPlaying; + @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {} @Override public synchronized void onStartTrackingTouch(SeekBar seekBar) { - if (audioSlidePlayer != null && pauseButton.getVisibility() == View.VISIBLE) { + wasPlaying = isPlaying; + if (audioSlidePlayer != null && isPlaying) { audioSlidePlayer.stop(); } } @@ -304,7 +303,7 @@ public class AudioView extends FrameLayout implements AudioSlidePlayer.Listener @Override public synchronized void onStopTrackingTouch(SeekBar seekBar) { try { - if (audioSlidePlayer != null && pauseButton.getVisibility() == View.VISIBLE) { + if (audioSlidePlayer != null && wasPlaying) { audioSlidePlayer.play(getProgress()); } } catch (IOException e) {