NekoX/TMessagesProj/src/main/java/org/telegram/ui/Components/PullForegroundDrawable.java

684 lines
24 KiB
Java

package org.telegram.ui.Components;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.text.TextPaint;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.LinearInterpolator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.ColorUtils;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.ui.ActionBar.Theme;
public class PullForegroundDrawable {
public final static float SNAP_HEIGHT = 0.85f;
public final static float startPullParallax = 0.45f;
public final static float endPullParallax = 0.25f;
public final static float startPullOverScroll = 0.2f;
public final static long minPullingTime = 200L;
public int scrollDy;
private String backgroundColorKey = Theme.key_chats_archivePullDownBackground;
private String backgroundActiveColorKey = Theme.key_chats_archivePullDownBackgroundActive;
private String avatarBackgroundColorKey = Theme.key_avatar_backgroundArchivedHidden;
private boolean changeAvatarColor = true;
private final Paint paintSecondary = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint paintWhite = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint paintBackgroundAccent = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint backgroundPaint = new Paint();
private final RectF rectF = new RectF();
private final Paint tooltipTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
private final ArrowDrawable arrowDrawable = new ArrowDrawable();
private final Path circleClipPath = new Path();
private float textSwappingProgress = 1f;
private float arrowRotateProgress = 1f;
private boolean animateToEndText;
private boolean arrowAnimateTo;
private ValueAnimator textSwipingAnimator;
private ValueAnimator accentRevalAnimatorIn;
private ValueAnimator accentRevalAnimatorOut;
private float accentRevalProgress = 1f;
private float accentRevalProgressOut = 1f;
private float textInProgress;
private boolean animateToTextIn;
private ValueAnimator textIntAnimator;
private ValueAnimator arrowRotateAnimator;
private AnimatorSet outAnimator;
public float outProgress;
private float bounceProgress;
private boolean animateOut;
private boolean bounceIn;
private boolean animateToColorize;
private View cell;
private RecyclerListView listView;
public float pullProgress;
public float outCy;
public float outCx;
public float outRadius;
public float outImageSize;
public float outOverScroll;
private String pullTooltip;
private String releaseTooltip;
private boolean willDraw;
private boolean isOut;
private float touchSlop;
private ValueAnimator.AnimatorUpdateListener textSwappingUpdateListener = animation -> {
textSwappingProgress = (float) animation.getAnimatedValue();
if (cell != null) {
cell.invalidate();
}
};
private ValueAnimator.AnimatorUpdateListener textInUpdateListener = animation -> {
textInProgress = (float) animation.getAnimatedValue();
if (cell != null) {
cell.invalidate();
}
};
public PullForegroundDrawable(String pullText, String releaseText) {
tooltipTextPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
tooltipTextPaint.setTextAlign(Paint.Align.CENTER);
tooltipTextPaint.setTextSize(AndroidUtilities.dp(16));
final ViewConfiguration vc = ViewConfiguration.get(ApplicationLoader.applicationContext);
touchSlop = vc.getScaledTouchSlop();
pullTooltip = pullText;
releaseTooltip = releaseText;
}
public static int getMaxOverscroll() {
return AndroidUtilities.dp(72);
}
public void setColors(String background, String active) {
backgroundColorKey = background;
backgroundActiveColorKey = active;
changeAvatarColor = false;
updateColors();
}
public void setCell(View view) {
cell = view;
updateColors();
}
public void updateColors() {
int primaryColor = Color.WHITE;
int backgroundColor = Theme.getColor(backgroundColorKey);
tooltipTextPaint.setColor(primaryColor);
paintWhite.setColor(primaryColor);
paintSecondary.setColor(ColorUtils.setAlphaComponent(primaryColor, 100));
backgroundPaint.setColor(backgroundColor);
arrowDrawable.setColor(backgroundColor);
paintBackgroundAccent.setColor(Theme.getColor(avatarBackgroundColorKey));
}
public void setListView(RecyclerListView listView) {
this.listView = listView;
}
public void drawOverScroll(Canvas canvas) {
draw(canvas, true);
}
public void draw(Canvas canvas) {
draw(canvas, false);
}
protected float getViewOffset() {
return 0;
}
public void draw(Canvas canvas, boolean header) {
if (!willDraw || isOut || cell == null || listView == null) {
return;
}
int startPadding = AndroidUtilities.dp(28);
int smallMargin = AndroidUtilities.dp(8);
int radius = AndroidUtilities.dp(9);
int diameter = AndroidUtilities.dp(18);
int overscroll = (int) getViewOffset();
int visibleHeight = (int) (cell.getHeight() * pullProgress);
float bounceP = bounceIn ? (0.07f * bounceProgress) - 0.05f : 0.02f * bounceProgress;
updateTextProgress(pullProgress);
float outProgressHalf = outProgress * 2f;
if (outProgressHalf > 1f) {
outProgressHalf = 1f;
}
float cX = outCx;
float cY = outCy;
if (header) {
cY += overscroll;
}
int smallCircleX = startPadding + radius;
int smallCircleY = cell.getMeasuredHeight() - smallMargin - radius;
if (header) {
smallCircleY += overscroll;
}
float startPullProgress = visibleHeight > diameter + smallMargin * 2 ? 1f : (float) visibleHeight / (diameter + smallMargin * 2);
canvas.save();
if (header) {
canvas.clipRect(0, 0, listView.getMeasuredWidth(), overscroll + 1);
}
if (outProgress == 0f) {
if (!(accentRevalProgress == 1f || accentRevalProgressOut == 1)) {
canvas.drawPaint(backgroundPaint);
}
} else {
float outBackgroundRadius = outRadius + (cell.getWidth() - outRadius) * (1f - outProgress) + (outRadius * bounceP);
if (!(accentRevalProgress == 1f || accentRevalProgressOut == 1)) {
canvas.drawCircle(cX, cY, outBackgroundRadius, backgroundPaint);
}
circleClipPath.reset();
rectF.set(cX - outBackgroundRadius, cY - outBackgroundRadius, cX + outBackgroundRadius, cY + outBackgroundRadius);
circleClipPath.addOval(rectF, Path.Direction.CW);
canvas.clipPath(circleClipPath);
}
if (animateToColorize) {
if (accentRevalProgressOut > accentRevalProgress) {
canvas.save();
canvas.translate((cX - smallCircleX) * (outProgress), (cY - smallCircleY) * (outProgress));
canvas.drawCircle(smallCircleX, smallCircleY, cell.getWidth() * accentRevalProgressOut, backgroundPaint);
canvas.restore();
}
if (accentRevalProgress > 0f) {
canvas.save();
canvas.translate((cX - smallCircleX) * (outProgress), (cY - smallCircleY) * (outProgress));
canvas.drawCircle(smallCircleX, smallCircleY, cell.getWidth() * accentRevalProgress, paintBackgroundAccent);
canvas.restore();
}
} else {
if (accentRevalProgress > accentRevalProgressOut) {
canvas.save();
canvas.translate((cX - smallCircleX) * (outProgress), (cY - smallCircleY) * (outProgress));
canvas.drawCircle(smallCircleX, smallCircleY, cell.getWidth() * accentRevalProgress, paintBackgroundAccent);
canvas.restore();
}
if (accentRevalProgressOut > 0f) {
canvas.save();
canvas.translate((cX - smallCircleX) * (outProgress), (cY - smallCircleY) * (outProgress));
canvas.drawCircle(smallCircleX, smallCircleY, cell.getWidth() * accentRevalProgressOut, backgroundPaint);
canvas.restore();
}
}
if (visibleHeight > diameter + smallMargin * 2) {
paintSecondary.setAlpha((int) ((1f - outProgressHalf) * 0.4f * startPullProgress * 255));
if (header) {
rectF.set(startPadding, smallMargin, startPadding + diameter, smallMargin + overscroll + radius);
} else {
rectF.set(startPadding, cell.getHeight() - visibleHeight + smallMargin - overscroll, startPadding + diameter, cell.getHeight() - smallMargin);
}
canvas.drawRoundRect(rectF, radius, radius, paintSecondary);
}
if (header) {
canvas.restore();
return;
}
if (outProgress == 0f) {
paintWhite.setAlpha((int) (startPullProgress * 255));
canvas.drawCircle(smallCircleX, smallCircleY, radius, paintWhite);
int ih = arrowDrawable.getIntrinsicHeight();
int iw = arrowDrawable.getIntrinsicWidth();
arrowDrawable.setBounds(smallCircleX - (iw >> 1), smallCircleY - (ih >> 1), smallCircleX + (iw >> 1), smallCircleY + (ih >> 1));
float rotateProgress = 1f - arrowRotateProgress;
if (rotateProgress < 0) {
rotateProgress = 0f;
}
rotateProgress = 1f - rotateProgress;
canvas.save();
canvas.rotate(180 * rotateProgress, smallCircleX, smallCircleY);
canvas.translate(0, AndroidUtilities.dpf2(1f) * 1f - rotateProgress);
arrowDrawable.setColor(animateToColorize ? paintBackgroundAccent.getColor() : Theme.getColor(backgroundColorKey));
arrowDrawable.draw(canvas);
canvas.restore();
}
if (pullProgress > 0f) {
textIn();
}
float textY = cell.getHeight() - ((diameter + smallMargin * 2) / 2f) + AndroidUtilities.dp(6);
tooltipTextPaint.setAlpha((int) (255 * textSwappingProgress * startPullProgress * textInProgress));
float textCx = cell.getWidth() / 2f - AndroidUtilities.dp(2);
if (textSwappingProgress > 0 && textSwappingProgress < 1f) {
canvas.save();
float scale = 0.8f + 0.2f * textSwappingProgress;
canvas.scale(scale, scale, textCx, textY + AndroidUtilities.dp(16) * (1f - textSwappingProgress));
}
canvas.drawText(pullTooltip, textCx, textY + AndroidUtilities.dp(8) * (1f - textSwappingProgress), tooltipTextPaint);
if (textSwappingProgress > 0 && textSwappingProgress < 1f) {
canvas.restore();
}
if (textSwappingProgress > 0 && textSwappingProgress < 1f) {
canvas.save();
float scale = 0.9f + 0.1f * (1f - textSwappingProgress);
canvas.scale(scale, scale, textCx, textY - AndroidUtilities.dp(8) * (textSwappingProgress));
}
tooltipTextPaint.setAlpha((int) (255 * (1f - textSwappingProgress) * startPullProgress * textInProgress));
canvas.drawText(releaseTooltip, textCx, textY - AndroidUtilities.dp(8) * (textSwappingProgress), tooltipTextPaint);
if (textSwappingProgress > 0 && textSwappingProgress < 1f) {
canvas.restore();
}
canvas.restore();
if (changeAvatarColor && outProgress > 0) {
canvas.save();
int iw = Theme.dialogs_archiveAvatarDrawable.getIntrinsicWidth();
int startCx = startPadding + radius;
int startCy = cell.getHeight() - smallMargin - radius;
float scaleStart = (float) AndroidUtilities.dp(24) / iw;
float scale = scaleStart + (1f - scaleStart) * outProgress + bounceP;
int x = (int) cX;
int y = (int) cY;
canvas.translate((startCx - cX) * (1f - outProgress), (startCy - cY) * (1f - outProgress));
canvas.scale(scale, scale, cX, cY);
Theme.dialogs_archiveAvatarDrawable.setProgress(0f);
if (!Theme.dialogs_archiveAvatarDrawableRecolored) {
Theme.dialogs_archiveAvatarDrawable.beginApplyLayerColors();
Theme.dialogs_archiveAvatarDrawable.setLayerColor("Arrow1.**", Theme.getNonAnimatedColor(avatarBackgroundColorKey));
Theme.dialogs_archiveAvatarDrawable.setLayerColor("Arrow2.**", Theme.getNonAnimatedColor(avatarBackgroundColorKey));
Theme.dialogs_archiveAvatarDrawable.commitApplyLayerColors();
Theme.dialogs_archiveAvatarDrawableRecolored = true;
}
Theme.dialogs_archiveAvatarDrawable.setBounds((int) (cX - iw / 2f), (int) (cY - iw / 2f), (int) (cX + iw / 2f), (int) (cY + iw / 2f));
Theme.dialogs_archiveAvatarDrawable.draw(canvas);
canvas.restore();
}
}
private void updateTextProgress(float pullProgress) {
boolean endText = pullProgress > SNAP_HEIGHT;
if (animateToEndText != endText) {
animateToEndText = endText;
if (textInProgress == 0f) {
if (textSwipingAnimator != null) {
textSwipingAnimator.cancel();
}
textSwappingProgress = endText ? 0f : 1f;
} else {
if (textSwipingAnimator != null) {
textSwipingAnimator.cancel();
}
textSwipingAnimator = ValueAnimator.ofFloat(textSwappingProgress, endText ? 0f : 1f);
textSwipingAnimator.addUpdateListener(textSwappingUpdateListener);
textSwipingAnimator.setInterpolator(new LinearInterpolator());
textSwipingAnimator.setDuration(170);
textSwipingAnimator.start();
}
}
if (endText != arrowAnimateTo) {
arrowAnimateTo = endText;
if (arrowRotateAnimator != null) {
arrowRotateAnimator.cancel();
}
arrowRotateAnimator = ValueAnimator.ofFloat(arrowRotateProgress, arrowAnimateTo ? 0f : 1f);
arrowRotateAnimator.addUpdateListener(animation -> {
arrowRotateProgress = (float) animation.getAnimatedValue();
if (cell != null) {
cell.invalidate();
}
});
arrowRotateAnimator.setInterpolator(CubicBezierInterpolator.EASE_BOTH);
arrowRotateAnimator.setDuration(250);
arrowRotateAnimator.start();
}
}
public void colorize(boolean colorize) {
if (animateToColorize != colorize) {
animateToColorize = colorize;
if (colorize) {
if (accentRevalAnimatorIn != null) {
accentRevalAnimatorIn.cancel();
accentRevalAnimatorIn = null;
}
accentRevalProgress = 0f;
accentRevalAnimatorIn = ValueAnimator.ofFloat(accentRevalProgress, 1f);
accentRevalAnimatorIn.addUpdateListener(animation -> {
accentRevalProgress = (float) animation.getAnimatedValue();
if (cell != null) {
cell.invalidate();
}
if (listView != null) {
listView.invalidate();
}
});
accentRevalAnimatorIn.setInterpolator(AndroidUtilities.accelerateInterpolator);
accentRevalAnimatorIn.setDuration(230);
accentRevalAnimatorIn.start();
} else {
if (accentRevalAnimatorOut != null) {
accentRevalAnimatorOut.cancel();
accentRevalAnimatorOut = null;
}
accentRevalProgressOut = 0f;
accentRevalAnimatorOut = ValueAnimator.ofFloat(accentRevalProgressOut, 1f);
accentRevalAnimatorOut.addUpdateListener(animation -> {
accentRevalProgressOut = (float) animation.getAnimatedValue();
if (cell != null) {
cell.invalidate();
}
if (listView != null) {
listView.invalidate();
}
});
accentRevalAnimatorOut.setInterpolator(AndroidUtilities.accelerateInterpolator);
accentRevalAnimatorOut.setDuration(230);
accentRevalAnimatorOut.start();
}
}
}
Runnable textInRunnable = new Runnable() {
@Override
public void run() {
animateToTextIn = true;
if (textIntAnimator != null) {
textIntAnimator.cancel();
}
textInProgress = 0f;
textIntAnimator = ValueAnimator.ofFloat(0f, 1f);
textIntAnimator.addUpdateListener(textInUpdateListener);
textIntAnimator.setInterpolator(new LinearInterpolator());
textIntAnimator.setDuration(150);
textIntAnimator.start();
}
};
boolean wasSendCallback = false;
private void textIn() {
if (!animateToTextIn) {
if (Math.abs(scrollDy) < touchSlop * 0.5f) {
if (!wasSendCallback) {
textInProgress = 1f;
animateToTextIn = true;
}
} else {
wasSendCallback = true;
cell.removeCallbacks(textInRunnable);
cell.postDelayed(textInRunnable, 200);
}
}
}
public void startOutAnimation() {
if (animateOut || listView == null) {
return;
}
if (outAnimator != null) {
outAnimator.removeAllListeners();
outAnimator.cancel();
}
animateOut = true;
bounceIn = true;
bounceProgress = 0f;
outOverScroll = listView.getTranslationY() / AndroidUtilities.dp(100);
ValueAnimator out = ValueAnimator.ofFloat(0f, 1f);
out.addUpdateListener(animation -> {
setOutProgress((float) animation.getAnimatedValue());
if (cell != null) {
cell.invalidate();
}
});
out.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT);
out.setDuration(250);
ValueAnimator bounceIn = ValueAnimator.ofFloat(0f, 1f);
bounceIn.addUpdateListener(animation -> {
bounceProgress = (float) animation.getAnimatedValue();
this.bounceIn = true;
if (cell != null) {
cell.invalidate();
}
});
bounceIn.setInterpolator(CubicBezierInterpolator.EASE_BOTH);
bounceIn.setDuration(150);
ValueAnimator bounceOut = ValueAnimator.ofFloat(1f, 0f);
bounceOut.addUpdateListener(animation -> {
bounceProgress = (float) animation.getAnimatedValue();
this.bounceIn = false;
if (cell != null) {
cell.invalidate();
}
});
bounceOut.setInterpolator(CubicBezierInterpolator.EASE_BOTH);
bounceOut.setDuration(135);
outAnimator = new AnimatorSet();
outAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
doNotShow();
}
});
AnimatorSet bounce = new AnimatorSet();
bounce.playSequentially(bounceIn, bounceOut);
bounce.setStartDelay(180);
outAnimator.playTogether(out, bounce);
outAnimator.start();
}
private void setOutProgress(float value) {
outProgress = value;
int color = ColorUtils.blendARGB(Theme.getNonAnimatedColor(avatarBackgroundColorKey), Theme.getNonAnimatedColor(backgroundActiveColorKey), 1f - outProgress);
paintBackgroundAccent.setColor(color);
if (changeAvatarColor && isDraw()) {
Theme.dialogs_archiveAvatarDrawable.beginApplyLayerColors();
Theme.dialogs_archiveAvatarDrawable.setLayerColor("Arrow1.**", color);
Theme.dialogs_archiveAvatarDrawable.setLayerColor("Arrow2.**", color);
Theme.dialogs_archiveAvatarDrawable.commitApplyLayerColors();
}
}
public void doNotShow() {
if (textSwipingAnimator != null) {
textSwipingAnimator.cancel();
}
if (textIntAnimator != null) {
textIntAnimator.cancel();
}
if (cell != null) {
cell.removeCallbacks(textInRunnable);
}
if (accentRevalAnimatorIn != null) {
accentRevalAnimatorIn.cancel();
}
textSwappingProgress = 1f;
arrowRotateProgress = 1f;
animateToEndText = false;
arrowAnimateTo = false;
animateToTextIn = false;
wasSendCallback = false;
textInProgress = 0f;
isOut = true;
setOutProgress(1f);
animateToColorize = false;
accentRevalProgress = 0f;
}
public void showHidden() {
if (outAnimator != null) {
outAnimator.removeAllListeners();
outAnimator.cancel();
}
setOutProgress(0f);
isOut = false;
animateOut = false;
}
public void destroyView() {
cell = null;
if (textSwipingAnimator != null) {
textSwipingAnimator.cancel();
}
if (outAnimator != null) {
outAnimator.removeAllListeners();
outAnimator.cancel();
}
}
public boolean isDraw() {
return willDraw && !isOut;
}
public void setWillDraw(boolean b) {
willDraw = b;
}
public void resetText() {
if (textIntAnimator != null) {
textIntAnimator.cancel();
}
if (cell != null) {
cell.removeCallbacks(textInRunnable);
}
textInProgress = 0f;
animateToTextIn = false;
wasSendCallback = false;
}
public Paint getBackgroundPaint() {
return backgroundPaint;
}
private class ArrowDrawable extends Drawable {
private Path path = new Path();
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private float lastDensity;
public ArrowDrawable() {
updatePath();
}
private void updatePath() {
int h = AndroidUtilities.dp(18);
path.reset();
path.moveTo(h >> 1, AndroidUtilities.dpf2(4.98f));
path.lineTo(AndroidUtilities.dpf2(4.95f), AndroidUtilities.dpf2(9f));
path.lineTo(h - AndroidUtilities.dpf2(4.95f), AndroidUtilities.dpf2(9f));
path.lineTo(h >> 1, AndroidUtilities.dpf2(4.98f));
paint.setStyle(Paint.Style.FILL_AND_STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeWidth(AndroidUtilities.dpf2(1f));
lastDensity = AndroidUtilities.density;
}
public void setColor(int color) {
paint.setColor(color);
}
@Override
public int getIntrinsicHeight() {
return AndroidUtilities.dp(18);
}
@Override
public int getIntrinsicWidth() {
return getIntrinsicHeight();
}
@Override
public void draw(@NonNull Canvas canvas) {
if (lastDensity != AndroidUtilities.density) {
updatePath();
}
canvas.save();
canvas.translate(getBounds().left, getBounds().top);
canvas.drawPath(path, paint);
int h = AndroidUtilities.dp(18);
canvas.drawRect(AndroidUtilities.dpf2(7.56f), AndroidUtilities.dpf2(8f), h - AndroidUtilities.dpf2(7.56f), AndroidUtilities.dpf2(11.1f), paint);
canvas.restore();
}
@Override
public void setAlpha(int alpha) {
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
}
@Override
public int getOpacity() {
return PixelFormat.UNKNOWN;
}
}
}