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

371 lines
13 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.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.OvershootInterpolator;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.R;
import org.telegram.ui.ActionBar.Theme;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TextSelectionHint extends View {
StaticLayout textLayout;
TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
Paint selectionPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
int padding = AndroidUtilities.dp(24);
private Interpolator interpolator = new OvershootInterpolator();
private final Theme.ResourcesProvider resourcesProvider;
float enterValue;
int start;
int end;
int animateToStart;
int animateToEnd;
float startOffsetValue;
float endOffsetValue;
int currentStart;
int currentEnd;
int lastW;
boolean showing;
float prepareProgress;
Animator a;
Runnable dismissTunnable = this::hideInternal;
private boolean showOnMeasure;
public TextSelectionHint(Context context, Theme.ResourcesProvider resourcesProvider) {
super(context);
this.resourcesProvider = resourcesProvider;
int textColor = getThemedColor(Theme.key_undo_infoColor);
int alpha = Color.alpha(textColor);
textPaint.setTextSize(AndroidUtilities.dp(15));
textPaint.setColor(textColor);
selectionPaint.setColor(textColor);
selectionPaint.setAlpha((int) (alpha * 0.14));
setBackground(Theme.createRoundRectDrawable(AndroidUtilities.dp(6), getThemedColor(Theme.key_undo_background)));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getMeasuredWidth() != lastW || textLayout == null) {
if (a != null) {
a.removeAllListeners();
a.cancel();
}
String text = LocaleController.getString("TextSelectionHit", R.string.TextSelectionHit);
Pattern pattern = Pattern.compile("\\*\\*.*\\*\\*");
Matcher matcher = pattern.matcher(text);
String word = null;
if (matcher.matches()) {
word = matcher.group();
}
text = text.replace("**", "");
textLayout = new StaticLayout(text, textPaint, getMeasuredWidth() - padding * 2, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
start = 0;
end = 0;
if (word != null) {
start = text.indexOf(word);
}
if (start > 0) {
end = start + word.length();
} else {
int k = 0;
for (int i = 0; i < text.length(); i++) {
if (text.charAt(i) == ' ') {
k++;
if (k == 2) {
start = i + 1;
}
if (k == 3) {
end = i - 1;
}
}
}
}
if (end == 0) {
end = text.length();
}
animateToStart = 0;
animateToEnd = textLayout.getOffsetForHorizontal(textLayout.getLineForOffset(end), textLayout.getWidth() - 1);
currentStart = start;
currentEnd = end;
if (showing) {
prepareProgress = 1f;
enterValue = 1f;
currentStart = animateToStart;
currentEnd = animateToEnd;
startOffsetValue = 0;
endOffsetValue = 0;
} else if (showOnMeasure) {
show();
}
showOnMeasure = false;
lastW = getMeasuredWidth();
}
int h = textLayout.getHeight() + AndroidUtilities.dp(8) * 2;
if (h < AndroidUtilities.dp(56)) {
h = AndroidUtilities.dp(56);
}
setMeasuredDimension(getMeasuredWidth(), h);
}
Path path = new Path();
@Override
protected void onDraw(Canvas canvas) {
if (textLayout == null) {
return;
}
super.onDraw(canvas);
canvas.save();
int topPadding = (getMeasuredHeight() - textLayout.getHeight()) >> 1;
canvas.translate(padding, topPadding);
if (enterValue != 0) {
drawSelection(canvas, textLayout, currentStart, currentEnd);
}
textLayout.draw(canvas);
int handleViewSize = AndroidUtilities.dp(14);
int line = textLayout.getLineForOffset(currentEnd);
float x = textLayout.getPrimaryHorizontal(currentEnd);
int y = textLayout.getLineBottom(line);
if (currentEnd == animateToEnd) {
roundedRect(path, textLayout.getPrimaryHorizontal(animateToEnd), textLayout.getLineTop(line), textLayout.getPrimaryHorizontal(animateToEnd) + AndroidUtilities.dpf2(4), textLayout.getLineBottom(line), AndroidUtilities.dpf2(4), AndroidUtilities.dpf2(4), false, true);
canvas.drawPath(path, selectionPaint);
}
float enterProgress = interpolator.getInterpolation(enterValue);
int xOffset = (int) (textLayout.getPrimaryHorizontal(animateToEnd) + (AndroidUtilities.dpf2(4) * (1f - endOffsetValue)) + (textLayout.getPrimaryHorizontal(end) - textLayout.getPrimaryHorizontal(animateToEnd)) * endOffsetValue);
canvas.save();
canvas.translate(xOffset, y);
canvas.scale(enterProgress, enterProgress, handleViewSize / 2f, handleViewSize / 2f);
path.reset();
path.addCircle(handleViewSize / 2f, handleViewSize / 2f, handleViewSize / 2f, Path.Direction.CCW);
path.addRect(0, 0, handleViewSize / 2f, handleViewSize / 2f, Path.Direction.CCW);
canvas.drawPath(path, textPaint);
canvas.restore();
line = textLayout.getLineForOffset(currentStart);
x = textLayout.getPrimaryHorizontal(currentStart);
y = textLayout.getLineBottom(line);
if (currentStart == animateToStart) {
roundedRect(path, -AndroidUtilities.dp(4), textLayout.getLineTop(line), 0, textLayout.getLineBottom(line), AndroidUtilities.dp(4), AndroidUtilities.dp(4), true, false);
canvas.drawPath(path, selectionPaint);
}
canvas.save();
xOffset = (int) (textLayout.getPrimaryHorizontal(animateToStart) - (AndroidUtilities.dp(4) * (1f - startOffsetValue)) + (textLayout.getPrimaryHorizontal(start) - textLayout.getPrimaryHorizontal(animateToStart)) * startOffsetValue);
canvas.translate(xOffset - handleViewSize, y);
canvas.scale(enterProgress, enterProgress, handleViewSize / 2f, handleViewSize / 2f);
path.reset();
path.addCircle(handleViewSize / 2f, handleViewSize / 2f, handleViewSize / 2f, Path.Direction.CCW);
path.addRect(handleViewSize / 2f, 0, handleViewSize, handleViewSize / 2f, Path.Direction.CCW);
canvas.drawPath(path, textPaint);
canvas.restore();
canvas.restore();
}
private void roundedRect(Path path, float left, float top, float right, float bottom, float rx, float ry,
boolean tl, boolean tr) {
path.reset();
if (rx < 0) rx = 0;
if (ry < 0) ry = 0;
float width = right - left;
float height = bottom - top;
if (rx > width / 2) rx = width / 2;
if (ry > height / 2) ry = height / 2;
float widthMinusCorners = (width - (2 * rx));
float heightMinusCorners = (height - (2 * ry));
path.moveTo(right, top + ry);
if (tr)
path.rQuadTo(0, -ry, -rx, -ry);
else {
path.rLineTo(0, -ry);
path.rLineTo(-rx, 0);
}
path.rLineTo(-widthMinusCorners, 0);
if (tl)
path.rQuadTo(-rx, 0, -rx, ry);
else {
path.rLineTo(-rx, 0);
path.rLineTo(0, ry);
}
path.rLineTo(0, heightMinusCorners);
path.rLineTo(0, ry);
path.rLineTo(rx, 0);
path.rLineTo(widthMinusCorners, 0);
path.rLineTo(rx, 0);
path.rLineTo(0, -ry);
path.rLineTo(0, -heightMinusCorners);
path.close();
}
private void drawSelection(Canvas canvas, StaticLayout layout, int selectionStart, int selectionEnd) {
int startLine = layout.getLineForOffset(selectionStart);
int endLine = layout.getLineForOffset(selectionEnd);
int startX = (int) layout.getPrimaryHorizontal(selectionStart);
int endX = (int) layout.getPrimaryHorizontal(selectionEnd);
if (startLine == endLine) {
canvas.drawRect(startX, layout.getLineTop(startLine), endX, layout.getLineBottom(startLine), selectionPaint);
} else {
canvas.drawRect(startX, layout.getLineTop(startLine), layout.getLineWidth(startLine), layout.getLineBottom(startLine), selectionPaint);
canvas.drawRect(0, layout.getLineTop(endLine), endX, layout.getLineBottom(endLine), selectionPaint);
for (int i = startLine + 1; i < endLine; i++) {
canvas.drawRect(0, layout.getLineTop(i), layout.getLineWidth(i), layout.getLineBottom(i), selectionPaint);
}
}
}
public void show() {
AndroidUtilities.cancelRunOnUIThread(dismissTunnable);
if (a != null) {
a.removeAllListeners();
a.cancel();
}
if (getMeasuredHeight() == 0 || getMeasuredWidth() == 0) {
showOnMeasure = true;
return;
}
showing = true;
setVisibility(View.VISIBLE);
prepareProgress = 0;
enterValue = 0;
currentStart = start;
currentEnd = end;
startOffsetValue = 1f;
endOffsetValue = 1f;
invalidate();
ValueAnimator prepareAnimation = ValueAnimator.ofFloat(0, 1f);
prepareAnimation.addUpdateListener(animation -> {
prepareProgress = (float) animation.getAnimatedValue();
invalidate();
});
prepareAnimation.setDuration(210);
prepareAnimation.setInterpolator(new DecelerateInterpolator());
ValueAnimator enterAnimation = ValueAnimator.ofFloat(0, 1f);
enterAnimation.addUpdateListener(animation -> {
enterValue = (float) animation.getAnimatedValue();
invalidate();
});
enterAnimation.setStartDelay(600);
enterAnimation.setDuration(250);
ValueAnimator moveStart = ValueAnimator.ofFloat(1f, 0f);
moveStart.setStartDelay(500);
moveStart.addUpdateListener(animation -> {
startOffsetValue = (float) animation.getAnimatedValue();
currentStart = (int) (animateToStart + ((start - animateToStart) * startOffsetValue));
invalidate();
});
moveStart.setInterpolator(CubicBezierInterpolator.EASE_OUT);
moveStart.setDuration(500);
ValueAnimator moveEnd = ValueAnimator.ofFloat(1f, 0f);
moveEnd.setStartDelay(400);
moveEnd.addUpdateListener(animation -> {
endOffsetValue = (float) animation.getAnimatedValue();
currentEnd = animateToEnd + (int) Math.ceil((end - animateToEnd) * endOffsetValue);
invalidate();
});
moveEnd.setInterpolator(CubicBezierInterpolator.EASE_OUT);
moveEnd.setDuration(900);
AnimatorSet set = new AnimatorSet();
set.playSequentially(
prepareAnimation,
enterAnimation,
moveStart,
moveEnd
);
a = set;
a.start();
AndroidUtilities.runOnUIThread(dismissTunnable, 5000);
}
public void hide() {
AndroidUtilities.cancelRunOnUIThread(dismissTunnable);
hideInternal();
}
private void hideInternal() {
if (a != null) {
a.removeAllListeners();
a.cancel();
}
showing = false;
ValueAnimator animator = ValueAnimator.ofFloat(prepareProgress, 0f);
animator.addUpdateListener(animation -> {
prepareProgress = (float) animation.getAnimatedValue();
invalidate();
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
setVisibility(View.INVISIBLE);
}
});
a = animator;
a.start();
}
public float getPrepareProgress() {
return prepareProgress;
}
private int getThemedColor(String key) {
Integer color = resourcesProvider != null ? resourcesProvider.getColor(key) : null;
return color != null ? color : Theme.getColor(key);
}
}