mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-22 18:55:12 +00:00
Add helper text when dragging filter at a low velocity.
This commit is contained in:
@@ -1,15 +1,18 @@
|
||||
package org.thoughtcrime.securesms.conversationlist.chatfilter
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.FloatEvaluator
|
||||
import android.animation.ObjectAnimator
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.animation.doOnEnd
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.animation.AnimationCompleteListener
|
||||
import org.thoughtcrime.securesms.databinding.ConversationListFilterPullViewBinding
|
||||
import org.thoughtcrime.securesms.util.VibrateUtil
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
/**
|
||||
* Encapsulates the push / pull latch for enabling and disabling
|
||||
@@ -24,7 +27,9 @@ class ConversationListFilterPullView @JvmOverloads constructor(
|
||||
) : FrameLayout(context, attrs) {
|
||||
|
||||
companion object {
|
||||
private val EVAL = FloatEvaluator()
|
||||
private const val ANIMATE_HELP_TEXT_VELOCITY_THRESHOLD = 1f
|
||||
private const val ANIMATE_HELP_TEXT_THRESHOLD = 30
|
||||
private const val ANIMATE_HELP_TEXT_START_FRACTION = 0.35f
|
||||
}
|
||||
|
||||
private val binding: ConversationListFilterPullViewBinding
|
||||
@@ -42,6 +47,9 @@ class ConversationListFilterPullView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
private var pillAnimator: Animator? = null
|
||||
private val velocityTracker = ProgressVelocityTracker(5)
|
||||
private var animateHelpText = 0
|
||||
private var helpTextStartFraction = 0.35f
|
||||
|
||||
fun onUserDrag(progress: Float) {
|
||||
binding.filterCircle.textFieldMetrics = Pair(binding.filterText.width, binding.filterText.height)
|
||||
@@ -52,11 +60,33 @@ class ConversationListFilterPullView @JvmOverloads constructor(
|
||||
} else if (state == FilterPullState.CLOSED && progress >= 1f) {
|
||||
setState(FilterPullState.OPEN_APEX)
|
||||
vibrate()
|
||||
resetHelpText()
|
||||
} else if (state == FilterPullState.OPEN && progress >= 1f) {
|
||||
setState(FilterPullState.CLOSE_APEX)
|
||||
vibrate()
|
||||
}
|
||||
|
||||
if (state == FilterPullState.CLOSED && animateHelpText < ANIMATE_HELP_TEXT_THRESHOLD) {
|
||||
velocityTracker.submitProgress(progress, System.currentTimeMillis().milliseconds)
|
||||
val velocity = velocityTracker.calculateVelocity()
|
||||
animateHelpText = if (velocity > 0f && velocity < ANIMATE_HELP_TEXT_VELOCITY_THRESHOLD) {
|
||||
min(Int.MAX_VALUE, animateHelpText + 1)
|
||||
} else {
|
||||
max(0, animateHelpText - 1)
|
||||
}
|
||||
|
||||
if (animateHelpText >= ANIMATE_HELP_TEXT_THRESHOLD) {
|
||||
helpTextStartFraction = max(progress, ANIMATE_HELP_TEXT_START_FRACTION)
|
||||
}
|
||||
}
|
||||
|
||||
if (animateHelpText >= ANIMATE_HELP_TEXT_THRESHOLD) {
|
||||
binding.helpText.visibility = VISIBLE
|
||||
}
|
||||
|
||||
binding.helpText.alpha = max(0f, FilterLerp.getHelpTextAlphaLerp(progress, helpTextStartFraction))
|
||||
binding.helpText.translationY = FilterLerp.getPillLerp(progress)
|
||||
|
||||
if (state == FilterPullState.OPEN || state == FilterPullState.OPEN_APEX || state == FilterPullState.CLOSE_APEX || state == FilterPullState.CLOSING) {
|
||||
binding.filterText.translationY = FilterLerp.getPillLerp(progress)
|
||||
} else {
|
||||
@@ -96,6 +126,17 @@ class ConversationListFilterPullView @JvmOverloads constructor(
|
||||
animatePillOut()
|
||||
}
|
||||
|
||||
private fun resetHelpText() {
|
||||
velocityTracker.clear()
|
||||
animateHelpText = 0
|
||||
helpTextStartFraction = ANIMATE_HELP_TEXT_START_FRACTION
|
||||
binding.helpText.animate().alpha(0f).setListener(object : AnimationCompleteListener() {
|
||||
override fun onAnimationEnd(animation: Animator?) {
|
||||
binding.helpText.visibility = INVISIBLE
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun animatePillIn() {
|
||||
binding.filterText.visibility = VISIBLE
|
||||
binding.filterText.alpha = 0f
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms.conversationlist.chatfilter
|
||||
|
||||
import android.animation.FloatEvaluator
|
||||
import androidx.annotation.FloatRange
|
||||
import org.signal.core.util.dp
|
||||
|
||||
/**
|
||||
@@ -36,6 +37,15 @@ object FilterLerp {
|
||||
Point(1f, FILTER_APEX * 0.55f)
|
||||
)
|
||||
|
||||
private fun helpTextAlphaLerp(@FloatRange(from = 0.0, to = 1.0) startFraction: Float) = getFn(
|
||||
Point(startFraction, 0f),
|
||||
Point(1f, 1f)
|
||||
)
|
||||
|
||||
fun getHelpTextAlphaLerp(fraction: Float, startFraction: Float): Float {
|
||||
return getLerp(fraction, helpTextAlphaLerp(startFraction))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the LERP for the "Filter enabled" pill.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.thoughtcrime.securesms.conversationlist.chatfilter
|
||||
|
||||
import androidx.annotation.FloatRange
|
||||
import org.whispersystems.signalservice.api.util.Preconditions
|
||||
import kotlin.time.Duration
|
||||
|
||||
/**
|
||||
* Velocity tracker based off % progress.
|
||||
* Units are thus in %/s
|
||||
*
|
||||
* This class only supports a single axial value.
|
||||
*/
|
||||
class ProgressVelocityTracker(@androidx.annotation.IntRange(from = 0) capacity: Int) {
|
||||
|
||||
private val progressBuffer = RingBuffer<Float>(capacity)
|
||||
private val durationBuffer = RingBuffer<Duration>(capacity)
|
||||
|
||||
fun submitProgress(@FloatRange(from = 0.0, to = 1.0) progress: Float, duration: Duration) {
|
||||
progressBuffer.add(progress)
|
||||
durationBuffer.add(duration)
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
progressBuffer.clear()
|
||||
durationBuffer.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the average velocity. The units are %/s
|
||||
*/
|
||||
fun calculateVelocity(): Float {
|
||||
Preconditions.checkState(progressBuffer.size() == durationBuffer.size())
|
||||
|
||||
if (progressBuffer.size() < 2) {
|
||||
return 0f
|
||||
}
|
||||
|
||||
var progressDelta: Float
|
||||
var timeDelta: Duration
|
||||
|
||||
val percentPerMillisecond = (0 until progressBuffer.size()).windowed(2).map { (indexA, indexB) ->
|
||||
progressDelta = progressBuffer[indexB] - progressBuffer[indexA]
|
||||
timeDelta = durationBuffer[indexB] - durationBuffer[indexA]
|
||||
progressDelta / timeDelta.inWholeMilliseconds
|
||||
}.sum() / (progressBuffer.size() - 1)
|
||||
|
||||
return percentPerMillisecond * 1000
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.thoughtcrime.securesms.conversationlist.chatfilter
|
||||
|
||||
import androidx.collection.CircularArray
|
||||
|
||||
class RingBuffer<T>(@androidx.annotation.IntRange(from = 0) private val capacity: Int) {
|
||||
private val buffer = CircularArray<T>(capacity)
|
||||
|
||||
fun add(t: T) {
|
||||
if (size() == capacity) {
|
||||
buffer.popFirst()
|
||||
}
|
||||
|
||||
buffer.addLast(t)
|
||||
}
|
||||
|
||||
fun size() = buffer.size()
|
||||
|
||||
fun clear() = buffer.clear()
|
||||
|
||||
operator fun get(index: Int): T = buffer.get(index)
|
||||
}
|
||||
Reference in New Issue
Block a user