Add new gift opening animation and confirmation haptic.

fork-5.53.8
Alex Hart 2022-07-20 10:47:37 -03:00 zatwierdzone przez Cody Henthorne
rodzic d49c8d5184
commit 6bd8bc08d8
3 zmienionych plików z 94 dodań i 10 usunięć

Wyświetl plik

@ -25,4 +25,31 @@ interface OpenableGift {
* Clears any callback created to start the open animation
*/
fun clearOpenGiftCallback()
/**
* Gets the appropriate sign for the animation evaluators:
*
* - Incoming and LTR -> Positive
* - Incoming and RTL -> Negative
* - Outgoing and LTR -> Negative
* - Outgoing and RTL -> Positive
*/
fun getAnimationSign(): AnimationSign
enum class AnimationSign(val sign: Float) {
POSITIVE(1f),
NEGATIVE(-1f);
companion object {
@JvmStatic
fun get(isLtr: Boolean, isOutgoing: Boolean): AnimationSign {
return when {
isLtr && isOutgoing -> NEGATIVE
isLtr -> POSITIVE
isOutgoing -> POSITIVE
else -> NEGATIVE
}
}
}
}
}

Wyświetl plik

@ -5,6 +5,7 @@ import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.graphics.RectF
import android.graphics.drawable.Drawable
import android.provider.Settings
@ -13,8 +14,10 @@ import android.view.animation.AnticipateInterpolator
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
import androidx.core.graphics.toRect
import androidx.core.graphics.withRotation
import androidx.core.graphics.withSave
import androidx.core.graphics.withTranslation
import androidx.core.view.animation.PathInterpolatorCompat
import androidx.core.view.children
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
@ -191,7 +194,7 @@ class OpenableGiftItemDecoration(context: Context) : RecyclerView.ItemDecoration
}
private fun getTranslation(progress: Float): Double {
val interpolated = INTERPOLATOR.getInterpolation(progress)
val interpolated = TRANSLATION_X_INTERPOLATOR.getInterpolation(progress)
val evaluated = EVALUATOR.evaluate(interpolated, 0f, 360f)
return 0.25f * sin(4 * evaluated * PI / 180f) * 180f / PI
@ -199,17 +202,60 @@ class OpenableGiftItemDecoration(context: Context) : RecyclerView.ItemDecoration
}
class OpenAnimationState(openableGift: OpenableGift, startTime: Long) : GiftAnimationState(openableGift, startTime, OPEN_DURATION_MILLIS) {
override fun update(canvas: Canvas, projection: Projection, progress: Float, lastFrameProgress: Float, drawBox: (Canvas, Projection) -> Unit, drawBow: (Canvas, Projection) -> Unit) {
val interpolatedProgress = INTERPOLATOR.getInterpolation(progress)
val evaluatedValue = EVALUATOR.evaluate(interpolatedProgress, 0f, DimensionUnit.DP.toPixels(161f))
val interpolatedY = TRANSLATION_Y_INTERPOLATOR.getInterpolation(progress)
private val bowRotationPath = Path().apply {
lineTo(0.13f, -0.75f)
lineTo(0.26f, 0f)
lineTo(0.73f, -1.375f)
lineTo(1f, 1f)
}
private val boxRotationPath = Path().apply {
lineTo(0.63f, -1.6f)
lineTo(1f, 1f)
}
private val bowRotationInterpolator = PathInterpolatorCompat.create(bowRotationPath)
private val boxRotationInterpolator = PathInterpolatorCompat.create(boxRotationPath)
override fun update(canvas: Canvas, projection: Projection, progress: Float, lastFrameProgress: Float, drawBox: (Canvas, Projection) -> Unit, drawBow: (Canvas, Projection) -> Unit) {
val sign = openableGift.getAnimationSign().sign
val boxStartDelay: Float = OPEN_BOX_START_DELAY / duration.toFloat()
val boxProgress: Float = max(0f, progress - boxStartDelay) / (1f - boxStartDelay)
val bowStartDelay: Float = OPEN_BOW_START_DELAY / duration.toFloat()
val bowProgress: Float = max(0f, progress - bowStartDelay) / (1f - bowStartDelay)
val interpolatedX = TRANSLATION_X_INTERPOLATOR.getInterpolation(boxProgress)
val evaluatedX = EVALUATOR.evaluate(interpolatedX, 0f, DimensionUnit.DP.toPixels(18f * sign))
val interpolatedY = TRANSLATION_Y_INTERPOLATOR.getInterpolation(boxProgress)
val evaluatedY = EVALUATOR.evaluate(interpolatedY, 0f, DimensionUnit.DP.toPixels(355f))
canvas.translate(evaluatedValue, evaluatedY)
val interpolatedBowRotation = bowRotationInterpolator.getInterpolation(bowProgress)
val evaluatedBowRotation = EVALUATOR.evaluate(interpolatedBowRotation, 0f, 8f * sign)
drawBox(canvas, projection)
drawBow(canvas, projection)
val interpolatedBoxRotation = boxRotationInterpolator.getInterpolation(boxProgress)
val evaluatedBoxRotation = EVALUATOR.evaluate(interpolatedBoxRotation, 0f, -5f * sign)
canvas.withTranslation(evaluatedX, evaluatedY) {
canvas.withRotation(
degrees = evaluatedBoxRotation,
pivotX = projection.x + projection.width / 2f,
pivotY = projection.y + projection.height / 2f
) {
drawBox(this, projection)
canvas.withRotation(
degrees = evaluatedBowRotation,
pivotX = projection.x + projection.width / 2f,
pivotY = projection.y + projection.height / 2f
) {
drawBow(this, projection)
}
}
}
}
}
@ -249,11 +295,13 @@ class OpenableGiftItemDecoration(context: Context) : RecyclerView.ItemDecoration
companion object {
private val TRANSLATION_Y_INTERPOLATOR = AnticipateInterpolator(3f)
private val INTERPOLATOR = AccelerateDecelerateInterpolator()
private val TRANSLATION_X_INTERPOLATOR = AccelerateDecelerateInterpolator()
private val EVALUATOR = FloatEvaluator()
private const val SHAKE_DURATION_MILLIS = 1000L
private const val OPEN_DURATION_MILLIS = 700L
private const val OPEN_DURATION_MILLIS = 1400L
private const val OPEN_BOX_START_DELAY = 400L
private const val OPEN_BOW_START_DELAY = 50L
private const val ONE_FRAME_RELATIVE_TO_30_FPS_MILLIS = 33
}
}

Wyświetl plik

@ -26,6 +26,7 @@ import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Build;
import android.text.Annotation;
import android.text.Spannable;
import android.text.SpannableString;
@ -41,6 +42,7 @@ import android.text.style.URLSpan;
import android.text.util.Linkify;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.TouchDelegate;
import android.view.View;
@ -2149,6 +2151,8 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
bodyBubble.setOnClickListener(unused -> {
openGift.invoke(this);
eventListener.onGiftBadgeRevealed(messageRecord);
bodyBubble.performHapticFeedback(Build.VERSION.SDK_INT >= 30 ? HapticFeedbackConstants.CONFIRM
: HapticFeedbackConstants.KEYBOARD_TAP);
});
giftViewStub.get().onGiftNotOpened();
}
@ -2163,6 +2167,11 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
}
}
@Override
public @NonNull AnimationSign getAnimationSign() {
return AnimationSign.get(ViewUtil.isLtr(this), messageRecord.isOutgoing());
}
private class SharedContactEventListener implements SharedContactView.EventListener {
@Override
public void onAddToContactsClicked(@NonNull Contact contact) {