Audio view ported to Kotlin.

pull/365/head
Anton Chekulaev 4 years ago
parent 29f988d45c
commit fdaadcb2b0

@ -1,330 +0,0 @@
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;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import com.pnikosis.materialishprogress.ProgressWheel;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.events.PartProgressEvent;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.AudioSlide;
import org.thoughtcrime.securesms.mms.SlideClickListener;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import network.loki.messenger.R;
public 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 @Nullable SlideClickListener downloadListener;
private @Nullable AudioSlidePlayer audioSlidePlayer;
private int backwardsCounter;
public AudioView(Context context) {
this(context, null);
}
public AudioView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AudioView(Context context, AttributeSet attrs, int defStyleAttr) {
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.playButton.setOnClickListener(new PlayClickedListener());
this.pauseButton.setOnClickListener(new PauseClickedListener());
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),
typedArray.getColor(R.styleable.AudioView_backgroundTintColor, Color.WHITE));
container.setBackgroundColor(typedArray.getColor(R.styleable.AudioView_widgetBackground, Color.TRANSPARENT));
typedArray.recycle();
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (!EventBus.getDefault().isRegistered(this)) EventBus.getDefault().register(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
EventBus.getDefault().unregister(this);
}
public void setAudio(final @NonNull AudioSlide audio,
final boolean showControls)
{
if (showControls && audio.isPendingDownload()) {
controlToggle.displayQuick(downloadButton);
seekBar.setEnabled(false);
downloadButton.setOnClickListener(new DownloadClickedListener(audio));
if (downloadProgress.isSpinning()) downloadProgress.stopSpinning();
} else if (showControls && audio.getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_STARTED) {
controlToggle.displayQuick(downloadProgress);
seekBar.setEnabled(false);
downloadProgress.spin();
} else {
controlToggle.displayQuick(playButton);
seekBar.setEnabled(true);
if (downloadProgress.isSpinning()) downloadProgress.stopSpinning();
}
this.audioSlidePlayer = AudioSlidePlayer.createFor(getContext(), audio, this);
}
public void cleanup() {
if (this.audioSlidePlayer != null && pauseButton.getVisibility() == View.VISIBLE) {
this.audioSlidePlayer.stop();
}
}
public void setDownloadClickListener(@Nullable SlideClickListener listener) {
this.downloadListener = listener;
}
@Override
public void onStart() {
if (this.pauseButton.getVisibility() != View.VISIBLE) {
togglePlayToPause();
}
}
@Override
public void onStop() {
if (this.playButton.getVisibility() != View.VISIBLE) {
togglePauseToPlay();
}
if (seekBar.getProgress() + 5 >= seekBar.getMax()) {
backwardsCounter = 4;
onProgress(0.0, 0);
}
}
@Override
public void setFocusable(boolean focusable) {
super.setFocusable(focusable);
this.playButton.setFocusable(focusable);
this.pauseButton.setFocusable(focusable);
this.seekBar.setFocusable(focusable);
this.seekBar.setFocusableInTouchMode(focusable);
this.downloadButton.setFocusable(focusable);
}
@Override
public void setClickable(boolean clickable) {
super.setClickable(clickable);
this.playButton.setClickable(clickable);
this.pauseButton.setClickable(clickable);
this.seekBar.setClickable(clickable);
this.seekBar.setOnTouchListener(clickable ? null : new TouchIgnoringListener());
this.downloadButton.setClickable(clickable);
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
this.playButton.setEnabled(enabled);
this.pauseButton.setEnabled(enabled);
this.seekBar.setEnabled(enabled);
this.downloadButton.setEnabled(enabled);
}
@Override
public void onProgress(double progress, long millis) {
int seekProgress = (int)Math.floor(progress * this.seekBar.getMax());
if (seekProgress > seekBar.getProgress() || backwardsCounter > 3) {
backwardsCounter = 0;
this.seekBar.setProgress(seekProgress);
this.timestamp.setText(String.format("%02d:%02d",
TimeUnit.MILLISECONDS.toMinutes(millis),
TimeUnit.MILLISECONDS.toSeconds(millis)));
} else {
backwardsCounter++;
}
}
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.downloadButton.setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN);
this.downloadProgress.setBarColor(foregroundTint);
this.timestamp.setTextColor(foregroundTint);
this.seekBar.getProgressDrawable().setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN);
this.seekBar.getThumb().setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN);
}
private double getProgress() {
if (this.seekBar.getProgress() <= 0 || this.seekBar.getMax() <= 0) {
return 0;
} else {
return (double)this.seekBar.getProgress() / (double)this.seekBar.getMax();
}
}
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();
}
}
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();
}
}
private class PlayClickedListener implements View.OnClickListener {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void onClick(View v) {
try {
Log.d(TAG, "playbutton onClick");
if (audioSlidePlayer != null) {
togglePlayToPause();
audioSlidePlayer.play(getProgress());
}
} 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();
}
}
}
private class DownloadClickedListener implements View.OnClickListener {
private final @NonNull AudioSlide slide;
private DownloadClickedListener(@NonNull AudioSlide slide) {
this.slide = slide;
}
@Override
public void onClick(View v) {
if (downloadListener != null) downloadListener.onClick(v, slide);
}
}
private class SeekBarModifiedListener implements SeekBar.OnSeekBarChangeListener {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {}
@Override
public synchronized void onStartTrackingTouch(SeekBar seekBar) {
if (audioSlidePlayer != null && pauseButton.getVisibility() == View.VISIBLE) {
audioSlidePlayer.stop();
}
}
@Override
public synchronized void onStopTrackingTouch(SeekBar seekBar) {
try {
if (audioSlidePlayer != null && pauseButton.getVisibility() == View.VISIBLE) {
audioSlidePlayer.play(getProgress());
}
} catch (IOException e) {
Log.w(TAG, e);
}
}
}
private static class TouchIgnoringListener implements OnTouchListener {
@Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEventAsync(final PartProgressEvent event) {
if (audioSlidePlayer != null && event.attachment.equals(audioSlidePlayer.getAudioSlide().asAttachment())) {
downloadProgress.setInstantProgress(((float) event.progress) / event.total);
}
}
}

@ -0,0 +1,267 @@
package org.thoughtcrime.securesms.components
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.PorterDuff
import android.graphics.drawable.AnimatedVectorDrawable
import android.os.Build
import android.util.AttributeSet
import android.view.View
import android.view.View.OnTouchListener
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.SeekBar
import android.widget.SeekBar.OnSeekBarChangeListener
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.graphics.BlendModeColorFilterCompat.createBlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat
import com.pnikosis.materialishprogress.ProgressWheel
import network.loki.messenger.R
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.thoughtcrime.securesms.audio.AudioSlidePlayer
import org.thoughtcrime.securesms.database.AttachmentDatabase
import org.thoughtcrime.securesms.events.PartProgressEvent
import org.thoughtcrime.securesms.logging.Log
import org.thoughtcrime.securesms.mms.AudioSlide
import org.thoughtcrime.securesms.mms.SlideClickListener
import java.io.IOException
import java.util.concurrent.TimeUnit
import kotlin.math.floor
class AudioView: FrameLayout, AudioSlidePlayer.Listener {
companion object {
private const val TAG = "AudioViewKt"
}
private val controlToggle: AnimatingToggle
private val container: ViewGroup
private val playButton: ImageView
private val pauseButton: ImageView
private val downloadButton: ImageView
private val downloadProgress: ProgressWheel
private val seekBar: SeekBar
private val timestamp: TextView
private var downloadListener: SlideClickListener? = null
private var audioSlidePlayer: AudioSlidePlayer? = null
private var backwardsCounter = 0
constructor(context: Context): this(context, null)
constructor(context: Context, attrs: AttributeSet?): this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int): super(context, attrs, defStyleAttr) {
View.inflate(context, R.layout.audio_view, this)
container = findViewById<View>(R.id.audio_widget_container) as ViewGroup
controlToggle = findViewById<View>(R.id.control_toggle) as AnimatingToggle
playButton = findViewById<View>(R.id.play) as ImageView
pauseButton = findViewById<View>(R.id.pause) as ImageView
downloadButton = findViewById<View>(R.id.download) as ImageView
downloadProgress = findViewById<View>(R.id.download_progress) as ProgressWheel
seekBar = findViewById<View>(R.id.seek) as SeekBar
timestamp = findViewById<View>(R.id.timestamp) as TextView
playButton.setOnClickListener {
try {
Log.d(TAG, "playbutton onClick")
if (audioSlidePlayer != null) {
togglePlayToPause()
audioSlidePlayer!!.play(getProgress())
}
} catch (e: IOException) {
Log.w(TAG, e)
}
}
pauseButton.setOnClickListener {
Log.d(TAG, "pausebutton onClick")
if (audioSlidePlayer != null) {
togglePauseToPlay()
audioSlidePlayer!!.stop()
}
}
seekBar.setOnSeekBarChangeListener(SeekBarModifiedListener())
playButton.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.play_icon))
pauseButton.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.pause_icon))
playButton.background = ContextCompat.getDrawable(context, R.drawable.ic_circle_fill_white_48dp)
pauseButton.background = ContextCompat.getDrawable(context, R.drawable.ic_circle_fill_white_48dp)
if (attrs != null) {
val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.AudioView, 0, 0)
setTint(typedArray.getColor(R.styleable.AudioView_foregroundTintColor, Color.WHITE),
typedArray.getColor(R.styleable.AudioView_backgroundTintColor, Color.WHITE))
container.setBackgroundColor(typedArray.getColor(R.styleable.AudioView_widgetBackground, Color.TRANSPARENT))
typedArray.recycle()
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
if (!EventBus.getDefault().isRegistered(this)) EventBus.getDefault().register(this)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
EventBus.getDefault().unregister(this)
}
fun setAudio(audio: AudioSlide, showControls: Boolean) {
if (showControls && audio.isPendingDownload) {
controlToggle.displayQuick(downloadButton)
seekBar.isEnabled = false
downloadButton.setOnClickListener { v -> downloadListener?.onClick(v, audio) }
if (downloadProgress.isSpinning) {
downloadProgress.stopSpinning()
}
} else if (showControls && audio.transferState == AttachmentDatabase.TRANSFER_PROGRESS_STARTED) {
controlToggle.displayQuick(downloadProgress)
seekBar.isEnabled = false
downloadProgress.spin()
} else {
controlToggle.displayQuick(playButton)
seekBar.isEnabled = true
if (downloadProgress.isSpinning) {
downloadProgress.stopSpinning()
}
}
audioSlidePlayer = AudioSlidePlayer.createFor(context, audio, this)
}
fun cleanup() {
if (audioSlidePlayer != null && pauseButton.visibility == View.VISIBLE) {
audioSlidePlayer!!.stop()
}
}
fun setDownloadClickListener(listener: SlideClickListener?) {
downloadListener = listener
}
fun setTint(foregroundTint: Int, backgroundTint: Int) {
playButton.backgroundTintList = ColorStateList.valueOf(foregroundTint)
playButton.imageTintList = ColorStateList.valueOf(backgroundTint)
pauseButton.backgroundTintList = ColorStateList.valueOf(foregroundTint)
pauseButton.imageTintList = ColorStateList.valueOf(backgroundTint)
downloadButton.setColorFilter(foregroundTint, PorterDuff.Mode.SRC_IN)
downloadProgress.barColor = foregroundTint
timestamp.setTextColor(foregroundTint)
val colorFilter = createBlendModeColorFilterCompat(foregroundTint, BlendModeCompat.SRC_IN)
seekBar.progressDrawable.colorFilter = colorFilter
seekBar.thumb.colorFilter = colorFilter
}
override fun onStart() {
if (pauseButton.visibility != View.VISIBLE) {
togglePlayToPause()
}
}
override fun onStop() {
if (playButton.visibility != View.VISIBLE) {
togglePauseToPlay()
}
if (seekBar.progress + 5 >= seekBar.max) {
backwardsCounter = 4
onProgress(0.0, 0)
}
}
override fun setFocusable(focusable: Boolean) {
super.setFocusable(focusable)
playButton.isFocusable = focusable
pauseButton.isFocusable = focusable
seekBar.isFocusable = focusable
seekBar.isFocusableInTouchMode = focusable
downloadButton.isFocusable = focusable
}
override fun setClickable(clickable: Boolean) {
super.setClickable(clickable)
playButton.isClickable = clickable
pauseButton.isClickable = clickable
seekBar.isClickable = clickable
seekBar.setOnTouchListener(if (clickable) null else
OnTouchListener { _, _ -> return@OnTouchListener true }) // Suppress touch events.
downloadButton.isClickable = clickable
}
override fun setEnabled(enabled: Boolean) {
super.setEnabled(enabled)
playButton.isEnabled = enabled
pauseButton.isEnabled = enabled
seekBar.isEnabled = enabled
downloadButton.isEnabled = enabled
}
override fun onProgress(progress: Double, millis: Long) {
val seekProgress = floor(progress * seekBar.max).toInt()
if (seekProgress > seekBar.progress || backwardsCounter > 3) {
backwardsCounter = 0
seekBar.progress = seekProgress
timestamp.text = String.format("%02d:%02d",
TimeUnit.MILLISECONDS.toMinutes(millis),
TimeUnit.MILLISECONDS.toSeconds(millis))
} else {
backwardsCounter++
}
}
private fun getProgress(): Double {
return if (seekBar.progress <= 0 || seekBar.max <= 0) {
0.0
} else {
seekBar.progress.toDouble() / seekBar.max.toDouble()
}
}
private fun togglePlayToPause() {
controlToggle.displayQuick(pauseButton)
val playToPauseDrawable = ContextCompat.getDrawable(context, R.drawable.play_to_pause_animation) as AnimatedVectorDrawable
pauseButton.setImageDrawable(playToPauseDrawable)
playToPauseDrawable.start()
}
private fun togglePauseToPlay() {
controlToggle.displayQuick(playButton)
val pauseToPlayDrawable = ContextCompat.getDrawable(context, R.drawable.pause_to_play_animation) as AnimatedVectorDrawable
playButton.setImageDrawable(pauseToPlayDrawable)
pauseToPlayDrawable.start()
}
private inner class SeekBarModifiedListener : OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {}
@Synchronized
override fun onStartTrackingTouch(seekBar: SeekBar) {
if (audioSlidePlayer != null && pauseButton.visibility == View.VISIBLE) {
audioSlidePlayer!!.stop()
}
}
@Synchronized
override fun onStopTrackingTouch(seekBar: SeekBar) {
try {
if (audioSlidePlayer != null && pauseButton.visibility == View.VISIBLE) {
audioSlidePlayer!!.play(getProgress())
}
} catch (e: IOException) {
Log.w(TAG, e)
}
}
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
fun onEventAsync(event: PartProgressEvent) {
if (audioSlidePlayer != null && event.attachment == audioSlidePlayer!!.audioSlide.asAttachment()) {
downloadProgress.setInstantProgress(event.progress.toFloat() / event.total)
}
}
}
Loading…
Cancel
Save