mirror of https://github.com/NekoX-Dev/NekoX.git
1099 lines
46 KiB
Java
1099 lines
46 KiB
Java
package org.telegram.ui.Components;
|
|
|
|
import android.animation.Animator;
|
|
import android.animation.AnimatorListenerAdapter;
|
|
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.graphics.PorterDuff;
|
|
import android.graphics.PorterDuffColorFilter;
|
|
import android.graphics.RectF;
|
|
import android.graphics.Rect;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.os.Build;
|
|
import android.util.Log;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.view.animation.OvershootInterpolator;
|
|
import android.widget.HorizontalScrollView;
|
|
import android.widget.ImageView;
|
|
import android.widget.LinearLayout;
|
|
import android.widget.ViewAnimator;
|
|
|
|
import androidx.core.graphics.ColorUtils;
|
|
import androidx.core.math.MathUtils;
|
|
|
|
import org.telegram.messenger.AndroidUtilities;
|
|
import org.telegram.messenger.ImageReceiver;
|
|
import org.telegram.messenger.MediaDataController;
|
|
import org.telegram.messenger.MessageObject;
|
|
import org.telegram.messenger.R;
|
|
import org.telegram.messenger.UserConfig;
|
|
import org.telegram.tgnet.TLRPC;
|
|
import org.telegram.ui.ActionBar.Theme;
|
|
import org.telegram.ui.Components.Premium.PremiumLockIconView;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
|
|
public class EmojiTabsStrip extends ScrollableHorizontalScrollView {
|
|
|
|
private int recentDrawableId = R.drawable.msg_emoji_recent;
|
|
private static int[] emojiTabsDrawableIds = {
|
|
R.drawable.msg_emoji_smiles,
|
|
R.drawable.msg_emoji_cat,
|
|
R.drawable.msg_emoji_food,
|
|
R.drawable.msg_emoji_activities,
|
|
R.drawable.msg_emoji_travel,
|
|
R.drawable.msg_emoji_objects,
|
|
R.drawable.msg_emoji_other,
|
|
R.drawable.msg_emoji_flags
|
|
};
|
|
private static int[] emojiTabsAnimatedDrawableIds = {
|
|
R.raw.msg_emoji_smiles,
|
|
R.raw.msg_emoji_cat,
|
|
R.raw.msg_emoji_food,
|
|
R.raw.msg_emoji_activities,
|
|
R.raw.msg_emoji_travel,
|
|
R.raw.msg_emoji_objects,
|
|
R.raw.msg_emoji_other,
|
|
R.raw.msg_emoji_flags
|
|
};
|
|
private int settingsDrawableId = R.drawable.smiles_tab_settings;
|
|
|
|
private Theme.ResourcesProvider resourcesProvider;
|
|
private EmojiView parent;
|
|
private boolean includeAnimated;
|
|
private LinearLayout contentView;
|
|
|
|
private EmojiTabButton recentTab;
|
|
private EmojiTabButton settingsTab;
|
|
private EmojiTabsView emojiTabs;
|
|
private ArrayList<EmojiTabButton> emojipackTabs;
|
|
private HashMap<View, Rect> removingViews = new HashMap<>();
|
|
|
|
private ValueAnimator selectAnimator;
|
|
private float selectT = 0f;
|
|
private float selectAnimationT = 0f;
|
|
private int selected = 0;
|
|
private int wasIndex = 0;
|
|
|
|
private Runnable onSettingsOpenRunnable;
|
|
private boolean wasDrawn;
|
|
|
|
public EmojiTabsStrip(Context context, Theme.ResourcesProvider resourcesProvider, boolean includeAnimated, EmojiView parent, Runnable onSettingsOpen) {
|
|
super(context);
|
|
this.parent = parent;
|
|
this.includeAnimated = includeAnimated;
|
|
this.resourcesProvider = resourcesProvider;
|
|
this.onSettingsOpenRunnable = onSettingsOpen;
|
|
|
|
contentView = new LinearLayout(context) {
|
|
|
|
HashMap<Integer, Integer> lastX = new HashMap<>();
|
|
|
|
@Override
|
|
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
|
int cy = (b - t) / 2;
|
|
if (includeAnimated) {
|
|
int x = getPaddingLeft() - (!recentIsShown ? AndroidUtilities.dp(30 + 3) : 0);
|
|
for (int i = 0; i < getChildCount(); ++i) {
|
|
View child = getChildAt(i);
|
|
if (child == settingsTab || removingViews.containsKey(child)) {
|
|
continue;
|
|
}
|
|
if (child != null) {
|
|
child.layout(x, cy - child.getMeasuredHeight() / 2, x + child.getMeasuredWidth(), cy + child.getMeasuredHeight() / 2);
|
|
Integer id = child instanceof EmojiTabButton ? ((EmojiTabButton) child).id : (child instanceof EmojiTabsView ? ((EmojiTabsView) child).id : null);
|
|
if (child instanceof EmojiTabButton && ((EmojiTabButton) child).newly) {
|
|
((EmojiTabButton) child).newly = false;
|
|
child.setScaleX(0);
|
|
child.setScaleY(0);
|
|
child.setAlpha(0);
|
|
child.animate().scaleX(1f).scaleY(1f).alpha(1f).setDuration(200).setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT).start();
|
|
}
|
|
if (id != null) {
|
|
if (lastX.get(id) != null && lastX.get(id) != x) {
|
|
child.setTranslationX(lastX.get(id) - x);
|
|
child.animate().translationX(0).setDuration(250).setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT).start();
|
|
}
|
|
lastX.put(id, x);
|
|
}
|
|
x += child.getMeasuredWidth() + AndroidUtilities.dp(3);
|
|
}
|
|
}
|
|
if (settingsTab != null) {
|
|
x += (!recentIsShown ? AndroidUtilities.dp(30 + 3) : 0);
|
|
Integer id = settingsTab.id;
|
|
if (x + settingsTab.getMeasuredWidth() + getPaddingRight() <= EmojiTabsStrip.this.getMeasuredWidth()) {
|
|
settingsTab.layout(x = (r - l - getPaddingRight() - settingsTab.getMeasuredWidth()), cy - settingsTab.getMeasuredHeight() / 2, r - l - getPaddingRight(), cy + settingsTab.getMeasuredHeight() / 2);
|
|
} else {
|
|
settingsTab.layout(x, cy - settingsTab.getMeasuredHeight() / 2, x + settingsTab.getMeasuredWidth(), cy + settingsTab.getMeasuredHeight() / 2);
|
|
}
|
|
if (id != null) {
|
|
if (lastX.get(id) != null && lastX.get(id) != x) {
|
|
settingsTab.setTranslationX(lastX.get(id) - x);
|
|
settingsTab.animate().translationX(0).setDuration(350).start();
|
|
}
|
|
lastX.put(id, x);
|
|
}
|
|
}
|
|
} else {
|
|
final int childCount = getChildCount() - (!recentIsShown ? 1 : 0);
|
|
int margin = (int) ((r - l - getPaddingLeft() - getPaddingRight() - childCount * AndroidUtilities.dp(30)) / (float) Math.max(1, childCount - 1));
|
|
int x = getPaddingLeft();
|
|
for (int i = 0; i < childCount; ++i) {
|
|
View child = getChildAt((!recentIsShown ? 1 : 0) + i);
|
|
if (child != null) {
|
|
child.layout(x, cy - child.getMeasuredHeight() / 2, x + child.getMeasuredWidth(), cy + child.getMeasuredHeight() / 2);
|
|
x += child.getMeasuredWidth() + margin;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
final int AT_MOST = MeasureSpec.makeMeasureSpec(99999999, MeasureSpec.AT_MOST);
|
|
int width = getPaddingLeft() + getPaddingRight() - (int) (recentIsShown ? 0 : recentTab.getAlpha() * AndroidUtilities.dp(30 + 3));
|
|
for (int i = 0; i < getChildCount(); ++i) {
|
|
View child = getChildAt(i);
|
|
if (child != null) {
|
|
child.measure(AT_MOST, heightMeasureSpec);
|
|
width += child.getMeasuredWidth() + (i + 1 < getChildCount() ? AndroidUtilities.dp(3) : 0);
|
|
}
|
|
}
|
|
if (!includeAnimated) {
|
|
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
|
|
return;
|
|
}
|
|
setMeasuredDimension(Math.max(width, MeasureSpec.getSize(widthMeasureSpec)), MeasureSpec.getSize(heightMeasureSpec));
|
|
}
|
|
|
|
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
private RectF from = new RectF();
|
|
private RectF to = new RectF();
|
|
private RectF rect = new RectF();
|
|
private Path path = new Path();
|
|
@Override
|
|
protected void dispatchDraw(Canvas canvas) {
|
|
for (Map.Entry<View, Rect> entry : removingViews.entrySet()) {
|
|
View view = entry.getKey();
|
|
Rect bounds = entry.getValue();
|
|
canvas.save();
|
|
canvas.translate(bounds.left, bounds.top);
|
|
canvas.scale(view.getScaleX(), view.getScaleY(), bounds.width() / 2f, bounds.height() / 2f);
|
|
view.draw(canvas);
|
|
canvas.restore();
|
|
}
|
|
|
|
int selectFrom = (int) Math.floor(selectT), selectTo = (int) Math.ceil(selectT);
|
|
getChildBounds(selectFrom, from);
|
|
getChildBounds(selectTo, to);
|
|
AndroidUtilities.lerp(from, to, selectT - selectFrom, rect);
|
|
float isEmojiTabs = emojiTabs == null ? 0 : MathUtils.clamp(1f - Math.abs(selectT - 1), 0, 1);
|
|
float isMiddle = 4f * selectAnimationT * (1f - selectAnimationT);
|
|
float hw = rect.width() / 2 * (1f + isMiddle * .3f);
|
|
float hh = rect.height() / 2 * (1f - isMiddle * .05f);
|
|
rect.set(rect.centerX() - hw, rect.centerY() - hh, rect.centerX() + hw, rect.centerY() + hh);
|
|
float r = AndroidUtilities.dp(AndroidUtilities.lerp(8f, 16f, isEmojiTabs));
|
|
paint.setColor(selectorColor());
|
|
|
|
path.rewind();
|
|
path.addRoundRect(rect, r, r, Path.Direction.CW);
|
|
canvas.drawPath(path, paint);
|
|
|
|
if (emojiTabs != null) {
|
|
path.addCircle(emojiTabs.getLeft() + AndroidUtilities.dp(15), (emojiTabs.getTop() + emojiTabs.getBottom()) / 2f, AndroidUtilities.dp(15), Path.Direction.CW);
|
|
}
|
|
|
|
super.dispatchDraw(canvas);
|
|
wasDrawn = true;
|
|
}
|
|
|
|
@Override
|
|
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
|
|
if (child == emojiTabs) {
|
|
canvas.save();
|
|
canvas.clipPath(path);
|
|
boolean res = super.drawChild(canvas, child, drawingTime);
|
|
canvas.restore();
|
|
return res;
|
|
}
|
|
return super.drawChild(canvas, child, drawingTime);
|
|
}
|
|
|
|
private void getChildBounds(int i, RectF out) {
|
|
View child = getChildAt(MathUtils.clamp(i, 0, getChildCount() - 1));
|
|
out.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
|
|
// out.offset(recentIsShown ? recentTab.getTranslationX() : AndroidUtilities.dp(30 + 3) - recentTab.getTranslationX(), 0);
|
|
}
|
|
};
|
|
contentView.setClipToPadding(false);
|
|
contentView.setPadding(AndroidUtilities.dp(5 + 6), 0, AndroidUtilities.dp(3), 0);
|
|
contentView.setOrientation(LinearLayout.HORIZONTAL);
|
|
setVerticalScrollBarEnabled(false);
|
|
setHorizontalScrollBarEnabled(false);
|
|
addView(contentView);
|
|
|
|
contentView.addView(recentTab = new EmojiTabButton(context, recentDrawableId, false, false));
|
|
recentTab.id = "recent".hashCode();
|
|
if (!includeAnimated) {
|
|
for (int i = 0; i < emojiTabsDrawableIds.length; ++i) {
|
|
contentView.addView(new EmojiTabButton(context, emojiTabsDrawableIds[i], false, i == 0));
|
|
}
|
|
updateClickListeners();
|
|
} else {
|
|
contentView.addView(emojiTabs = new EmojiTabsView(context));
|
|
emojiTabs.id = "tabs".hashCode();
|
|
if (onSettingsOpen != null) {
|
|
contentView.addView(settingsTab = new EmojiTabButton(context, settingsDrawableId, false, true));
|
|
settingsTab.id = "settings".hashCode();
|
|
settingsTab.setAlpha(0);
|
|
}
|
|
updateClickListeners();
|
|
updateEmojiPacks();
|
|
}
|
|
}
|
|
|
|
private boolean recentFirstChange = true;
|
|
private boolean recentIsShown = true;
|
|
public void showRecent(boolean show) {
|
|
if (recentIsShown == show) {
|
|
return;
|
|
}
|
|
recentIsShown = show;
|
|
if (recentFirstChange) {
|
|
recentTab.setAlpha(show ? 1f : 0f);
|
|
} else {
|
|
recentTab.animate().alpha(show ? 1f : 0f).setDuration(200).setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT).start();
|
|
}
|
|
if (!show && selected == 0 || show && selected == 1) {
|
|
select(0, !recentFirstChange);
|
|
}
|
|
contentView.requestLayout();
|
|
recentFirstChange = false;
|
|
}
|
|
|
|
private boolean isFreeEmojiPack(TLRPC.StickerSet set, ArrayList<TLRPC.Document> documents) {
|
|
if (set == null || documents == null) {
|
|
return false;
|
|
}
|
|
for (int i = 0; i < documents.size(); ++i) {
|
|
if (!MessageObject.isFreeEmoji(documents.get(i))) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private TLRPC.Document getThumbDocument(TLRPC.StickerSet set, ArrayList<TLRPC.Document> documents) {
|
|
if (set == null) {
|
|
return null;
|
|
}
|
|
if (documents != null) {
|
|
for (int i = 0; i < documents.size(); ++i) {
|
|
TLRPC.Document d = documents.get(i);
|
|
if (d.id == set.thumb_document_id) {
|
|
return d;
|
|
}
|
|
}
|
|
}
|
|
if (documents != null && documents.size() >= 1) {
|
|
return documents.get(0);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
boolean first = true;
|
|
private ValueAnimator appearAnimation;
|
|
private int appearCount;
|
|
public void updateEmojiPacks() {
|
|
if (!includeAnimated) {
|
|
return;
|
|
}
|
|
if (first && !MediaDataController.getInstance(UserConfig.selectedAccount).areStickersLoaded(MediaDataController.TYPE_EMOJIPACKS)) {
|
|
return;
|
|
}
|
|
first = false;
|
|
ArrayList<EmojiView.EmojiPack> emojiPacks = parent.getEmojipacks();
|
|
if (emojiPacks == null) {
|
|
return;
|
|
}
|
|
if (emojipackTabs == null) {
|
|
emojipackTabs = new ArrayList<>();
|
|
}
|
|
boolean first = emojipackTabs.size() == 0 && emojiPacks.size() > 0 && appearCount != emojiPacks.size() && wasDrawn;
|
|
boolean doAppearAnimation = false;//emojipackTabs.size() == 0 && emojiPacks.size() > 0 && appearCount != emojiPacks.size() && wasDrawn;
|
|
if (appearAnimation != null && appearCount != emojiPacks.size()) {
|
|
appearAnimation.cancel();
|
|
appearAnimation = null;
|
|
}
|
|
appearCount = emojiPacks.size();
|
|
final boolean isPremium = UserConfig.getInstance(UserConfig.selectedAccount).isPremium();
|
|
for (int i = 0; i < emojipackTabs.size(); ++i) {
|
|
EmojiTabButton emojipackTab = emojipackTabs.get(i);
|
|
EmojiView.EmojiPack pack = null;
|
|
if (emojipackTab != null && emojipackTab.id != null) {
|
|
for (int j = 0; j < emojiPacks.size(); ++j) {
|
|
EmojiView.EmojiPack p = emojiPacks.get(j);
|
|
final int id = Objects.hash(p.set.id, p.featured);
|
|
if (id == emojipackTab.id) {
|
|
pack = p;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pack == null && emojipackTab != null) {
|
|
Rect bounds = new Rect();
|
|
bounds.set(emojipackTab.getLeft(), emojipackTab.getTop(), emojipackTab.getRight(), emojipackTab.getBottom());
|
|
removingViews.put(emojipackTab, bounds);
|
|
ValueAnimator anm = ValueAnimator.ofFloat(emojipackTab.getAlpha(), 0f);
|
|
anm.addUpdateListener(a -> {
|
|
float alpha = (float) a.getAnimatedValue();
|
|
emojipackTab.setAlpha(alpha);
|
|
emojipackTab.setScaleX(alpha);
|
|
emojipackTab.setScaleY(alpha);
|
|
contentView.invalidate();
|
|
});
|
|
anm.addListener(new AnimatorListenerAdapter() {
|
|
@Override
|
|
public void onAnimationEnd(Animator animation) {
|
|
removingViews.remove(emojipackTab);
|
|
contentView.invalidate();
|
|
}
|
|
});
|
|
anm.setDuration(200);
|
|
anm.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT);
|
|
anm.start();
|
|
emojipackTabs.remove(i--);
|
|
}
|
|
contentView.removeView(emojipackTab);
|
|
}
|
|
for (int i = 0; i < emojiPacks.size(); ++i) {
|
|
EmojiView.EmojiPack pack = emojiPacks.get(i);
|
|
final int id = Objects.hash(pack.set.id, pack.featured);
|
|
EmojiTabButton emojipackTab = null;
|
|
for (int j = 0; j < emojipackTabs.size(); ++j) {
|
|
EmojiTabButton tab = emojipackTabs.get(j);
|
|
if (tab != null && tab.id != null && tab.id == id) {
|
|
emojipackTab = tab;
|
|
break;
|
|
}
|
|
}
|
|
final boolean free = isFreeEmojiPack(pack.set, pack.documents);
|
|
AnimatedEmojiDrawable drawable = emojipackTab == null ? null : (AnimatedEmojiDrawable) emojipackTab.getDrawable();
|
|
TLRPC.Document thumbDocument = getThumbDocument(pack.set, pack.documents);
|
|
if (thumbDocument != null && (drawable == null || drawable.getDocumentId() != thumbDocument.id)) {
|
|
drawable = AnimatedEmojiDrawable.make(UserConfig.selectedAccount, AnimatedEmojiDrawable.CACHE_TYPE_TAB_STRIP, thumbDocument);
|
|
}
|
|
if (emojipackTab == null) {
|
|
emojipackTab = new EmojiTabButton(getContext(), drawable, free, false,false);
|
|
emojipackTab.id = id;
|
|
if (drawable != null) {
|
|
drawable.addView(emojipackTab.imageView);
|
|
}
|
|
emojipackTabs.add(emojipackTab);
|
|
} else {
|
|
if (emojipackTab.getDrawable() instanceof AnimatedEmojiDrawable) {
|
|
((AnimatedEmojiDrawable) emojipackTab.getDrawable()).removeView(emojipackTab.imageView);
|
|
}
|
|
emojipackTab.setDrawable(drawable);
|
|
if (drawable != null) {
|
|
drawable.addView(emojipackTab.imageView);
|
|
}
|
|
}
|
|
if (!isPremium && !free) {
|
|
emojipackTab.setLock(true);
|
|
} else if (!pack.installed && !parent.installedEmojiSets.contains(pack.set.id)) {
|
|
emojipackTab.setLock(false);
|
|
} else {
|
|
emojipackTab.setLock(null);
|
|
}
|
|
if (doAppearAnimation && !first) {
|
|
emojipackTab.newly = false;
|
|
}
|
|
if (emojipackTab.getParent() instanceof ViewGroup) {
|
|
((ViewGroup) emojipackTab.getParent()).removeView(emojipackTab);
|
|
}
|
|
contentView.addView(emojipackTab);
|
|
}
|
|
if (settingsTab != null) {
|
|
settingsTab.bringToFront();
|
|
if (settingsTab.getAlpha() < 1) {
|
|
settingsTab.animate().alpha(1f).setDuration(200).setInterpolator(CubicBezierInterpolator.DEFAULT).start();
|
|
}
|
|
}
|
|
if (doAppearAnimation) {
|
|
if (emojipackTabs != null) {
|
|
for (int i = 0; i < emojipackTabs.size(); ++i) {
|
|
emojipackTabs.get(i).setScaleX(0);
|
|
emojipackTabs.get(i).setScaleY(0);
|
|
}
|
|
}
|
|
appearAnimation = ValueAnimator.ofFloat(0, 1);
|
|
final OvershootInterpolator innerInterpolator = new OvershootInterpolator(3f);
|
|
appearAnimation.addUpdateListener(anm -> {
|
|
if (emojipackTabs == null) {
|
|
return;
|
|
}
|
|
final float t = (float) anm.getAnimatedValue();
|
|
final int count = emojipackTabs.size();
|
|
final float dur = 1f / count * 4.5f;
|
|
for (int i = 0; i < count; ++i) {
|
|
final float off = i / (float) count * (1f - dur);
|
|
final float T = MathUtils.clamp((t - off) / dur, 0, 1);
|
|
final float scale = innerInterpolator.getInterpolation(T);
|
|
emojipackTabs.get(i).setScaleX(scale);
|
|
emojipackTabs.get(i).setScaleY(scale);
|
|
}
|
|
});
|
|
appearAnimation.addListener(new AnimatorListenerAdapter() {
|
|
@Override
|
|
public void onAnimationCancel(Animator animation) {
|
|
if (emojipackTabs == null) {
|
|
return;
|
|
}
|
|
for (int i = 0; i < emojipackTabs.size(); ++i) {
|
|
emojipackTabs.get(i).setScaleX(1);
|
|
emojipackTabs.get(i).setScaleY(1);
|
|
}
|
|
}
|
|
});
|
|
appearAnimation.setStartDelay(150);
|
|
appearAnimation.setDuration((emojipackTabs == null ? 0 : emojipackTabs.size()) * 75L);
|
|
appearAnimation.setInterpolator(CubicBezierInterpolator.EASE_OUT);
|
|
appearAnimation.start();
|
|
}
|
|
updateClickListeners();
|
|
}
|
|
|
|
public void updateClickListeners() {
|
|
for (int i = 0, j = 0; i < contentView.getChildCount(); ++i, ++j) {
|
|
View child = contentView.getChildAt(i);
|
|
if (child instanceof EmojiTabsView) {
|
|
EmojiTabsView tabsView = (EmojiTabsView) child;
|
|
for (int a = 0; a < tabsView.contentView.getChildCount(); ++a, ++j) {
|
|
final int index = j;
|
|
tabsView.contentView.getChildAt(a).setOnClickListener(e -> {
|
|
onTabClick(index);
|
|
});
|
|
}
|
|
--j;
|
|
} else if (child != null) {
|
|
final int index = j;
|
|
child.setOnClickListener(e -> {
|
|
onTabClick(index);
|
|
});
|
|
}
|
|
}
|
|
if (settingsTab != null) {
|
|
settingsTab.setOnClickListener(e -> {
|
|
if (onSettingsOpenRunnable != null) {
|
|
onSettingsOpenRunnable.run();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
protected boolean onTabClick(int index) {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
contentView.setPadding(AndroidUtilities.dp(5 + 6), 0, AndroidUtilities.dp(5 + 6), 0);
|
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
|
}
|
|
|
|
public void updateColors() {
|
|
if (recentTab != null) {
|
|
recentTab.updateColor();
|
|
}
|
|
}
|
|
|
|
public void select(int index) {
|
|
select(index, true);
|
|
}
|
|
|
|
public void select(int index, boolean animated) {
|
|
animated = animated && !first;
|
|
if (!recentIsShown) {
|
|
index = Math.max(1, index);
|
|
}
|
|
final int wasSelected = selected;
|
|
for (int i = 0, j = 0; i < contentView.getChildCount(); ++i, ++j) {
|
|
View child = contentView.getChildAt(i);
|
|
int from = j;
|
|
if (child instanceof EmojiTabsView) {
|
|
EmojiTabsView tabs = (EmojiTabsView) child;
|
|
for (int a = 0; a < tabs.contentView.getChildCount(); ++a, ++j) {
|
|
View child2 = tabs.contentView.getChildAt(a);
|
|
if (child2 instanceof EmojiTabButton) {
|
|
((EmojiTabButton) child2).updateSelect(index == j, animated);
|
|
}
|
|
}
|
|
--j;
|
|
} else if (child instanceof EmojiTabButton) {
|
|
((EmojiTabButton) child).updateSelect(index == j, animated);
|
|
}
|
|
if (index >= from && index <= j) {
|
|
selected = i;
|
|
}
|
|
}
|
|
if (wasSelected != selected) {
|
|
if (selectAnimator != null) {
|
|
selectAnimator.cancel();
|
|
}
|
|
final float from = selectT, to = selected;
|
|
if (animated) {
|
|
selectAnimator = ValueAnimator.ofFloat(0, 1);
|
|
selectAnimator.addUpdateListener(a -> {
|
|
selectAnimationT = (float) a.getAnimatedValue();
|
|
selectT = AndroidUtilities.lerp(from, to, selectAnimationT);
|
|
contentView.invalidate();
|
|
});
|
|
selectAnimator.setDuration(350);
|
|
selectAnimator.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT);
|
|
selectAnimator.start();
|
|
} else {
|
|
selectAnimationT = 1f;
|
|
selectT = AndroidUtilities.lerp(from, to, selectAnimationT);
|
|
contentView.invalidate();
|
|
}
|
|
|
|
if (emojiTabs != null) {
|
|
emojiTabs.show(selected == 1, animated);
|
|
}
|
|
|
|
View child = contentView.getChildAt(selected);
|
|
if (selected >= 2) {
|
|
scrollToVisible(child.getLeft(), child.getRight());
|
|
} else {
|
|
scrollTo(0);
|
|
}
|
|
}
|
|
|
|
if (wasIndex != index) {
|
|
if (emojiTabs != null && selected == 1 && index >= 1 && index <= 1 + emojiTabs.contentView.getChildCount()) {
|
|
emojiTabs.scrollToVisible(AndroidUtilities.dp(36 * (index - 1) - 6), AndroidUtilities.dp(36 * (index - 1) - 6 + 30));
|
|
}
|
|
wasIndex = index;
|
|
}
|
|
}
|
|
|
|
private int selectorColor() {
|
|
return 0x2effffff & Theme.getColor(Theme.key_chat_emojiPanelIcon, resourcesProvider);
|
|
}
|
|
|
|
private class EmojiTabButton extends ViewGroup {
|
|
public boolean shown = true;
|
|
|
|
public Integer id;
|
|
public boolean newly;
|
|
|
|
private ImageView imageView;
|
|
private RLottieDrawable lottieDrawable;
|
|
private PremiumLockIconView lockView;
|
|
private boolean round, forceSelector;
|
|
|
|
public EmojiTabButton(Context context, int drawableId, int lottieId, boolean roundSelector, boolean forceSelector) {
|
|
super(context);
|
|
this.round = roundSelector;
|
|
this.forceSelector = forceSelector;
|
|
if (round) {
|
|
setBackground(Theme.createCircleSelectorDrawable(selectorColor(), 0, 0));
|
|
} else if (forceSelector) {
|
|
setBackground(Theme.createRadSelectorDrawable(selectorColor(), 8, 8));
|
|
}
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
lottieDrawable = new RLottieDrawable(lottieId, "" + lottieId, AndroidUtilities.dp(24), AndroidUtilities.dp(24), false, null);
|
|
lottieDrawable.setBounds(AndroidUtilities.dp(3), AndroidUtilities.dp(3), AndroidUtilities.dp(27), AndroidUtilities.dp(27));
|
|
lottieDrawable.setMasterParent(this);
|
|
lottieDrawable.setAllowDecodeSingleFrame(true);
|
|
lottieDrawable.start();
|
|
} else {
|
|
imageView = new ImageView(context);
|
|
imageView.setImageDrawable(context.getResources().getDrawable(drawableId).mutate());
|
|
addView(imageView);
|
|
}
|
|
setColor(Theme.getColor(Theme.key_chat_emojiPanelIcon, resourcesProvider));
|
|
}
|
|
|
|
public EmojiTabButton(Context context, int drawableId, boolean roundSelector, boolean forceSelector) {
|
|
super(context);
|
|
this.round = roundSelector;
|
|
this.forceSelector = forceSelector;
|
|
if (round) {
|
|
setBackground(Theme.createCircleSelectorDrawable(selectorColor(), 0, 0));
|
|
} else if (forceSelector) {
|
|
setBackground(Theme.createRadSelectorDrawable(selectorColor(), 8, 8));
|
|
}
|
|
|
|
imageView = new ImageView(context);
|
|
imageView.setImageDrawable(context.getResources().getDrawable(drawableId).mutate());
|
|
setColor(Theme.getColor(Theme.key_chat_emojiPanelIcon, resourcesProvider));
|
|
|
|
addView(imageView);
|
|
}
|
|
|
|
public EmojiTabButton(Context context, Drawable drawable, boolean free, boolean roundSelector, boolean forceSelector) {
|
|
super(context);
|
|
this.newly = true;
|
|
this.round = roundSelector;
|
|
this.forceSelector = forceSelector;
|
|
if (round) {
|
|
setBackground(Theme.createCircleSelectorDrawable(selectorColor(), 0, 0));
|
|
} else if (forceSelector) {
|
|
setBackground(Theme.createRadSelectorDrawable(selectorColor(), 8, 8));
|
|
}
|
|
|
|
imageView = new ImageView(context) {
|
|
@Override
|
|
public void invalidate() {
|
|
super.invalidate();
|
|
updateLockImageReceiver();
|
|
}
|
|
|
|
@Override
|
|
protected void dispatchDraw(Canvas canvas) {
|
|
if (getDrawable() != null) {
|
|
getDrawable().setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
|
|
getDrawable().setAlpha(255);
|
|
getDrawable().draw(canvas);
|
|
}
|
|
}
|
|
};
|
|
imageView.setImageDrawable(drawable);
|
|
addView(imageView);
|
|
|
|
lockView = new PremiumLockIconView(context, PremiumLockIconView.TYPE_STICKERS_PREMIUM_LOCKED);
|
|
lockView.setAlpha(0f);
|
|
lockView.setScaleX(0);
|
|
lockView.setScaleY(0);
|
|
updateLockImageReceiver();
|
|
addView(lockView);
|
|
}
|
|
|
|
@Override
|
|
protected void dispatchDraw(Canvas canvas) {
|
|
super.dispatchDraw(canvas);
|
|
if (lottieDrawable != null) {
|
|
lottieDrawable.draw(canvas);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean performClick() {
|
|
// if (lottieDrawable != null) {
|
|
// lottieDrawable.setProgress(0);
|
|
// AndroidUtilities.runOnUIThread(() -> lottieDrawable.start(), 75);
|
|
// }
|
|
return super.performClick();
|
|
}
|
|
|
|
private boolean wasVisible;
|
|
public void updateVisibilityInbounds(boolean visible, boolean ignore) {
|
|
if (!wasVisible && visible) {
|
|
if (lottieDrawable != null && !lottieDrawable.isRunning() && !ignore) {
|
|
lottieDrawable.setProgress(0);
|
|
lottieDrawable.start();
|
|
}
|
|
}
|
|
wasVisible = visible;
|
|
}
|
|
|
|
public void setLock(Boolean lock) {
|
|
if (lockView == null) {
|
|
return;
|
|
}
|
|
if (lock == null) {
|
|
updateLock(false);
|
|
} else {
|
|
updateLock(true);
|
|
if (lock) {
|
|
lockView.setImageResource(R.drawable.msg_mini_lockedemoji);
|
|
} else {
|
|
Drawable addIcon = getResources().getDrawable(R.drawable.msg_mini_addemoji).mutate();
|
|
addIcon.setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.MULTIPLY));
|
|
lockView.setImageDrawable(addIcon);
|
|
}
|
|
}
|
|
}
|
|
|
|
private float lockT;
|
|
private ValueAnimator lockAnimator;
|
|
private void updateLock(boolean enable) {
|
|
if (lockAnimator != null) {
|
|
lockAnimator.cancel();
|
|
}
|
|
if (Math.abs(lockT - (enable ? 1f : 0f)) < 0.01f) {
|
|
return;
|
|
}
|
|
lockView.setVisibility(View.VISIBLE);
|
|
lockAnimator = ValueAnimator.ofFloat(lockT, enable ? 1f : 0f);
|
|
lockAnimator.addUpdateListener(anm -> {
|
|
lockT = (float) anm.getAnimatedValue();
|
|
lockView.setScaleX(lockT);
|
|
lockView.setScaleY(lockT);
|
|
lockView.setAlpha(lockT);
|
|
});
|
|
lockAnimator.addListener(new AnimatorListenerAdapter() {
|
|
@Override
|
|
public void onAnimationEnd(Animator animation) {
|
|
if (!enable) {
|
|
lockView.setVisibility(View.GONE);
|
|
}
|
|
}
|
|
});
|
|
lockAnimator.setInterpolator(CubicBezierInterpolator.DEFAULT);
|
|
lockAnimator.setDuration(200);
|
|
lockAnimator.start();
|
|
}
|
|
|
|
public void updateLockImageReceiver() {
|
|
if (lockView != null && !lockView.ready() && getDrawable() instanceof AnimatedEmojiDrawable) {
|
|
ImageReceiver imageReceiver = ((AnimatedEmojiDrawable) getDrawable()).getImageReceiver();
|
|
if (imageReceiver != null) {
|
|
lockView.setImageReceiver(imageReceiver);
|
|
lockView.invalidate();
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
setMeasuredDimension(AndroidUtilities.dp(30), AndroidUtilities.dp(30));
|
|
if (imageView != null) {
|
|
imageView.measure(
|
|
MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(24), MeasureSpec.EXACTLY),
|
|
MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(24), MeasureSpec.EXACTLY)
|
|
);
|
|
}
|
|
if (lockView != null) {
|
|
lockView.measure(
|
|
MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(12), MeasureSpec.EXACTLY),
|
|
MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(12), MeasureSpec.EXACTLY)
|
|
);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
|
if (imageView != null) {
|
|
int cx = (r - l) / 2, cy = (b - t) / 2;
|
|
imageView.layout(cx - imageView.getMeasuredWidth() / 2, cy - imageView.getMeasuredHeight() / 2, cx + imageView.getMeasuredWidth() / 2, cy + imageView.getMeasuredHeight() / 2);
|
|
}
|
|
if (lockView != null) {
|
|
lockView.layout(r - l - lockView.getMeasuredWidth(), b - t - lockView.getMeasuredHeight(), r - l, b - t);
|
|
}
|
|
}
|
|
|
|
public Drawable getDrawable() {
|
|
return imageView != null ? imageView.getDrawable() : null;
|
|
}
|
|
|
|
public void setDrawable(Drawable drawable) {
|
|
if (lockView != null && drawable instanceof AnimatedEmojiDrawable) {
|
|
ImageReceiver imageReceiver = ((AnimatedEmojiDrawable) drawable).getImageReceiver();
|
|
if (imageReceiver != null) {
|
|
lockView.setImageReceiver(imageReceiver);
|
|
}
|
|
}
|
|
if (imageView != null) {
|
|
imageView.setImageDrawable(drawable);
|
|
}
|
|
}
|
|
|
|
private float selectT;
|
|
private boolean selected;
|
|
private ValueAnimator selectAnimator;
|
|
public void updateSelect(boolean selected, boolean animated) {
|
|
if (imageView != null && imageView.getDrawable() == null) {
|
|
return;
|
|
}
|
|
if (this.selected == selected) {
|
|
return;
|
|
}
|
|
this.selected = selected;
|
|
if (selectAnimator != null) {
|
|
selectAnimator.cancel();
|
|
selectAnimator = null;
|
|
}
|
|
|
|
if (animated) {
|
|
selectAnimator = ValueAnimator.ofFloat(selectT, selected ? 1f : 0f);
|
|
selectAnimator.addUpdateListener(a -> {
|
|
selectT = (float) a.getAnimatedValue();
|
|
setColor(ColorUtils.blendARGB(Theme.getColor(Theme.key_chat_emojiPanelIcon, resourcesProvider), Theme.getColor(Theme.key_chat_emojiPanelIconSelected, resourcesProvider), selectT));
|
|
});
|
|
selectAnimator.addListener(new AnimatorListenerAdapter() {
|
|
@Override
|
|
public void onAnimationEnd(Animator animation) {
|
|
if (!round) {
|
|
if (selected || forceSelector) {
|
|
if (getBackground() == null) {
|
|
setBackground(Theme.createRadSelectorDrawable(selectorColor(), 8, 8));
|
|
}
|
|
} else {
|
|
setBackground(null);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
selectAnimator.setDuration(350);
|
|
selectAnimator.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT);
|
|
selectAnimator.start();
|
|
} else {
|
|
selectT = selected ? 1f : 0f;
|
|
updateColor();
|
|
}
|
|
}
|
|
|
|
public void updateColor() {
|
|
Theme.setSelectorDrawableColor(getBackground(), selectorColor(), false);
|
|
setColor(
|
|
ColorUtils.blendARGB(
|
|
Theme.getColor(Theme.key_chat_emojiPanelIcon, resourcesProvider),
|
|
Theme.getColor(Theme.key_chat_emojiPanelIconSelected, resourcesProvider),
|
|
selectT
|
|
)
|
|
);
|
|
}
|
|
|
|
private void setColor(int color) {
|
|
PorterDuffColorFilter colorFilter = new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY);
|
|
if (imageView != null) {
|
|
imageView.setColorFilter(colorFilter);
|
|
imageView.invalidate();
|
|
}
|
|
if (lottieDrawable != null) {
|
|
lottieDrawable.setColorFilter(colorFilter);
|
|
invalidate();
|
|
}
|
|
}
|
|
}
|
|
|
|
private class EmojiTabsView extends ScrollableHorizontalScrollView {
|
|
public int id;
|
|
|
|
private LinearLayout contentView;
|
|
|
|
public EmojiTabsView(Context context) {
|
|
super(context);
|
|
setSmoothScrollingEnabled(true);
|
|
setHorizontalScrollBarEnabled(false);
|
|
setVerticalScrollBarEnabled(false);
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
setNestedScrollingEnabled(true);
|
|
}
|
|
contentView = new LinearLayout(context) {
|
|
@Override
|
|
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
|
int x = getPaddingLeft(), cy = (b - t) / 2;
|
|
for (int i = 0; i < getChildCount(); ++i) {
|
|
View child = getChildAt(i);
|
|
if (child == settingsTab) {
|
|
continue;
|
|
}
|
|
if (child != null) {
|
|
child.layout(x, cy - child.getMeasuredHeight() / 2, x + child.getMeasuredWidth(), cy + child.getMeasuredHeight() / 2);
|
|
x += child.getMeasuredWidth() + AndroidUtilities.dp(2);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
super.onMeasure(
|
|
Math.max(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp((30 + 2) * contentView.getChildCount()), MeasureSpec.EXACTLY)),
|
|
heightMeasureSpec
|
|
);
|
|
}
|
|
};
|
|
contentView.setOrientation(LinearLayout.HORIZONTAL);
|
|
addView(contentView, new LayoutParams(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT));
|
|
|
|
for (int i = 0; i < emojiTabsDrawableIds.length; ++i) {
|
|
contentView.addView(new EmojiTabButton(context, emojiTabsDrawableIds[i], emojiTabsAnimatedDrawableIds[i], true, false) {
|
|
@Override
|
|
public boolean onTouchEvent(MotionEvent ev) {
|
|
intercept(ev);
|
|
return super.onTouchEvent(ev);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
super.onMeasure(
|
|
MeasureSpec.makeMeasureSpec(AndroidUtilities.lerp(AndroidUtilities.dp(30), maxWidth(), showT), MeasureSpec.EXACTLY),
|
|
MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(30), MeasureSpec.EXACTLY)
|
|
);
|
|
}
|
|
|
|
public int maxWidth() {
|
|
return AndroidUtilities.dp((30 + 2) * Math.min(5.7f, contentView.getChildCount()));
|
|
}
|
|
|
|
private boolean touching = false;
|
|
|
|
@Override
|
|
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
|
|
super.onScrollChanged(l, t, oldl, oldt);
|
|
if (Math.abs(t - oldt) < 2 || t >= getMeasuredHeight() || t == 0) {
|
|
if (!touching) {
|
|
EmojiTabsStrip.this.requestDisallowInterceptTouchEvent(false);
|
|
}
|
|
}
|
|
updateButtonsVisibility();
|
|
}
|
|
|
|
private void updateButtonsVisibility() {
|
|
final int count = contentView.getChildCount();
|
|
for (int i = 0; i < count; ++i) {
|
|
View child = contentView.getChildAt(i);
|
|
if (child instanceof EmojiTabButton) {
|
|
((EmojiTabButton) child).updateVisibilityInbounds(child.getRight() - getScrollX() > 0 && child.getLeft() - getScrollX() < getMeasuredWidth(), scrollingAnimation && !(showAnimator != null && showAnimator.isRunning()));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void intercept(MotionEvent ev) {
|
|
if (shown && !scrollingAnimation) {
|
|
switch (ev.getAction()) {
|
|
case MotionEvent.ACTION_UP:
|
|
touching = false;
|
|
break;
|
|
case MotionEvent.ACTION_DOWN:
|
|
case MotionEvent.ACTION_MOVE:
|
|
touching = true;
|
|
if (!scrollingAnimation) {
|
|
resetScrollTo();
|
|
}
|
|
EmojiTabsStrip.this.requestDisallowInterceptTouchEvent(true);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean onTouchEvent(MotionEvent ev) {
|
|
intercept(ev);
|
|
return super.onTouchEvent(ev);
|
|
}
|
|
|
|
private boolean shown = false;
|
|
private float showT = 0f;
|
|
private ValueAnimator showAnimator;
|
|
public void show(boolean show, boolean animated) {
|
|
if (show == shown) {
|
|
return;
|
|
}
|
|
shown = show;
|
|
if (!show) {
|
|
scrollTo(0);
|
|
}
|
|
|
|
if (showAnimator != null) {
|
|
showAnimator.cancel();
|
|
}
|
|
if (animated) {
|
|
showAnimator = ValueAnimator.ofFloat(showT, show ? 1f : 0f);
|
|
showAnimator.addUpdateListener(a -> {
|
|
showT = (float) a.getAnimatedValue();
|
|
invalidate();
|
|
requestLayout();
|
|
updateButtonsVisibility();
|
|
EmojiTabsStrip.this.contentView.invalidate();
|
|
});
|
|
showAnimator.setDuration(475);
|
|
showAnimator.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT);
|
|
showAnimator.start();
|
|
} else {
|
|
showT = show ? 1f : 0f;
|
|
invalidate();
|
|
requestLayout();
|
|
updateButtonsVisibility();
|
|
EmojiTabsStrip.this.contentView.invalidate();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class ScrollableHorizontalScrollView extends HorizontalScrollView {
|
|
public ScrollableHorizontalScrollView(Context context) {
|
|
super(context);
|
|
}
|
|
|
|
protected boolean scrollingAnimation;
|
|
public boolean isScrolling() {
|
|
return scrollingAnimation;
|
|
}
|
|
|
|
public boolean scrollToVisible(int left, int right) {
|
|
if (getChildCount() <= 0) {
|
|
return false;
|
|
}
|
|
final int padding = AndroidUtilities.dp(50);
|
|
int to;
|
|
if (left < getScrollX() + padding) {
|
|
to = left - padding;
|
|
} else if (right > getScrollX() + (getMeasuredWidth() - padding)) {
|
|
to = right - getMeasuredWidth() + padding;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
scrollTo(MathUtils.clamp(to, 0, getChildAt(0).getMeasuredWidth() - getMeasuredWidth()));
|
|
return true;
|
|
}
|
|
|
|
private int scrollingTo = -1;
|
|
private ValueAnimator scrollAnimator;
|
|
public void scrollTo(int x) {
|
|
if (scrollingTo == x) {
|
|
return;
|
|
}
|
|
scrollingTo = x;
|
|
if (scrollAnimator != null) {
|
|
scrollAnimator.cancel();
|
|
}
|
|
if (this.getScrollX() == x) {
|
|
return;
|
|
}
|
|
scrollAnimator = ValueAnimator.ofFloat(this.getScrollX(), x);
|
|
scrollAnimator.addUpdateListener(a -> this.setScrollX((int) (float) a.getAnimatedValue()));
|
|
scrollAnimator.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT);
|
|
scrollAnimator.setDuration(250);
|
|
scrollAnimator.addListener(new AnimatorListenerAdapter() {
|
|
@Override
|
|
public void onAnimationEnd(Animator animation) {
|
|
scrollingAnimation = false;
|
|
}
|
|
|
|
@Override
|
|
public void onAnimationStart(Animator animation) {
|
|
scrollingAnimation = true;
|
|
if (getParent() instanceof HorizontalScrollView) {
|
|
((HorizontalScrollView) getParent()).requestDisallowInterceptTouchEvent(false);
|
|
}
|
|
}
|
|
});
|
|
scrollAnimator.start();
|
|
}
|
|
|
|
public void resetScrollTo() {
|
|
scrollingTo = -1;
|
|
}
|
|
}
|