From 20b14091d628bd2dcbd0161b0387f644104843f8 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 20 Sep 2017 15:35:35 +0200 Subject: [PATCH] play audio files inline --- .../ui/ConversationFragment.java | 4 + .../ui/adapter/MessageAdapter.java | 69 +++-- .../conversations/ui/service/AudioPlayer.java | 280 ++++++++++++++++++ .../conversations/utils/WeakReferenceSet.java | 26 ++ .../res/drawable-hdpi/ic_pause_black_36dp.png | Bin 0 -> 123 bytes .../res/drawable-hdpi/ic_pause_white_36dp.png | Bin 0 -> 124 bytes .../ic_play_arrow_black_36dp.png | Bin 0 -> 236 bytes .../ic_play_arrow_white_36dp.png | Bin 0 -> 242 bytes .../res/drawable-mdpi/ic_pause_black_36dp.png | Bin 0 -> 102 bytes .../res/drawable-mdpi/ic_pause_white_36dp.png | Bin 0 -> 105 bytes .../ic_play_arrow_black_36dp.png | Bin 0 -> 194 bytes .../ic_play_arrow_white_36dp.png | Bin 0 -> 195 bytes .../drawable-xhdpi/ic_pause_black_36dp.png | Bin 0 -> 109 bytes .../drawable-xhdpi/ic_pause_white_36dp.png | Bin 0 -> 92 bytes .../ic_play_arrow_black_36dp.png | Bin 0 -> 265 bytes .../ic_play_arrow_white_36dp.png | Bin 0 -> 283 bytes .../drawable-xxhdpi/ic_pause_black_36dp.png | Bin 0 -> 143 bytes .../drawable-xxhdpi/ic_pause_white_36dp.png | Bin 0 -> 158 bytes .../ic_play_arrow_black_36dp.png | Bin 0 -> 366 bytes .../ic_play_arrow_white_36dp.png | Bin 0 -> 390 bytes .../drawable-xxxhdpi/ic_pause_black_36dp.png | Bin 0 -> 127 bytes .../drawable-xxxhdpi/ic_pause_white_36dp.png | Bin 0 -> 110 bytes .../ic_play_arrow_black_36dp.png | Bin 0 -> 394 bytes .../ic_play_arrow_white_36dp.png | Bin 0 -> 461 bytes src/main/res/layout/message_content.xml | 37 +++ src/main/res/values/ids.xml | 1 + 26 files changed, 395 insertions(+), 22 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/ui/service/AudioPlayer.java create mode 100644 src/main/java/eu/siacs/conversations/utils/WeakReferenceSet.java create mode 100644 src/main/res/drawable-hdpi/ic_pause_black_36dp.png create mode 100644 src/main/res/drawable-hdpi/ic_pause_white_36dp.png create mode 100644 src/main/res/drawable-hdpi/ic_play_arrow_black_36dp.png create mode 100644 src/main/res/drawable-hdpi/ic_play_arrow_white_36dp.png create mode 100644 src/main/res/drawable-mdpi/ic_pause_black_36dp.png create mode 100644 src/main/res/drawable-mdpi/ic_pause_white_36dp.png create mode 100644 src/main/res/drawable-mdpi/ic_play_arrow_black_36dp.png create mode 100644 src/main/res/drawable-mdpi/ic_play_arrow_white_36dp.png create mode 100644 src/main/res/drawable-xhdpi/ic_pause_black_36dp.png create mode 100644 src/main/res/drawable-xhdpi/ic_pause_white_36dp.png create mode 100644 src/main/res/drawable-xhdpi/ic_play_arrow_black_36dp.png create mode 100644 src/main/res/drawable-xhdpi/ic_play_arrow_white_36dp.png create mode 100644 src/main/res/drawable-xxhdpi/ic_pause_black_36dp.png create mode 100644 src/main/res/drawable-xxhdpi/ic_pause_white_36dp.png create mode 100644 src/main/res/drawable-xxhdpi/ic_play_arrow_black_36dp.png create mode 100644 src/main/res/drawable-xxhdpi/ic_play_arrow_white_36dp.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_pause_black_36dp.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_pause_white_36dp.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_play_arrow_black_36dp.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_play_arrow_white_36dp.png diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index d0af53c3b..170689cd7 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -868,6 +868,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa @Override public void onStop() { super.onStop(); + if (activity == null || !activity.isChangingConfigurations()) { + messageListAdapter.stopAudioPlayer(); + } if (this.conversation != null) { final String msg = mEditMessage.getText().toString(); this.conversation.setNextMessage(msg); @@ -894,6 +897,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa this.conversation.setNextMessage(msg); if (this.conversation != conversation) { updateChatState(this.conversation, msg); + messageListAdapter.stopAudioPlayer(); } this.conversation.trim(); diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index be6204a20..179d3aa23 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -33,6 +33,7 @@ import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; @@ -59,6 +60,7 @@ import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.services.MessageArchiveService; import eu.siacs.conversations.services.NotificationService; import eu.siacs.conversations.ui.ConversationActivity; +import eu.siacs.conversations.ui.service.AudioPlayer; import eu.siacs.conversations.ui.text.DividerSpan; import eu.siacs.conversations.ui.text.QuoteSpan; import eu.siacs.conversations.ui.widget.ClickableMovementMethod; @@ -120,8 +122,11 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie private final ListSelectionManager listSelectionManager = new ListSelectionManager(); + private final AudioPlayer audioPlayer; + public MessageAdapter(ConversationActivity activity, List messages) { super(activity, 0, messages); + this.audioPlayer = new AudioPlayer(this); this.activity = activity; metrics = getContext().getResources().getDisplayMetrics(); updatePreferences(); @@ -164,7 +169,7 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie return this.getItemViewType(getItem(position)); } - private int getMessageTextColor(boolean onDark, boolean primary) { + public int getMessageTextColor(boolean onDark, boolean primary) { if (onDark) { return ContextCompat.getColor(activity, primary ? R.color.white : R.color.white70); } else { @@ -299,9 +304,8 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie } private void displayInfoMessage(ViewHolder viewHolder, String text, boolean darkBackground) { - if (viewHolder.download_button != null) { - viewHolder.download_button.setVisibility(View.GONE); - } + viewHolder.download_button.setVisibility(View.GONE); + viewHolder.audioPlayer.setVisibility(View.GONE); viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.VISIBLE); viewHolder.messageBody.setText(text); @@ -311,10 +315,9 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie } private void displayDecryptionFailed(ViewHolder viewHolder, boolean darkBackground) { - if (viewHolder.download_button != null) { - viewHolder.download_button.setVisibility(View.GONE); - } + viewHolder.download_button.setVisibility(View.GONE); viewHolder.image.setVisibility(View.GONE); + viewHolder.audioPlayer.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.VISIBLE); viewHolder.messageBody.setText(getContext().getString( R.string.decryption_failed)); @@ -324,9 +327,8 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie } private void displayEmojiMessage(final ViewHolder viewHolder, final String body) { - if (viewHolder.download_button != null) { - viewHolder.download_button.setVisibility(View.GONE); - } + viewHolder.download_button.setVisibility(View.GONE); + viewHolder.audioPlayer.setVisibility(View.GONE); viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.VISIBLE); viewHolder.messageBody.setIncludeFontPadding(false); @@ -406,10 +408,9 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie } private void displayTextMessage(final ViewHolder viewHolder, final Message message, boolean darkBackground, int type) { - if (viewHolder.download_button != null) { - viewHolder.download_button.setVisibility(View.GONE); - } + viewHolder.download_button.setVisibility(View.GONE); viewHolder.image.setVisibility(View.GONE); + viewHolder.audioPlayer.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.VISIBLE); viewHolder.messageBody.setIncludeFontPadding(true); if (message.getBody() != null) { @@ -492,10 +493,10 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); } - private void displayDownloadableMessage(ViewHolder viewHolder, - final Message message, String text) { + private void displayDownloadableMessage(ViewHolder viewHolder, final Message message, String text) { viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.GONE); + viewHolder.audioPlayer.setVisibility(View.GONE); viewHolder.download_button.setVisibility(View.VISIBLE); viewHolder.download_button.setText(text); viewHolder.download_button.setOnClickListener(new OnClickListener() { @@ -510,6 +511,7 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie private void displayOpenableMessage(ViewHolder viewHolder,final Message message) { viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.GONE); + viewHolder.audioPlayer.setVisibility(View.GONE); viewHolder.download_button.setVisibility(View.VISIBLE); viewHolder.download_button.setText(activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity, message))); viewHolder.download_button.setOnClickListener(new OnClickListener() { @@ -524,6 +526,7 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie private void displayLocationMessage(ViewHolder viewHolder, final Message message) { viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.GONE); + viewHolder.audioPlayer.setVisibility(View.GONE); viewHolder.download_button.setVisibility(View.VISIBLE); viewHolder.download_button.setText(R.string.show_location); viewHolder.download_button.setOnClickListener(new OnClickListener() { @@ -535,12 +538,21 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie }); } - private void displayImageMessage(ViewHolder viewHolder, - final Message message) { - if (viewHolder.download_button != null) { - viewHolder.download_button.setVisibility(View.GONE); - } + private void displayAudioMessage(ViewHolder viewHolder, Message message, boolean darkBackground) { + viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.GONE); + viewHolder.download_button.setVisibility(View.GONE); + final RelativeLayout audioPlayer = viewHolder.audioPlayer; + audioPlayer.setVisibility(View.VISIBLE); + AudioPlayer.ViewHolder.get(audioPlayer).setDarkBackground(darkBackground); + this.audioPlayer.init(audioPlayer, message); + } + + + private void displayImageMessage(ViewHolder viewHolder, final Message message) { + viewHolder.download_button.setVisibility(View.GONE); + viewHolder.messageBody.setVisibility(View.GONE); + viewHolder.audioPlayer.setVisibility(View.GONE); viewHolder.image.setVisibility(View.VISIBLE); FileParams params = message.getFileParams(); double target = metrics.density * 288; @@ -627,6 +639,7 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie .findViewById(R.id.message_time); viewHolder.indicatorReceived = (ImageView) view .findViewById(R.id.indicator_received); + viewHolder.audioPlayer = (RelativeLayout) view.findViewById(R.id.audio_player); break; case RECEIVED: view = activity.getLayoutInflater().inflate( @@ -649,6 +662,7 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie viewHolder.indicatorReceived = (ImageView) view .findViewById(R.id.indicator_received); viewHolder.encryption = (TextView) view.findViewById(R.id.message_encryption); + viewHolder.audioPlayer = (RelativeLayout) view.findViewById(R.id.audio_player); break; case STATUS: view = activity.getLayoutInflater().inflate(R.layout.message_status, parent, false); @@ -762,8 +776,10 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie } else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) { displayImageMessage(viewHolder, message); } else if (message.getType() == Message.TYPE_FILE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) { - if (message.getFileParams().width > 0) { - displayImageMessage(viewHolder,message); + if (message.getFileParams().width > 0 && message.getFileParams().height > 0) { + displayImageMessage(viewHolder, message); + } else if (message.getFileParams().runtime > 0) { + displayAudioMessage(viewHolder, message, darkBackground); } else { displayOpenableMessage(viewHolder, message); } @@ -877,6 +893,14 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie } } + public FileBackend getFileBackend() { + return activity.xmppConnectionService.getFileBackend(); + } + + public void stopAudioPlayer() { + audioPlayer.stop(); + } + public interface OnQuoteListener { public void onQuote(String text); } @@ -1004,6 +1028,7 @@ public class MessageAdapter extends ArrayAdapter implements CopyTextVie protected TextView encryption; public Button load_more_messages; public ImageView edit_indicator; + public RelativeLayout audioPlayer; } class BitmapWorkerTask extends AsyncTask { diff --git a/src/main/java/eu/siacs/conversations/ui/service/AudioPlayer.java b/src/main/java/eu/siacs/conversations/ui/service/AudioPlayer.java new file mode 100644 index 000000000..1b3d6747d --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/service/AudioPlayer.java @@ -0,0 +1,280 @@ +package eu.siacs.conversations.ui.service; + +import android.content.res.ColorStateList; +import android.media.MediaPlayer; +import android.os.Build; +import android.os.Handler; +import android.support.v4.content.ContextCompat; +import android.view.View; +import android.widget.ImageButton; +import android.widget.RelativeLayout; +import android.widget.SeekBar; +import android.widget.TextView; + +import java.lang.ref.WeakReference; +import java.util.Locale; + +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.ui.adapter.MessageAdapter; +import eu.siacs.conversations.utils.WeakReferenceSet; + +public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompletionListener, SeekBar.OnSeekBarChangeListener, Runnable { + + private static final int REFRESH_INTERVAL = 250; + private static final Object LOCK = new Object(); + private static MediaPlayer player = null; + private static Message currentlyPlayingMessage = null; + private final MessageAdapter messageAdapter; + private final WeakReferenceSet audioPlayerLayouts = new WeakReferenceSet<>(); + + private final Handler handler = new Handler(); + + public AudioPlayer(MessageAdapter adapter) { + this.messageAdapter = adapter; + synchronized (AudioPlayer.LOCK) { + if (AudioPlayer.player != null) { + AudioPlayer.player.setOnCompletionListener(this); + } + } + } + + private static String formatTime(int ms) { + return String.format(Locale.ENGLISH, "%d:%02d", ms / 60000, Math.min(Math.round((ms % 60000) / 1000f), 59)); + } + + public void init(RelativeLayout audioPlayer, Message message) { + synchronized (AudioPlayer.LOCK) { + audioPlayer.setTag(message); + if (init(ViewHolder.get(audioPlayer), message)) { + this.audioPlayerLayouts.addWeakReferenceTo(audioPlayer); + this.stopRefresher(true); + } else { + this.audioPlayerLayouts.removeWeakReferenceTo(audioPlayer); + } + } + } + + private boolean init(ViewHolder viewHolder, Message message) { + viewHolder.runtime.setTextColor(this.messageAdapter.getMessageTextColor(viewHolder.darkBackground, false)); + viewHolder.progress.setOnSeekBarChangeListener(this); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + ColorStateList color = ContextCompat.getColorStateList(messageAdapter.getContext(), viewHolder.darkBackground ? R.color.white70 : R.color.bubble); + viewHolder.progress.setThumbTintList(color); + viewHolder.progress.setProgressTintList(color); + } + viewHolder.playPause.setAlpha(viewHolder.darkBackground ? 0.7f : 0.57f); + viewHolder.playPause.setOnClickListener(this); + if (message == currentlyPlayingMessage) { + if (AudioPlayer.player != null && AudioPlayer.player.isPlaying()) { + viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_pause_white_36dp : R.drawable.ic_pause_black_36dp); + viewHolder.progress.setEnabled(true); + } else { + viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp); + viewHolder.progress.setEnabled(false); + } + return true; + } else { + viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp); + viewHolder.runtime.setText(formatTime(message.getFileParams().runtime)); + viewHolder.progress.setProgress(0); + viewHolder.progress.setEnabled(false); + return false; + } + } + + @Override + public synchronized void onClick(View v) { + if (v.getId() == R.id.play_pause) { + synchronized (LOCK) { + startStop((ImageButton) v); + } + } + } + + private void startStop(ImageButton playPause) { + final RelativeLayout audioPlayer = (RelativeLayout) playPause.getParent(); + final ViewHolder viewHolder = ViewHolder.get(audioPlayer); + final Message message = (Message) audioPlayer.getTag(); + if (startStop(viewHolder, message)) { + this.audioPlayerLayouts.clear(); + this.audioPlayerLayouts.addWeakReferenceTo(audioPlayer); + stopRefresher(true); + } + } + + private boolean playPauseCurrent(ViewHolder viewHolder) { + viewHolder.playPause.setAlpha(viewHolder.darkBackground ? 0.7f : 0.57f); + if (player.isPlaying()) { + viewHolder.progress.setEnabled(false); + player.pause(); + viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp); + } else { + viewHolder.progress.setEnabled(true); + player.start(); + this.stopRefresher(true); + viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_pause_white_36dp : R.drawable.ic_pause_black_36dp); + } + return false; + } + + private boolean play(ViewHolder viewHolder, Message message) { + AudioPlayer.player = new MediaPlayer(); + try { + AudioPlayer.currentlyPlayingMessage = message; + AudioPlayer.player.setDataSource(messageAdapter.getFileBackend().getFile(message).getAbsolutePath()); + AudioPlayer.player.setOnCompletionListener(this); + AudioPlayer.player.prepare(); + AudioPlayer.player.start(); + viewHolder.progress.setEnabled(true); + viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_pause_white_36dp : R.drawable.ic_pause_black_36dp); + return true; + } catch (Exception e) { + AudioPlayer.currentlyPlayingMessage = null; + return false; + } + } + + private boolean startStop(ViewHolder viewHolder, Message message) { + if (message == currentlyPlayingMessage && player != null) { + return playPauseCurrent(viewHolder); + } + if (AudioPlayer.player != null) { + stopCurrent(); + } + return play(viewHolder, message); + } + + private void stopCurrent() { + if (AudioPlayer.player.isPlaying()) { + AudioPlayer.player.stop(); + } + AudioPlayer.player.release(); + AudioPlayer.player = null; + resetPlayerUi(); + } + + private void resetPlayerUi() { + for (WeakReference audioPlayer : audioPlayerLayouts) { + resetPlayerUi(audioPlayer.get()); + } + } + + private void resetPlayerUi(RelativeLayout audioPlayer) { + if (audioPlayer == null) { + return; + } + final ViewHolder viewHolder = ViewHolder.get(audioPlayer); + final Message message = (Message) audioPlayer.getTag(); + viewHolder.playPause.setImageResource(viewHolder.darkBackground ? R.drawable.ic_play_arrow_white_36dp : R.drawable.ic_play_arrow_black_36dp); + if (message != null) { + viewHolder.runtime.setText(formatTime(message.getFileParams().runtime)); + } + viewHolder.progress.setProgress(0); + viewHolder.progress.setEnabled(false); + } + + @Override + public void onCompletion(MediaPlayer mediaPlayer) { + synchronized (AudioPlayer.LOCK) { + this.stopRefresher(false); + if (AudioPlayer.player == mediaPlayer) { + AudioPlayer.currentlyPlayingMessage = null; + AudioPlayer.player = null; + } + mediaPlayer.release(); + resetPlayerUi(); + } + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + synchronized (AudioPlayer.LOCK) { + final RelativeLayout audioPlayer = (RelativeLayout) seekBar.getParent(); + final Message message = (Message) audioPlayer.getTag(); + if (fromUser && message == AudioPlayer.currentlyPlayingMessage) { + float percent = progress / 100f; + int duration = AudioPlayer.player.getDuration(); + int seekTo = Math.round(duration * percent); + AudioPlayer.player.seekTo(seekTo); + } + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + + public void stop() { + synchronized (AudioPlayer.LOCK) { + stopRefresher(false); + if (AudioPlayer.player != null) { + stopCurrent(); + } + AudioPlayer.currentlyPlayingMessage = null; + } + } + + private void stopRefresher(boolean runOnceMore) { + this.handler.removeCallbacks(this); + if (runOnceMore) { + this.handler.post(this); + } + } + + @Override + public void run() { + synchronized (AudioPlayer.LOCK) { + if (AudioPlayer.player != null) { + boolean renew = false; + final int current = player.getCurrentPosition(); + final int duration = player.getDuration(); + for (WeakReference audioPlayer : audioPlayerLayouts) { + renew |= refreshAudioPlayer(audioPlayer.get(), current, duration); + } + if (renew && AudioPlayer.player.isPlaying()) { + handler.postDelayed(this, REFRESH_INTERVAL); + } + } + } + } + + private boolean refreshAudioPlayer(RelativeLayout audioPlayer, int current, int duration) { + if (audioPlayer == null || audioPlayer.getVisibility() != View.VISIBLE) { + return false; + } + final ViewHolder viewHolder = ViewHolder.get(audioPlayer); + viewHolder.progress.setProgress(current * 100 / duration); + viewHolder.runtime.setText(formatTime(current) + " / " + formatTime(duration)); + return true; + } + + public static class ViewHolder { + private TextView runtime; + private SeekBar progress; + private ImageButton playPause; + private boolean darkBackground = false; + + public static ViewHolder get(RelativeLayout audioPlayer) { + ViewHolder viewHolder = (ViewHolder) audioPlayer.getTag(R.id.TAG_AUDIO_PLAYER_VIEW_HOLDER); + if (viewHolder == null) { + viewHolder = new ViewHolder(); + viewHolder.runtime = (TextView) audioPlayer.findViewById(R.id.runtime); + viewHolder.progress = (SeekBar) audioPlayer.findViewById(R.id.progress); + viewHolder.playPause = (ImageButton) audioPlayer.findViewById(R.id.play_pause); + audioPlayer.setTag(R.id.TAG_AUDIO_PLAYER_VIEW_HOLDER, viewHolder); + } + return viewHolder; + } + + public void setDarkBackground(boolean darkBackground) { + this.darkBackground = darkBackground; + } + } +} diff --git a/src/main/java/eu/siacs/conversations/utils/WeakReferenceSet.java b/src/main/java/eu/siacs/conversations/utils/WeakReferenceSet.java new file mode 100644 index 000000000..10de219db --- /dev/null +++ b/src/main/java/eu/siacs/conversations/utils/WeakReferenceSet.java @@ -0,0 +1,26 @@ +package eu.siacs.conversations.utils; + +import java.lang.ref.WeakReference; +import java.util.HashSet; +import java.util.Iterator; + +public class WeakReferenceSet extends HashSet> { + + public void removeWeakReferenceTo(T reference) { + for (Iterator> iterator = iterator(); iterator.hasNext(); ) { + if (reference == iterator.next().get()) { + iterator.remove(); + } + } + } + + + public void addWeakReferenceTo(T reference) { + for (WeakReference weakReference : this) { + if (reference == weakReference.get()) { + return; + } + } + this.add(new WeakReference<>(reference)); + } +} diff --git a/src/main/res/drawable-hdpi/ic_pause_black_36dp.png b/src/main/res/drawable-hdpi/ic_pause_black_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..b1425292c073fd1928e581aa97b8279c5a607f28 GIT binary patch literal 123 zcmeAS@N?(olHy`uVBq!ia0vp^W+2SL0wmRZ7KH&RCr=m0kch)?uW#gJP~dSr*z-z5 z^6c3y{;HZT5AWqOhpRhH+LwPsM|$n-#YSO~+wu|&BzV}G4+5z-d}Uu#CoO5eW&GFF U{^n-IZlGxlp00i_>zopr07Td*RR910 literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-hdpi/ic_pause_white_36dp.png b/src/main/res/drawable-hdpi/ic_pause_white_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..1d024393aad17639ee338941a2ccf278129f45eb GIT binary patch literal 124 zcmeAS@N?(olHy`uVBq!ia0vp^W+2SL0wmRZ7KH&RXHOT$kch)?uW#gJP~dSr*fT3o z=Iq&|d{s@ChjF*~!_}Q8)ve$3tnB8^If;wExur8VA51Wi;9&z&(>Ki4pRBU?3e%%g Wo9=4Ww>1KdWAJqKb6Mw<&;$U+sVfHn literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-hdpi/ic_play_arrow_black_36dp.png b/src/main/res/drawable-hdpi/ic_play_arrow_black_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..67d0c8209a62ec32b1e3610b02b771e05d424dab GIT binary patch literal 236 zcmV2gNZ|ul&tti{thL>`dk@e^5~rMJCQ# z2_y8>mXn3vx@w?g-T9IEdfN!a*=H2Z6{O1SE41kjz0K zG6w<490VeB5ZPplPSj_-Qv;cGpr(JLqMo#4ey(1+Xh`+Mc;tZ0BkxVek+J(02M_98mgdsH#&SQH!_eAzTxG6Z*B5Rn!Cd5DwRs5 zp>SqJ_xt(oF?h1|Fj%xK`-w@(4Q#Of7Nq0?mXZrt sO8$Q=C2#6tAzzt3pTY_&tT6Gs0j^tn*T5_tfdBvi07*qoM6N<$g0Q$?p8x;= literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-mdpi/ic_pause_black_36dp.png b/src/main/res/drawable-mdpi/ic_pause_black_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..3539b4ef1f0ea5dff36c9521d9fbed0fa0600d6c GIT binary patch literal 102 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K;uBVG*NCjiE1MA`h5fcRkyTU^c x?3o@3F5mCCn5|dJB|k0wldT1B8K;fv1aOh{y5d1PRu~43dBH9UPL5 z6iT|k0wldT1B8K8r>Bc!NCji^0XDaT8U~A|Io3<; zlH9Q(@j|`Ap5qU^Seq9V7&ddXvK@Sw-4@}+>vUi)PuCR5mWFsqFNqnN#vF|2Cr#G) zm)v~8#c`+O2N2QW;walJ7I=8UZk}~lINs>7>1Da>S2Pt$F=YO%a8hZ#WYUU>nc{&F pJqMI$1u=AH{1W@OI6;Jg!9HnCPgkS=OQ53|JYD@<);T3K0RXaJLVW-L literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-mdpi/ic_play_arrow_white_36dp.png b/src/main/res/drawable-mdpi/ic_play_arrow_white_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..57c9fa5460323823edb0289c1d15f0f561e0c06e GIT binary patch literal 195 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K8m#2$kh{y4_Q_k`_IEb*R=ADxY zV6uJDq_v_+Z-tYfR|K!cZcn#wZ_1w*Zwfx3^1JLFH&cH)|4gqfmxMH3rgg?BZ~AgX zO7P4Ssfnoy7dsLbs)cmuJnq)8IA+UruCTi)d!Bot@+%Rg#TB=_7E65dSjDj^h($AU t;!LNFTjoCMkvN)I{QpIN|NiIvIZ;mvueQI+xBzq&gQu&X%Q~loCIB@QN?HH_ literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xhdpi/ic_pause_black_36dp.png b/src/main/res/drawable-xhdpi/ic_pause_black_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..bb707eab97b3d48167a29bb8c21fc02cf582ffac GIT binary patch literal 109 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xf3?%cF6!jv*C{ m$r5W0{Noqkjk>0hJ%NeAb!~d=tj~okAZ?zmelF{r5}E*xz!xz9 literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xhdpi/ic_play_arrow_black_36dp.png b/src/main/res/drawable-xhdpi/ic_play_arrow_black_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..5345ee3c4a7a3576b7493cfe3b4243d183c5d996 GIT binary patch literal 265 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhawE_k{)hEy=Vona_=h(Y9NocJ*= z2gVEsR;>f9D;UIQtYDYfuzAy#I~!Bp9Eok4p0B{HuiHB}$V+qS6!&d`8=uTAZp(j*(RKzESv+D^YJz!E~ySM++u72Lv s%Lwqa0{zC|>FVdQ&MBb@0JEE2djJ3c literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xhdpi/ic_play_arrow_white_36dp.png b/src/main/res/drawable-xhdpi/ic_play_arrow_white_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..547ef30aacdebbd5bc27a3831971aa49be8813f7 GIT binary patch literal 283 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhaw9(lSrhEy=Vy|FR(PyjfM61bK%=q7O8Xj0w7EPdhfr5^#S_IEwe==vgFzHZ{arrqiL9p^2}oCQR8HlO`a zfBt>_q&-EaGMH-9&z)oSy0%IOibq!5=i7xQ8?znl*Ho#q?l1C zmLwh%pdb>pKr}hv!ldkF|5nzo_`k zc}eR_hl%VF4-<)DT=alRfHl0KMErSu6_?hE1x;YW&OCE+==ML_XMw(D@O1TaS?83{ F1OO`naDo5; literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xxhdpi/ic_pause_black_36dp.png b/src/main/res/drawable-xxhdpi/ic_pause_black_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..bfd01502f99be0eeaa986797322e05df361e3d46 GIT binary patch literal 143 zcmeAS@N?(olHy`uVBq!ia0vp^IUvl$3?x5s?2iLdoB=)|u0R?Bnv-810J4}$g8YIR z8fI(zNdS4qo-U3d6^zLX&f0ox;wU*zp+3S?kd XG^KBk?=#0$AY(mU{an^LB{Ts5G}@0s3~?UnljH%y;dKWn|R=Oh&%(k+^?bl>yT zb?FQLJS*5cJ@zV)=Bu@@~bA<1*&cj(QwY zV~uq!JX_|6Jl{G0e^cxm*4;O_%QlL?Nz%_a`i_4YkiKy=ZbSFp1o7&Fdf%GD-q^K6 wnQ*mRlD=;=hHLZM`2N@IsUSalPFf=WYoTCPmfK88U^p^(y85}Sb4q9e0A*jA-v9sr literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xxhdpi/ic_play_arrow_white_36dp.png b/src/main/res/drawable-xxhdpi/ic_play_arrow_white_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..23bb1ba9f683d095e4b5f491ca27f65f5f26942e GIT binary patch literal 390 zcmeAS@N?(olHy`uVBq!ia0vp^IUvlz0wh)Q=esd5F#3ABIEGX(zP-KCm)TLmCDA+O zh=~K|4)$`!PzTWqtRaqq;Y=n48;zI9$9^et*-%xrdFN97nUQm^Ef4b2Tsj3p*v$Mq zC(P>q_h9?aX4~CvOnkr7BGKLM**R9_-!?ZBo)o`}H7J@peP`jp6K3h^ITDPLJ%Sew zN-)ay2nrmMU{r+gFes25R0%2tRA~T|lZNXnICQUf!}^^PrypO9kh^Uo!Tu}K=)ynk zt9JdHybX@7YYKd$Bmt)MB#z!=zq~Y2WH*P~#?=XMira1DcbTM2iKBWzwOVlM9gt^R z7ANJlu^XtC3!;Tv?ceOH@%|eU4mPoJi|IrlG4{9p6;8^ES$iB9z6_qOelF{r5}E+~ CQIz8V literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xxxhdpi/ic_pause_black_36dp.png b/src/main/res/drawable-xxxhdpi/ic_pause_black_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..dc63538f31b9c96551808271cee9d704697e0b8a GIT binary patch literal 127 zcmeAS@N?(olHy`uVBq!ia0vp^6F``e8A#skDEJMe*aCb)T!AzYEKT2e9!N2k1o;Is zI6S+N2IQ!Fx;TbZFeWR=_cR3_`2YVuJ4a`HMhF6SjNuFVdQ&MBb@0ISd)(EtDd literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_36dp.png b/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..1c57756b001e8603899db40189e0bd55407fa857 GIT binary patch literal 394 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q1xWh(YZ)^zFa~4-4ofl{Z7~H@0_RqYg zGmm4=^jkLWd6)Ok@1euYtTP0@B%S;zO=7AU{CD^|j?_jXg?g7W|RFJPhZfk|cV$(bQ@w)cZXJYD@< J);T3K0RUM-zj6Qo literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_36dp.png b/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..2745c3ab9297e89453fc82f5d3fd2aba163e3b37 GIT binary patch literal 461 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q1xWh(YZ)^zFi!DwaSW+oe0%4hCv%{L+r=G) znT%lzOe0!DGsL4>%MyiO9^|{TyY%sUrH;mw9Rd=LHutq1ECuvCCvX3G-WUyRuAF)L z=AXNp|MkzPod4UBc}q#z?Q^CJ9zU;^|7`KRf$#jkxaVvagf8UG|7;`P@a_D=d(Xr! zoVVQ9VZWfqenB501XPBM2&zX@Ao_^^g!h}1-e=@r3LF1d_$-XXz6+p4;3$0!>08tq4uJx_E zBNzhMWhb2Wg^F?Q^1c@%%<zopr02sN!rT_o{ literal 0 HcmV?d00001 diff --git a/src/main/res/layout/message_content.xml b/src/main/res/layout/message_content.xml index bffac4bd9..206b7202b 100644 --- a/src/main/res/layout/message_content.xml +++ b/src/main/res/layout/message_content.xml @@ -30,4 +30,41 @@ android:layout_height="wrap_content" android:longClickable="true" android:visibility="gone"/> + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/values/ids.xml b/src/main/res/values/ids.xml index 142e4d66a..975b454ce 100644 --- a/src/main/res/values/ids.xml +++ b/src/main/res/values/ids.xml @@ -3,4 +3,5 @@ + \ No newline at end of file