mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-23 00:17:34 +00:00
Fix animation
This commit is contained in:
parent
1b5b7cfccc
commit
555209bec1
@ -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<ExpirationTimerView> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,8 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
|
import androidx.core.view.isGone
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import network.loki.messenger.databinding.ViewControlMessageBinding
|
import network.loki.messenger.databinding.ViewControlMessageBinding
|
||||||
@ -17,7 +19,6 @@ class ControlMessageView : LinearLayout {
|
|||||||
|
|
||||||
private lateinit var binding: ViewControlMessageBinding
|
private lateinit var binding: ViewControlMessageBinding
|
||||||
|
|
||||||
// region Lifecycle
|
|
||||||
constructor(context: Context) : super(context) { initialize() }
|
constructor(context: Context) : super(context) { initialize() }
|
||||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() }
|
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() }
|
||||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { 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)
|
binding = ViewControlMessageBinding.inflate(LayoutInflater.from(context), this, true)
|
||||||
layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)
|
layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)
|
||||||
}
|
}
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Updating
|
|
||||||
fun bind(message: MessageRecord, previous: MessageRecord?) {
|
fun bind(message: MessageRecord, previous: MessageRecord?) {
|
||||||
binding.dateBreakTextView.showDateBreak(message, previous)
|
binding.dateBreakTextView.showDateBreak(message, previous)
|
||||||
binding.iconImageView.visibility = View.GONE
|
binding.iconImageView.isGone = true
|
||||||
|
binding.expirationTimerView.isGone = true
|
||||||
var messageBody: CharSequence = message.getDisplayBody(context)
|
var messageBody: CharSequence = message.getDisplayBody(context)
|
||||||
binding.root.contentDescription= null
|
binding.root.contentDescription= null
|
||||||
when {
|
when {
|
||||||
message.isExpirationTimerUpdate -> {
|
message.isExpirationTimerUpdate -> {
|
||||||
ExpirationTimerView(binding.iconImageView, context.getColorFromAttr(android.R.attr.textColorPrimary)).apply {
|
binding.expirationTimerView.apply {
|
||||||
|
isVisible = true
|
||||||
setExpirationTime(message.expireStarted, message.expiresIn)
|
setExpirationTime(message.expireStarted, message.expiresIn)
|
||||||
startAnimation()
|
|
||||||
}
|
}
|
||||||
binding.iconImageView.visibility = View.VISIBLE
|
|
||||||
}
|
}
|
||||||
message.isMediaSavedNotification -> {
|
message.isMediaSavedNotification -> {
|
||||||
binding.iconImageView.setImageDrawable(
|
binding.iconImageView.apply {
|
||||||
|
setImageDrawable(
|
||||||
ResourcesCompat.getDrawable(resources, R.drawable.ic_file_download_white_36dp, context.theme)
|
ResourcesCompat.getDrawable(resources, R.drawable.ic_file_download_white_36dp, context.theme)
|
||||||
)
|
)
|
||||||
binding.iconImageView.visibility = View.VISIBLE
|
isVisible = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
message.isMessageRequestResponse -> {
|
message.isMessageRequestResponse -> {
|
||||||
messageBody = context.getString(R.string.message_requests_accepted)
|
messageBody = context.getString(R.string.message_requests_accepted)
|
||||||
@ -59,8 +60,10 @@ class ControlMessageView : LinearLayout {
|
|||||||
message.isFirstMissedCall -> R.drawable.ic_info_outline_light
|
message.isFirstMissedCall -> R.drawable.ic_info_outline_light
|
||||||
else -> R.drawable.ic_missed_call
|
else -> R.drawable.ic_missed_call
|
||||||
}
|
}
|
||||||
binding.iconImageView.setImageDrawable(ResourcesCompat.getDrawable(resources, drawable, context.theme))
|
binding.iconImageView.apply {
|
||||||
binding.iconImageView.visibility = View.VISIBLE
|
setImageDrawable(ResourcesCompat.getDrawable(resources, drawable, context.theme))
|
||||||
|
isVisible = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,5 +73,4 @@ class ControlMessageView : LinearLayout {
|
|||||||
fun recycle() {
|
fun recycle() {
|
||||||
|
|
||||||
}
|
}
|
||||||
// endregion
|
|
||||||
}
|
}
|
@ -29,7 +29,6 @@ import network.loki.messenger.databinding.ViewVisibleMessageBinding
|
|||||||
import org.session.libsession.messaging.contacts.Contact
|
import org.session.libsession.messaging.contacts.Contact
|
||||||
import org.session.libsession.messaging.contacts.Contact.ContactContext
|
import org.session.libsession.messaging.contacts.Contact.ContactContext
|
||||||
import org.session.libsession.messaging.open_groups.OpenGroupApi
|
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.Address
|
||||||
import org.session.libsession.utilities.ViewUtil
|
import org.session.libsession.utilities.ViewUtil
|
||||||
import org.session.libsession.utilities.getColorFromAttr
|
import org.session.libsession.utilities.getColorFromAttr
|
||||||
@ -37,7 +36,6 @@ import org.session.libsignal.utilities.IdPrefix
|
|||||||
import org.session.libsignal.utilities.ThreadUtils
|
import org.session.libsignal.utilities.ThreadUtils
|
||||||
import org.thoughtcrime.securesms.ApplicationContext
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
|
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.LokiAPIDatabase
|
||||||
import org.thoughtcrime.securesms.database.LokiThreadDatabase
|
import org.thoughtcrime.securesms.database.LokiThreadDatabase
|
||||||
import org.thoughtcrime.securesms.database.MmsDatabase
|
import org.thoughtcrime.securesms.database.MmsDatabase
|
||||||
@ -264,11 +262,15 @@ class VisibleMessageView : LinearLayout {
|
|||||||
val isLastMessage = message.id == lastMessageID
|
val isLastMessage = message.id == lastMessageID
|
||||||
binding.messageStatusTextView.isVisible =
|
binding.messageStatusTextView.isVisible =
|
||||||
textId != null && (!message.isSent || isLastMessage || disappearing)
|
textId != null && (!message.isSent || isLastMessage || disappearing)
|
||||||
|
val showTimer = disappearing && !message.isPending
|
||||||
binding.messageStatusImageView.isVisible =
|
binding.messageStatusImageView.isVisible =
|
||||||
iconID != null && (!message.isSent || isLastMessage || disappearing)
|
iconID != null && !showTimer && (!message.isSent || isLastMessage)
|
||||||
|
|
||||||
|
|
||||||
binding.messageStatusImageView.bringToFront()
|
binding.messageStatusImageView.bringToFront()
|
||||||
if (disappearing && !message.isPending) updateExpirationTimer(message, iconColor)
|
binding.expirationTimerView.bringToFront()
|
||||||
|
binding.expirationTimerView.isVisible = showTimer
|
||||||
|
if (showTimer) updateExpirationTimer(message)
|
||||||
} else {
|
} else {
|
||||||
binding.messageStatusTextView.isVisible = false
|
binding.messageStatusTextView.isVisible = false
|
||||||
binding.messageStatusImageView.isVisible = false
|
binding.messageStatusImageView.isVisible = false
|
||||||
@ -342,20 +344,16 @@ class VisibleMessageView : LinearLayout {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateExpirationTimer(message: MessageRecord, iconColor: Int?) {
|
private fun updateExpirationTimer(message: MessageRecord) {
|
||||||
val expirationTimerView = ExpirationTimerView(binding.messageStatusImageView, iconColor)
|
val expirationTimerView = binding.expirationTimerView
|
||||||
|
|
||||||
if (!message.isOutgoing) binding.messageStatusTextView.bringToFront()
|
if (!message.isOutgoing) binding.messageStatusTextView.bringToFront()
|
||||||
|
|
||||||
if (message.expiresIn > 0) {
|
if (message.expiresIn > 0) {
|
||||||
expirationTimerView.setPercentComplete(0.0f)
|
|
||||||
if (message.expireStarted > 0) {
|
if (message.expireStarted > 0) {
|
||||||
expirationTimerView.setExpirationTime(message.expireStarted, message.expiresIn)
|
expirationTimerView.setExpirationTime(message.expireStarted, message.expiresIn)
|
||||||
expirationTimerView.startAnimation()
|
|
||||||
ApplicationContext.getInstance(context).expiringMessageManager.checkSchedule()
|
ApplicationContext.getInstance(context).expiringMessageManager.checkSchedule()
|
||||||
} else {
|
} else {
|
||||||
expirationTimerView.setPercentComplete(0.0f)
|
|
||||||
expirationTimerView.stopAnimation()
|
|
||||||
ThreadUtils.queue {
|
ThreadUtils.queue {
|
||||||
val expirationManager = ApplicationContext.getInstance(context).expiringMessageManager
|
val expirationManager = ApplicationContext.getInstance(context).expiringMessageManager
|
||||||
val id = message.getId()
|
val id = message.getId()
|
||||||
|
@ -29,6 +29,16 @@
|
|||||||
tools:src="@drawable/ic_timer"
|
tools:src="@drawable/ic_timer"
|
||||||
tools:visibility="visible"/>
|
tools:visibility="visible"/>
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.conversation.v2.components.ExpirationTimerView
|
||||||
|
android:id="@+id/expirationTimerView"
|
||||||
|
android:layout_width="12dp"
|
||||||
|
android:layout_height="12dp"
|
||||||
|
android:layout_marginBottom="@dimen/small_spacing"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:tint="?android:textColorPrimary"
|
||||||
|
tools:src="@drawable/ic_timer"
|
||||||
|
tools:visibility="visible"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textView"
|
android:id="@+id/textView"
|
||||||
android:contentDescription="@string/AccessibilityId_control_message"
|
android:contentDescription="@string/AccessibilityId_control_message"
|
||||||
|
@ -164,6 +164,13 @@
|
|||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:src="@drawable/ic_delivery_status_sent" />
|
android:src="@drawable/ic_delivery_status_sent" />
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.conversation.v2.components.ExpirationTimerView
|
||||||
|
android:id="@+id/expirationTimerView"
|
||||||
|
android:layout_width="12dp"
|
||||||
|
android:layout_height="12dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:tint="?message_status_color" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user