From 555209bec12001152e77d4541b89fe537360497a Mon Sep 17 00:00:00 2001 From: andrew Date: Wed, 1 Nov 2023 14:00:59 +1030 Subject: [PATCH] Fix animation --- .../v2/components/ExpirationTimerView.java | 128 ------------------ .../v2/components/ExpirationTimerView.kt | 46 +++++++ .../v2/messages/ControlMessageView.kt | 30 ++-- .../v2/messages/VisibleMessageView.kt | 18 ++- .../main/res/layout/view_control_message.xml | 10 ++ .../main/res/layout/view_visible_message.xml | 7 + 6 files changed, 87 insertions(+), 152 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/ExpirationTimerView.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/ExpirationTimerView.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/ExpirationTimerView.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/ExpirationTimerView.java deleted file mode 100644 index ddd76626b7..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/ExpirationTimerView.java +++ /dev/null @@ -1,128 +0,0 @@ -package org.thoughtcrime.securesms.conversation.v2.components; - -import android.graphics.drawable.Drawable; -import android.widget.ImageView; - -import androidx.annotation.NonNull; -import androidx.appcompat.content.res.AppCompatResources; - -import org.session.libsession.utilities.Util; -import org.thoughtcrime.securesms.database.model.MessageRecord; - -import java.lang.ref.WeakReference; -import java.util.concurrent.TimeUnit; - -import network.loki.messenger.R; - -public class ExpirationTimerView { - - private final ImageView imageView; - private final Integer iconColor; - private long startedAt; - private long expiresIn; - - private boolean visible = false; - private boolean stopped = true; - - private final int[] frames = new int[]{ R.drawable.timer00, - R.drawable.timer05, - R.drawable.timer10, - R.drawable.timer15, - R.drawable.timer20, - R.drawable.timer25, - R.drawable.timer30, - R.drawable.timer35, - R.drawable.timer40, - R.drawable.timer45, - R.drawable.timer50, - R.drawable.timer55, - R.drawable.timer60 }; - - public ExpirationTimerView(ImageView imageView, Integer iconColor) { - this.imageView = imageView; - this.iconColor = iconColor; - } - - public void setExpirationTime(long startedAt, long expiresIn) { - this.startedAt = startedAt; - this.expiresIn = expiresIn; - setPercentComplete(calculateProgress(this.startedAt, this.expiresIn)); - } - - public void setPercentComplete(float percentage) { - float percentFull = 1 - percentage; - int frame = (int) Math.ceil(percentFull * (frames.length - 1)); - - frame = Math.max(0, Math.min(frame, frames.length - 1)); - - Drawable drawable = AppCompatResources.getDrawable(imageView.getContext(), frames[frame]).mutate(); - if (iconColor != null) drawable.setTint(iconColor); - imageView.setImageDrawable(drawable); - } - - public void startAnimation() { - synchronized (this) { - visible = true; - if (!stopped) return; - else stopped = false; - } - - Util.runOnMainDelayed(new AnimationUpdateRunnable(this), calculateAnimationDelay(this.startedAt, this.expiresIn)); - } - - public void stopAnimation() { - synchronized (this) { - visible = false; - } - } - - private float calculateProgress(long startedAt, long expiresIn) { - long progressed = System.currentTimeMillis() - startedAt; - float percentComplete = (float)progressed / (float)expiresIn; - - return Math.max(0, Math.min(percentComplete, 1)); - } - - private long calculateAnimationDelay(long startedAt, long expiresIn) { - long progressed = System.currentTimeMillis() - startedAt; - long remaining = expiresIn - progressed; - - if (remaining <= 0) { - return 0; - } else if (remaining < TimeUnit.SECONDS.toMillis(30)) { - return 1000; - } else { - return 5000; - } - } - - private static class AnimationUpdateRunnable implements Runnable { - - private final WeakReference expirationTimerViewReference; - - private AnimationUpdateRunnable(@NonNull ExpirationTimerView expirationTimerView) { - this.expirationTimerViewReference = new WeakReference<>(expirationTimerView); - } - - @Override - public void run() { - ExpirationTimerView timerView = expirationTimerViewReference.get(); - if (timerView == null) return; - - long nextUpdate = timerView.calculateAnimationDelay(timerView.startedAt, timerView.expiresIn); - synchronized (timerView) { - if (timerView.visible) { - timerView.setExpirationTime(timerView.startedAt, timerView.expiresIn); - } else { - timerView.stopped = true; - return; - } - if (nextUpdate <= 0) { - timerView.stopped = true; - return; - } - } - Util.runOnMainDelayed(this, nextUpdate); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/ExpirationTimerView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/ExpirationTimerView.kt new file mode 100644 index 0000000000..a1d37b0127 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/ExpirationTimerView.kt @@ -0,0 +1,46 @@ +package org.thoughtcrime.securesms.conversation.v2.components + +import android.content.Context +import android.graphics.drawable.AnimationDrawable +import android.util.AttributeSet +import android.widget.ImageView +import androidx.core.content.ContextCompat +import network.loki.messenger.R +import org.session.libsession.snode.SnodeAPI.nowWithOffset +import kotlin.math.round + +class ExpirationTimerView @JvmOverloads constructor( + context: Context?, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ImageView(context, attrs, defStyleAttr) { + private val frames = intArrayOf( + R.drawable.timer00, + R.drawable.timer05, + R.drawable.timer10, + R.drawable.timer15, + R.drawable.timer20, + R.drawable.timer25, + R.drawable.timer30, + R.drawable.timer35, + R.drawable.timer40, + R.drawable.timer45, + R.drawable.timer50, + R.drawable.timer55, + R.drawable.timer60 + ) + + fun setExpirationTime(startedAt: Long, expiresIn: Long) { + val elapsedTime = nowWithOffset - startedAt + val remainingTime = expiresIn - elapsedTime + val remainingPercent = (remainingTime / expiresIn.toFloat()).coerceIn(0f, 1f) + + val frameCount = round(frames.size * remainingPercent).toInt().coerceIn(1, frames.size) + val frameTime = round(remainingTime / frameCount.toFloat()).toInt() + + AnimationDrawable().apply { + frames.take(frameCount).reversed().forEach { addFrame(ContextCompat.getDrawable(context, it)!!, frameTime) } + isOneShot = true + }.also(::setImageDrawable).apply(AnimationDrawable::start) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt index 994fbd0150..6099b7d49a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt @@ -6,6 +6,8 @@ import android.view.LayoutInflater import android.view.View import android.widget.LinearLayout import androidx.core.content.res.ResourcesCompat +import androidx.core.view.isGone +import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import network.loki.messenger.R import network.loki.messenger.databinding.ViewControlMessageBinding @@ -17,7 +19,6 @@ class ControlMessageView : LinearLayout { private lateinit var binding: ViewControlMessageBinding - // region Lifecycle constructor(context: Context) : super(context) { initialize() } constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() } constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() } @@ -26,27 +27,27 @@ class ControlMessageView : LinearLayout { binding = ViewControlMessageBinding.inflate(LayoutInflater.from(context), this, true) layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT) } - // endregion - // region Updating fun bind(message: MessageRecord, previous: MessageRecord?) { binding.dateBreakTextView.showDateBreak(message, previous) - binding.iconImageView.visibility = View.GONE + binding.iconImageView.isGone = true + binding.expirationTimerView.isGone = true var messageBody: CharSequence = message.getDisplayBody(context) binding.root.contentDescription= null when { message.isExpirationTimerUpdate -> { - ExpirationTimerView(binding.iconImageView, context.getColorFromAttr(android.R.attr.textColorPrimary)).apply { + binding.expirationTimerView.apply { + isVisible = true setExpirationTime(message.expireStarted, message.expiresIn) - startAnimation() } - binding.iconImageView.visibility = View.VISIBLE } message.isMediaSavedNotification -> { - binding.iconImageView.setImageDrawable( - ResourcesCompat.getDrawable(resources, R.drawable.ic_file_download_white_36dp, context.theme) - ) - binding.iconImageView.visibility = View.VISIBLE + binding.iconImageView.apply { + setImageDrawable( + ResourcesCompat.getDrawable(resources, R.drawable.ic_file_download_white_36dp, context.theme) + ) + isVisible = true + } } message.isMessageRequestResponse -> { messageBody = context.getString(R.string.message_requests_accepted) @@ -59,8 +60,10 @@ class ControlMessageView : LinearLayout { message.isFirstMissedCall -> R.drawable.ic_info_outline_light else -> R.drawable.ic_missed_call } - binding.iconImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, drawable, context.theme)) - binding.iconImageView.visibility = View.VISIBLE + binding.iconImageView.apply { + setImageDrawable(ResourcesCompat.getDrawable(resources, drawable, context.theme)) + isVisible = true + } } } @@ -70,5 +73,4 @@ class ControlMessageView : LinearLayout { fun recycle() { } - // endregion } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index 8ffad98288..db437431c7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -29,7 +29,6 @@ import network.loki.messenger.databinding.ViewVisibleMessageBinding import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.contacts.Contact.ContactContext import org.session.libsession.messaging.open_groups.OpenGroupApi -import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address import org.session.libsession.utilities.ViewUtil import org.session.libsession.utilities.getColorFromAttr @@ -37,7 +36,6 @@ import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.ThreadUtils import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 -import org.thoughtcrime.securesms.conversation.v2.components.ExpirationTimerView import org.thoughtcrime.securesms.database.LokiAPIDatabase import org.thoughtcrime.securesms.database.LokiThreadDatabase import org.thoughtcrime.securesms.database.MmsDatabase @@ -264,11 +262,15 @@ class VisibleMessageView : LinearLayout { val isLastMessage = message.id == lastMessageID binding.messageStatusTextView.isVisible = textId != null && (!message.isSent || isLastMessage || disappearing) + val showTimer = disappearing && !message.isPending binding.messageStatusImageView.isVisible = - iconID != null && (!message.isSent || isLastMessage || disappearing) + iconID != null && !showTimer && (!message.isSent || isLastMessage) + binding.messageStatusImageView.bringToFront() - if (disappearing && !message.isPending) updateExpirationTimer(message, iconColor) + binding.expirationTimerView.bringToFront() + binding.expirationTimerView.isVisible = showTimer + if (showTimer) updateExpirationTimer(message) } else { binding.messageStatusTextView.isVisible = false binding.messageStatusImageView.isVisible = false @@ -342,20 +344,16 @@ class VisibleMessageView : LinearLayout { ) } - private fun updateExpirationTimer(message: MessageRecord, iconColor: Int?) { - val expirationTimerView = ExpirationTimerView(binding.messageStatusImageView, iconColor) + private fun updateExpirationTimer(message: MessageRecord) { + val expirationTimerView = binding.expirationTimerView if (!message.isOutgoing) binding.messageStatusTextView.bringToFront() if (message.expiresIn > 0) { - expirationTimerView.setPercentComplete(0.0f) if (message.expireStarted > 0) { expirationTimerView.setExpirationTime(message.expireStarted, message.expiresIn) - expirationTimerView.startAnimation() ApplicationContext.getInstance(context).expiringMessageManager.checkSchedule() } else { - expirationTimerView.setPercentComplete(0.0f) - expirationTimerView.stopAnimation() ThreadUtils.queue { val expirationManager = ApplicationContext.getInstance(context).expiringMessageManager val id = message.getId() diff --git a/app/src/main/res/layout/view_control_message.xml b/app/src/main/res/layout/view_control_message.xml index 03923677db..45c7f53f65 100644 --- a/app/src/main/res/layout/view_control_message.xml +++ b/app/src/main/res/layout/view_control_message.xml @@ -29,6 +29,16 @@ tools:src="@drawable/ic_timer" tools:visibility="visible"/> + + + +