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

1814 lines
79 KiB
Java

package org.telegram.ui.Components;
import static org.telegram.messenger.AndroidUtilities.dp;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.Shader;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.SystemClock;
import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
import android.text.util.Linkify;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.json.JSONArray;
import org.json.JSONTokener;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.ApplicationLoader;
import org.telegram.messenger.DispatchQueue;
import org.telegram.messenger.Emoji;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.MessageObject;
import org.telegram.messenger.R;
import org.telegram.messenger.XiaomiUtilities;
import org.telegram.tgnet.ConnectionsManager;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.BaseFragment;
import org.telegram.ui.ActionBar.BottomSheet;
import org.telegram.ui.ActionBar.Theme;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.ArrayList;
public class TranslateAlert2 extends BottomSheet {
public static volatile DispatchQueue translateQueue = new DispatchQueue("translateQueue", false);
public RecyclerListView listView;
private LinearLayoutManager layoutManager;
private View paddingView;
// private FrameLayout container;
private TextView titleView;
private LinearLayout subtitleView;
private InlineLoadingTextView subtitleFromView;
private ImageView subtitleArrowView;
private TextView subtitleToView;
private ImageView backButton;
private HeaderView header;
private FrameLayout headerShadowView;
// private NestedScrollView scrollView;
private TextBlocksLayout textsView;
private TextView buttonTextView;
private FrameLayout buttonContainerView;
private FrameLayout buttonView;
private FrameLayout buttonShadowView;
private TextView allTextsView;
// private FrameLayout textsContainerView;
private FrameLayout bulletinContainer;
// private FrameLayout.LayoutParams titleLayout;
// private FrameLayout.LayoutParams subtitleLayout;
// private FrameLayout.LayoutParams headerLayout;
// private FrameLayout.LayoutParams scrollViewLayout;
private int blockIndex = 0;
private ArrayList<CharSequence> textBlocks;
//
// private boolean canExpand() {
// return (
// textsView.getBlocksCount() < textBlocks.size() ||
// minHeight(true) >= (AndroidUtilities.displayMetrics.heightPixels * heightMaxPercent)
// );
// }
// private void updateCanExpand() {
// boolean canExpand = canExpand();
// if (containerOpenAnimationT > 0f && !canExpand) {
// openAnimationTo(0f, false);
// }
//
// buttonShadowView.animate().alpha(canExpand ? 1f : 0f).setDuration((long) (Math.abs(buttonShadowView.getAlpha() - (canExpand ? 1f : 0f)) * 220)).start();
// }
public interface OnLinkPress {
public boolean run(URLSpan urlSpan);
}
@Override
public void onBackPressed() {
dismiss();
}
private class HeaderView extends FrameLayout {
public HeaderView(Context context) {
super(context);
}
private float expandedT = 0f;
public void setExpandedT(float value) {
backButton.setAlpha(value);
headerShadowView.setAlpha(value);
if (Math.abs(expandedT - value) > 0.01f) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && expandedT > .5f != value > .5f) {
int flags = containerView.getSystemUiVisibility();
if (value > .5f) {
flags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
} else {
flags &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
}
containerView.setSystemUiVisibility(flags);
}
expandedT = value;
invalidate();
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
titleView.layout(
dp(22),
dp(22),
right - left - dp(22),
dp(22) + titleView.getMeasuredHeight()
);
subtitleView.layout(
dp(22) - LoadingTextView2.paddingHorizontal,
dp(47) - LoadingTextView2.paddingVertical,
right - left - dp(22) - LoadingTextView2.paddingHorizontal,
dp(47) - LoadingTextView2.paddingVertical + subtitleView.getMeasuredHeight()
);
backButton.layout(0, 0, dp(56), dp(56));
headerShadowView.layout(0, dp(55), right - left, dp(56));
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
if (child == titleView) {
canvas.save();
canvas.translate(dp(50 * expandedT), dp(-14 * expandedT));
canvas.scale(1f - expandedT * 0.111f, 1f - expandedT * 0.111f, child.getX(), child.getY() + child.getMeasuredHeight() / 2);
boolean result = super.drawChild(canvas, child, drawingTime);
canvas.restore();
return result;
} else if (child == subtitleView) {
canvas.save();
canvas.translate(dp(50 * expandedT), dp(-17 * expandedT));
boolean result = super.drawChild(canvas, child, drawingTime);
canvas.restore();
return result;
} else {
return super.drawChild(canvas, child, drawingTime);
}
}
@Override
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
if (child == backButton) {
backButton.measure(
MeasureSpec.makeMeasureSpec(dp(56), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(dp(56), MeasureSpec.EXACTLY)
);
} else {
super.measureChild(child, parentWidthMeasureSpec, parentHeightMeasureSpec);
}
}
}
private boolean allowScroll = true;
private String fromLanguage, toLanguage;
private CharSequence text;
private BaseFragment fragment;
private boolean noforwards;
private OnLinkPress onLinkPress;
private Runnable onDismiss;
public void updateCanExpand() {
boolean canExpand = listView.canScrollVertically(1) || listView.canScrollVertically(-1);
float canExpandAlpha = canExpand ? 1f : 0f;
buttonShadowView.animate().alpha(canExpandAlpha).setDuration(200).start();
allowScroll = canExpand;
}
public TranslateAlert2(BaseFragment fragment, Context context, String fromLanguage, String toLanguage, CharSequence text, boolean noforwards, OnLinkPress onLinkPress, Runnable onDismiss) {
super(context, false);
fixNavigationBar();
this.onLinkPress = onLinkPress;
this.noforwards = noforwards;
this.fragment = fragment;
this.fromLanguage = fromLanguage != null && fromLanguage.equals("und") ? "auto" : fromLanguage;
this.toLanguage = toLanguage;
this.text = text;
this.textBlocks = cutInBlocks(text, 1024);
this.onDismiss = onDismiss;
if (noforwards) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
}
allTextsView = new TextView(context) {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, MOST_SPEC);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.translate(getPaddingLeft(), getPaddingTop());
if (links != null && links.draw(canvas)) {
invalidate();
}
canvas.restore();
}
@Override
public boolean onTextContextMenuItem(int id) {
if (id == android.R.id.copy && isFocused()) {
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE);
android.content.ClipData clip = android.content.ClipData.newPlainText(
"label",
getText().subSequence(
Math.max(0, Math.min(getSelectionStart(), getSelectionEnd())),
Math.max(0, Math.max(getSelectionStart(), getSelectionEnd()))
)
);
clipboard.setPrimaryClip(clip);
BulletinFactory.of(bulletinContainer, null).createCopyBulletin(LocaleController.getString("TextCopied", R.string.TextCopied)).show();
clearFocus();
return true;
} else {
return super.onTextContextMenuItem(id);
}
}
};
links = new LinkSpanDrawable.LinkCollector(allTextsView);
allTextsView.setTextColor(0x00000000);
allTextsView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
allTextsView.setTextIsSelectable(!noforwards);
allTextsView.setHighlightColor(Theme.getColor(Theme.key_chat_inTextSelectionHighlight));
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && !XiaomiUtilities.isMIUI()) {
final int handleColor = Theme.getColor(Theme.key_chat_TextSelectionCursor);
Drawable left = allTextsView.getTextSelectHandleLeft();
left.setColorFilter(handleColor, PorterDuff.Mode.SRC_IN);
allTextsView.setTextSelectHandleLeft(left);
Drawable right = allTextsView.getTextSelectHandleRight();
right.setColorFilter(handleColor, PorterDuff.Mode.SRC_IN);
allTextsView.setTextSelectHandleRight(right);
}
} catch (Exception ignore) {}
allTextsView.setMovementMethod(new LinkMovementMethod());
textsView = new TextBlocksLayout(context, dp(16), Theme.getColor(Theme.key_dialogTextBlack), allTextsView) {
@Override
protected void onHeightUpdated(int height, int dy) {
// if (dy != 0 && listView != null && listView.canScrollVertically(-dy)) {
// try {
// listView.scrollBy(0, -dy);
// } catch (Exception ignore) {}
// }
paddingView.requestFocus();
paddingView.requestLayout();
}
};
textsView.setPadding(
dp(22) - LoadingTextView2.paddingHorizontal,
dp(12 + 8) - LoadingTextView2.paddingVertical,
dp(22) - LoadingTextView2.paddingHorizontal,
dp(12) - LoadingTextView2.paddingVertical
);
for (CharSequence blockText : textBlocks) {
textsView.addBlock(blockText);
}
final Paint backgroundPaint = new Paint();
backgroundPaint.setColor(Theme.getColor(Theme.key_dialogBackground));
backgroundPaint.setShadowLayer(dp(2), 0, dp(-0.66f), 0x1e000000);
final Paint navigationBarPaint = new Paint();
navigationBarPaint.setColor(Theme.getColor(Theme.key_dialogBackgroundGray));
containerView = new FrameLayout(context) {
@Override
public boolean hasOverlappingRendering() {
return false;
}
@Override
protected void dispatchDraw(Canvas canvas) {
float top = getCurrentItemTop();
float expandedT = 1f - Math.max(0, Math.min(top / dp(48), 1));
header.setTranslationY(top);
header.setExpandedT(expandedT);
updateCanExpand();
float r = dp(12) * (1f - expandedT);
AndroidUtilities.rectTmp.set(backgroundPaddingLeft, getPaddingTop() + top - (AndroidUtilities.statusBarHeight + dp(8)) * expandedT + dp(8), getWidth() - backgroundPaddingLeft, getPaddingTop() + top + dp(20));
canvas.drawRoundRect(AndroidUtilities.rectTmp, r, r, navigationBarPaint);
AndroidUtilities.rectTmp.set(backgroundPaddingLeft, getPaddingTop() + top, getWidth() - backgroundPaddingLeft, getPaddingTop() + getHeight() + dp(12));
canvas.drawRoundRect(AndroidUtilities.rectTmp, r, r, backgroundPaint);
super.dispatchDraw(canvas);
}
@Override
public void setTranslationY(float translationY) {
super.setTranslationY(translationY);
onContainerTranslationYChanged(translationY);
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
if (child == listView) {
canvas.save();
canvas.clipRect(0, getPaddingTop() + listView.getPaddingTop(), getWidth(), getHeight() - listView.getPaddingBottom());
boolean result = super.drawChild(canvas, child, drawingTime);
canvas.restore();
return result;
} else {
return super.drawChild(canvas, child, drawingTime);
}
}
};
containerView.setPadding(backgroundPaddingLeft, AndroidUtilities.statusBarHeight, backgroundPaddingLeft, 0);
containerView.setClipChildren(false);
containerView.setClipToPadding(false);
containerView.setWillNotDraw(false);
listView = new RecyclerListView(context) {
@Override
public View getFocusedChild() {
return textsView;
}
@Override
public void onScrolled(int dx, int dy) {
checkForNextLoading();
super.onScrolled(dx, dy);
containerView.invalidate();
}
@Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
if (state == SCROLL_STATE_IDLE && header.expandedT > 0 && header.expandedT < 1) {
smoothScrollBy(0, (int) header.getTranslationY());
}
}
@Override
public boolean onTouchEvent(MotionEvent e) {
if (!allowScroll) {
return false;
}
return super.onTouchEvent(e);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
if (!allowScroll) {
return false;
}
return super.onInterceptTouchEvent(e);
}
};
listView.setClipChildren(true);
listView.setClipToPadding(true);
listView.setPadding(0, dp(56), 0, dp(80));
listView.setLayoutManager(layoutManager = new LinearLayoutManager(context) {
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
super.onLayoutChildren(recycler, state);
}
});
layoutManager.setOrientation(RecyclerView.VERTICAL);
layoutManager.setReverseLayout(true);
listView.setAdapter(new RecyclerView.Adapter() {
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == 0) {
return new RecyclerListView.Holder(textsView);
}
return new RecyclerListView.Holder(paddingView);
}
@Override
public int getItemViewType(int position) {
return position;
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {}
@Override
public int getItemCount() {
return 2;
}
});
View redDot = new View(context) {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(32), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(32), MeasureSpec.EXACTLY));
}
};
redDot.setBackgroundColor(0xffff0000);
containerView.addView(redDot);
containerView.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
header = new HeaderView(context);
header.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
containerView.addView(header, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.FILL_HORIZONTAL));
titleView = new TextView(context);
titleView.setPivotX(LocaleController.isRTL ? titleView.getWidth() : 0);
titleView.setPivotY(0);
titleView.setLines(1);
titleView.setText(LocaleController.getString("AutomaticTranslation", R.string.AutomaticTranslation));
titleView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT);
titleView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
titleView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack));
titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
header.addView(titleView, LayoutHelper.createFrame(
LayoutHelper.MATCH_PARENT,
LayoutHelper.WRAP_CONTENT,
Gravity.FILL_HORIZONTAL | Gravity.TOP,
22, 22,22, 0
));
titleView.post(() -> {
titleView.setPivotX(LocaleController.isRTL ? titleView.getWidth() : 0);
});
subtitleView = new LinearLayout(context);
subtitleView.setOrientation(LinearLayout.HORIZONTAL);
if (Build.VERSION.SDK_INT >= 17) {
subtitleView.setLayoutDirection(LocaleController.isRTL ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
}
subtitleView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT);
String fromLanguageName = languageName(fromLanguage);
subtitleFromView = new InlineLoadingTextView(context, fromLanguageName == null ? languageName(toLanguage) : fromLanguageName, dp(14), Theme.getColor(Theme.key_player_actionBarSubtitle)) {
@Override
protected void onLoadAnimation(float t) {
MarginLayoutParams lp = (MarginLayoutParams) subtitleFromView.getLayoutParams();
if (lp != null) {
if (LocaleController.isRTL) {
lp.leftMargin = dp(2f - t * 6f);
} else {
lp.rightMargin = dp(2f - t * 6f);
}
subtitleFromView.setLayoutParams(lp);
}
}
};
subtitleFromView.showLoadingText = false;
subtitleArrowView = new ImageView(context);
subtitleArrowView.setImageResource(R.drawable.search_arrow);
subtitleArrowView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_player_actionBarSubtitle), PorterDuff.Mode.MULTIPLY));
if (LocaleController.isRTL) {
subtitleArrowView.setScaleX(-1f);
}
subtitleToView = new TextView(context);
subtitleToView.setLines(1);
subtitleToView.setTextColor(Theme.getColor(Theme.key_player_actionBarSubtitle));
subtitleToView.setTextSize(TypedValue.COMPLEX_UNIT_PX, dp(14));
subtitleToView.setText(languageName(toLanguage));
if (LocaleController.isRTL) {
subtitleView.setPadding(InlineLoadingTextView.paddingHorizontal, 0, 0, 0);
subtitleView.addView(subtitleToView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL));
subtitleView.addView(subtitleArrowView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 3, 1, 3, 0));
subtitleView.addView(subtitleFromView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 0, 0, 0, 0));
} else {
subtitleView.setPadding(0, 0, InlineLoadingTextView.paddingHorizontal, 0);
subtitleView.addView(subtitleFromView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 0, 0, 0, 0));
subtitleView.addView(subtitleArrowView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 3, 1, 3, 0));
subtitleView.addView(subtitleToView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL));
}
if (fromLanguageName != null) {
subtitleFromView.set(fromLanguageName);
}
header.addView(subtitleView, LayoutHelper.createFrame(
LayoutHelper.MATCH_PARENT,
LayoutHelper.WRAP_CONTENT,
Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT),
22 - LoadingTextView2.paddingHorizontal / AndroidUtilities.density,
47 - LoadingTextView2.paddingVertical / AndroidUtilities.density,
22 - LoadingTextView2.paddingHorizontal / AndroidUtilities.density,
0
));
backButton = new ImageView(context);
backButton.setImageResource(R.drawable.ic_ab_back);
backButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_dialogTextBlack), PorterDuff.Mode.MULTIPLY));
backButton.setScaleType(ImageView.ScaleType.FIT_CENTER);
backButton.setPadding(AndroidUtilities.dp(16), 0, AndroidUtilities.dp(16), 0);
backButton.setBackground(Theme.createSelectorDrawable(Theme.getColor(Theme.key_dialogButtonSelector)));
backButton.setClickable(false);
backButton.setAlpha(0f);
backButton.setOnClickListener(e -> {
if (backButton.getAlpha() > .5f) {
dismiss();
}
});
header.addView(backButton, LayoutHelper.createFrame(56, 56, Gravity.LEFT | Gravity.CENTER_HORIZONTAL));
headerShadowView = new FrameLayout(context);
headerShadowView.setBackgroundColor(Theme.getColor(Theme.key_dialogShadowLine));
headerShadowView.setAlpha(0);
header.addView(headerShadowView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 1, Gravity.BOTTOM | Gravity.FILL_HORIZONTAL));
paddingView = new View(context) {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int padding = (int) Math.max(AndroidUtilities.displaySize.y * .5f, listView.getMeasuredHeight() - listView.getPaddingTop() - listView.getPaddingBottom() - textsView.height());
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(padding, MeasureSpec.EXACTLY));
}
};
//
// header.setClipChildren(false);
// container.addView(header, headerLayout = LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 70, Gravity.FILL_HORIZONTAL | Gravity.TOP));
// textsContainerView = new FrameLayout(context);
// textsContainerView.addView(textsView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT));
// scrollView.addView(textsContainerView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 1f));
// container.addView(scrollView, scrollViewLayout = LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.FILL, 0, 70, 0, 81));
fetchNext();
// container.addView(buttonShadowView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 1, Gravity.BOTTOM | Gravity.FILL_HORIZONTAL, 0, 0, 0, 80));
buttonTextView = new TextView(context);
buttonTextView.setLines(1);
buttonTextView.setSingleLine(true);
buttonTextView.setGravity(Gravity.CENTER_HORIZONTAL);
buttonTextView.setEllipsize(TextUtils.TruncateAt.END);
buttonTextView.setGravity(Gravity.CENTER);
buttonTextView.setTextColor(Theme.getColor(Theme.key_featuredStickers_buttonText));
buttonTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
buttonTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
buttonTextView.setText(LocaleController.getString("CloseTranslation", R.string.CloseTranslation));
buttonView = new FrameLayout(context);
buttonView.setBackground(Theme.createSimpleSelectorRoundRectDrawable(AndroidUtilities.dp(4), Theme.getColor(Theme.key_featuredStickers_addButton), Theme.getColor(Theme.key_featuredStickers_addButtonPressed)));
buttonView.addView(buttonTextView);
buttonView.setOnClickListener(e -> dismiss());
buttonContainerView = new FrameLayout(context);
buttonContainerView.addView(buttonView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.FILL, 16, 16, 16, 16));
buttonShadowView = new FrameLayout(context);
buttonShadowView.setBackgroundColor(Theme.getColor(Theme.key_dialogShadowLine));
buttonShadowView.setAlpha(0);
buttonContainerView.addView(buttonShadowView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 1, Gravity.TOP | Gravity.FILL_HORIZONTAL));
containerView.addView(buttonContainerView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 80, Gravity.BOTTOM | Gravity.FILL_HORIZONTAL));
bulletinContainer = new FrameLayout(context);
containerView.addView(bulletinContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.FILL, 0, 0, 0, 81));
}
private float getCurrentItemTop() {
View child = listView.getChildAt(0);
if (child == null) {
return 0;
}
RecyclerListView.Holder holder = (RecyclerListView.Holder) listView.findContainingViewHolder(child);
if (holder != null && holder.getAdapterPosition() == 0) {
return Math.max(0, child.getY() - header.getMeasuredHeight());
}
return 0;
}
@Override
protected boolean canDismissWithSwipe() {
return !listView.canScrollVertically(-1);
}
// private boolean scrollAtBottom() {
// View view = (View) scrollView.getChildAt(scrollView.getChildCount() - 1);
// int bottom = view.getBottom();
// LoadingTextView2 lastUnloadedBlock = textsView.getFirstUnloadedBlock();
// if (lastUnloadedBlock != null) {
// bottom = lastUnloadedBlock.getTop();
// }
// int diff = (bottom - (scrollView.getHeight() + scrollView.getScrollY()));
// return diff <= textsContainerView.getPaddingBottom();
// }
private boolean hasSelection() {
return allTextsView.hasSelection();
}
private Rect containerRect = new Rect();
private Rect textRect = new Rect();
private Rect translateMoreRect = new Rect();
private Rect buttonRect = new Rect();
private Rect backRect = new Rect();
private Rect scrollRect = new Rect();
private float fromY = 0;
private boolean pressedOutside = false;
private boolean maybeScrolling = false;
private boolean scrolling = false;
private boolean fromScrollRect = false;
private boolean fromTranslateMoreView = false;
private float fromScrollViewY = 0;
private Spannable allTexts = null;
private LinkSpanDrawable pressedLink;
private LinkSpanDrawable.LinkCollector links;
// @Override
// public boolean dispatchTouchEvent(@NonNull MotionEvent event) {
// try {
// float x = event.getX();
// float y = event.getY();
//
// container.getGlobalVisibleRect(containerRect);
// if (!containerRect.contains((int) x, (int) y)) {
// if (event.getAction() == MotionEvent.ACTION_DOWN) {
// pressedOutside = true;
// return true;
// } else if (event.getAction() == MotionEvent.ACTION_UP) {
// if (pressedOutside) {
// pressedOutside = false;
// dismiss();
// return true;
// }
// }
// }
//
// try {
// allTextsView.getGlobalVisibleRect(textRect);
// if (textRect.contains((int) x, (int) y) && !maybeScrolling) {
// Layout allTextsLayout = allTextsView.getLayout();
// int tx = (int) (x - allTextsView.getLeft() - container.getLeft()),
// ty = (int) (y - allTextsView.getTop() - container.getTop() - scrollView.getTop() + scrollView.getScrollY());
// final int line = allTextsLayout.getLineForVertical(ty);
// final int off = allTextsLayout.getOffsetForHorizontal(line, tx);
//
// final float left = allTextsLayout.getLineLeft(line);
// if (allTexts instanceof Spannable && left <= tx && left + allTextsLayout.getLineWidth(line) >= tx) {
// ClickableSpan[] linkSpans = allTexts.getSpans(off, off, ClickableSpan.class);
// if (linkSpans != null && linkSpans.length >= 1) {
// if (event.getAction() == MotionEvent.ACTION_UP && pressedLink.getSpan() == linkSpans[0]) {
// ((ClickableSpan) pressedLink.getSpan()).onClick(allTextsView);
// if (links != null) {
// links.removeLink(pressedLink);
// }
// pressedLink = null;
// allTextsView.setTextIsSelectable(!noforwards);
// } else if (event.getAction() == MotionEvent.ACTION_DOWN) {
// pressedLink = new LinkSpanDrawable(linkSpans[0], fragment.getResourceProvider(), tx, ty, false);
// if (links != null) {
// links.addLink(pressedLink);
// }
// LinkPath path = pressedLink.obtainNewPath();
// int start = allTexts.getSpanStart(pressedLink.getSpan());
// int end = allTexts.getSpanEnd(pressedLink.getSpan());
// path.setCurrentLayout(allTextsLayout, start, 0);
// allTextsLayout.getSelectionPath(start, end, path);
// }
// allTextsView.invalidate();
// return true;
// }
// }
// }
// if (pressedLink != null) {
// if (links != null) {
// links.clear();
// }
// pressedLink = null;
// }
// } catch (Exception e2) {
// e2.printStackTrace();
// }
//
// scrollView.getGlobalVisibleRect(scrollRect);
// backButton.getGlobalVisibleRect(backRect);
// buttonView.getGlobalVisibleRect(buttonRect);
// if (pressedLink == null && /*!(scrollRect.contains((int) x, (int) y) && !canExpand() && containerOpenAnimationT < .5f && !scrolling) &&*/ !hasSelection()) {
// if (
// !backRect.contains((int) x, (int) y) &&
// !buttonRect.contains((int) x, (int) y) &&
// event.getAction() == MotionEvent.ACTION_DOWN
// ) {
// fromScrollRect = scrollRect.contains((int) x, (int) y) && (containerOpenAnimationT > 0 || !canExpand());
// maybeScrolling = true;
// scrolling = scrollRect.contains((int) x, (int) y) && textsView.getBlocksCount() > 0 && !((LoadingTextView2) textsView.getBlockAt(0)).loaded;
// fromY = y;
// fromScrollY = getScrollY();
// fromScrollViewY = scrollView.getScrollY();
// return super.dispatchTouchEvent(event) || true;
// } else if (maybeScrolling && (event.getAction() == MotionEvent.ACTION_MOVE || event.getAction() == MotionEvent.ACTION_UP)) {
// float dy = fromY - y;
// if (fromScrollRect) {
// dy = -Math.max(0, -(fromScrollViewY + dp(48)) - dy);
// if (dy < 0) {
// scrolling = true;
// allTextsView.setTextIsSelectable(false);
// }
// } else if (Math.abs(dy) > dp(4) && !fromScrollRect) {
// scrolling = true;
// allTextsView.setTextIsSelectable(false);
// scrollView.stopNestedScroll();
// allowScroll = false;
// }
// float fullHeight = AndroidUtilities.displayMetrics.heightPixels,
// minHeight = Math.min(minHeight(), fullHeight * heightMaxPercent);
// float scrollYPx = minHeight * (1f - -Math.min(Math.max(fromScrollY, -1), 0)) + (fullHeight - minHeight) * Math.min(1, Math.max(fromScrollY, 0)) + dy;
// float scrollY = scrollYPx > minHeight ? (scrollYPx - minHeight) / (fullHeight - minHeight) : -(1f - scrollYPx / minHeight);
// if (!canExpand()) {
// scrollY = Math.min(scrollY, 0);
// }
// updateCanExpand();
//
// if (scrolling) {
// setScrollY(scrollY);
// if (event.getAction() == MotionEvent.ACTION_UP) {
// scrolling = false;
// allTextsView.setTextIsSelectable(!noforwards);
// maybeScrolling = false;
// allowScroll = true;
// scrollYTo(
// Math.abs(dy) > dp(16) ?
// Math.round(fromScrollY) + (scrollY > fromScrollY ? 1f : -1f) * (float) Math.ceil(Math.abs(fromScrollY - scrollY)) :
// Math.round(fromScrollY),
// () -> {
// contentView.post(this::checkForNextLoading);
// }
// );
// }
// return true;
// }
// }
// }
// if (hasSelection() && maybeScrolling) {
// scrolling = false;
// allTextsView.setTextIsSelectable(!noforwards);
// maybeScrolling = false;
// allowScroll = true;
// scrollYTo(Math.round(fromScrollY));
// }
// return super.dispatchTouchEvent(event);
// } catch (Exception e) {
// e.printStackTrace();
// return super.dispatchTouchEvent(event);
// }
// return super.dispatchTouchEvent(event);
// }
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
containerView.setPadding(backgroundPaddingLeft, AndroidUtilities.statusBarHeight, backgroundPaddingLeft, 0);
}
//
// contentView.setPadding(0, AndroidUtilities.statusBarHeight, 0, 0);
// contentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
// setContentView(contentView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
//
// Window window = getWindow();
// window.setWindowAnimations(R.style.DialogNoAnimation);
// WindowManager.LayoutParams params = window.getAttributes();
// params.width = ViewGroup.LayoutParams.MATCH_PARENT;
// params.gravity = Gravity.TOP | Gravity.LEFT;
// params.dimAmount = 0;
// params.flags &= ~WindowManager.LayoutParams.FLAG_DIM_BEHIND;
// if (Build.VERSION.SDK_INT >= 21) {
// params.flags |=
// WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
// WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM |
// WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR |
// WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
// }
// params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
// params.height = ViewGroup.LayoutParams.MATCH_PARENT;
// if (Build.VERSION.SDK_INT >= 28) {
// params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
// }
// window.setAttributes(params);
// }
protected ColorDrawable backDrawable = new ColorDrawable(0xff000000) {
@Override
public void setAlpha(int alpha) {
super.setAlpha(alpha);
// contentView.invalidate();
}
};
public String languageName(String locale) {
// sorry, no more vodka
if (locale == null || locale.equals("und") || locale.equals("auto")) {
return null;
}
LocaleController.LocaleInfo thisLanguageInfo = LocaleController.getInstance().getBuiltinLanguageByPlural(locale);
if (thisLanguageInfo == null) {
return null;
}
LocaleController.LocaleInfo currentLanguageInfo = LocaleController.getInstance().getCurrentLocaleInfo();
boolean isCurrentLanguageEnglish = currentLanguageInfo != null && "en".equals(currentLanguageInfo.pluralLangCode);
if (isCurrentLanguageEnglish) {
// trying to show this language in a language of the interface, but there are only names in english and its own
return thisLanguageInfo.nameEnglish;
} else {
return thisLanguageInfo.name;
}
}
public void updateSourceLanguage() {
String fromLanguageName = languageName(fromLanguage);
if (fromLanguageName != null) {
subtitleView.setAlpha(1);
if (!subtitleFromView.loaded) {
subtitleFromView.loaded(fromLanguageName);
}
} else if (loaded) {
subtitleView.animate().alpha(0).setDuration(150).start();
titleView.animate().scaleX(1.2f).scaleY(1.2f).translationY(dp(5)).setDuration(150).start();
}
}
private ArrayList<CharSequence> cutInBlocks(CharSequence full, int maxBlockSize) {
ArrayList<CharSequence> blocks = new ArrayList<>();
if (full == null) {
return blocks;
}
while (full.length() > maxBlockSize) {
String maxBlockStr = full.subSequence(0, maxBlockSize).toString();
int n = maxBlockStr.lastIndexOf("\n\n");
if (n == -1) n = maxBlockStr.lastIndexOf("\n");
if (n == -1) n = maxBlockStr.lastIndexOf(". ");
if (n == -1) n = maxBlockStr.length();
blocks.add(full.subSequence(0, n + 1));
full = full.subSequence(n + 1, full.length());
}
if (full.length() > 0) {
blocks.add(full);
}
return blocks;
}
private boolean loading = false;
private boolean loaded = false;
private boolean fetchNext() {
if (loading) {
return false;
}
loading = true;
if (blockIndex >= textBlocks.size()) {
return false;
}
fetchTranslation(
textBlocks.get(blockIndex),
Math.min((blockIndex + 1) * 1000, 3500),
(String translatedText, String sourceLanguage) -> {
loaded = true;
Spannable spannable = new SpannableStringBuilder(translatedText);
try {
MessageObject.addUrlsByPattern(false, spannable, false, 0, 0, true);
URLSpan[] urlSpans = spannable.getSpans(0, spannable.length(), URLSpan.class);
for (int i = 0; i < urlSpans.length; ++i) {
URLSpan urlSpan = urlSpans[i];
int start = spannable.getSpanStart(urlSpan),
end = spannable.getSpanEnd(urlSpan);
if (start == -1 || end == -1) {
continue;
}
spannable.removeSpan(urlSpan);
spannable.setSpan(
new ClickableSpan() {
@Override
public void onClick(@NonNull View view) {
if (onLinkPress != null) {
onLinkPress.run(urlSpan);
dismiss();
} else {
AlertsCreator.showOpenUrlAlert(fragment, urlSpan.getURL(), false, false);
}
}
@Override
public void updateDrawState(@NonNull TextPaint ds) {
int alpha = Math.min(ds.getAlpha(), ds.getColor() >> 24 & 0xff);
if (!(urlSpan instanceof URLSpanNoUnderline)) {
ds.setUnderlineText(true);
}
ds.setColor(Theme.getColor(Theme.key_dialogTextLink));
ds.setAlpha(alpha);
}
},
start, end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
);
}
AndroidUtilities.addLinks(spannable, Linkify.WEB_URLS);
urlSpans = spannable.getSpans(0, spannable.length(), URLSpan.class);
for (int i = 0; i < urlSpans.length; ++i) {
URLSpan urlSpan = urlSpans[i];
int start = spannable.getSpanStart(urlSpan),
end = spannable.getSpanEnd(urlSpan);
if (start == -1 || end == -1) {
continue;
}
spannable.removeSpan(urlSpan);
spannable.setSpan(
new ClickableSpan() {
@Override
public void onClick(@NonNull View view) {
AlertsCreator.showOpenUrlAlert(fragment, urlSpan.getURL(), false, false);
}
@Override
public void updateDrawState(@NonNull TextPaint ds) {
int alpha = Math.min(ds.getAlpha(), ds.getColor() >> 24 & 0xff);
if (!(urlSpan instanceof URLSpanNoUnderline))
ds.setUnderlineText(true);
ds.setColor(Theme.getColor(Theme.key_dialogTextLink));
ds.setAlpha(alpha);
}
},
start, end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
);
}
spannable = (Spannable) Emoji.replaceEmoji(spannable, allTextsView.getPaint().getFontMetricsInt(), dp(14), false);
} catch (Exception e) {
e.printStackTrace();
}
SpannableStringBuilder allTextsBuilder = new SpannableStringBuilder(allTexts == null ? "" : allTexts);
if (blockIndex != 0) {
allTextsBuilder.append("\n");
}
allTextsBuilder.append(spannable);
allTexts = allTextsBuilder;
textsView.setWholeText(allTexts);
LoadingTextView2 block = textsView.getBlockAt(blockIndex);
if (block != null) {
block.loaded(spannable, () -> AndroidUtilities.runOnUIThread(this::checkForNextLoading));
}
if (sourceLanguage != null) {
fromLanguage = sourceLanguage;
updateSourceLanguage();
}
blockIndex++;
loading = false;
},
(boolean rateLimit) -> {
Toast.makeText(
getContext(),
rateLimit ?
LocaleController.getString("TranslationFailedAlert1", R.string.TranslationFailedAlert1) :
LocaleController.getString("TranslationFailedAlert2", R.string.TranslationFailedAlert2),
Toast.LENGTH_SHORT
).show();
if (blockIndex == 0) {
dismiss();
}
}
);
return true;
}
@Override
public void dismissInternal() {
super.dismissInternal();
if (onDismiss != null) {
onDismiss.run();
}
}
// @Override
// public boolean dispatchTouchEvent(@NonNull MotionEvent event) {
// if (event.getAction() == MotionEvent.ACTION_DOWN && event.getY() < contentView.getPaddingTop() + header.getTranslationY()) {
// dismiss();
// return true;
// }
// return super.dispatchTouchEvent(event);
// }
private void checkForNextLoading() {
if (!listView.canScrollVertically(-1)) {
fetchNext();
}
}
public interface OnTranslationSuccess {
public void run(String translated, String sourceLanguage);
}
public interface OnTranslationFail {
public void run(boolean rateLimit);
}
private void fetchTranslation(CharSequence text, long minDuration, OnTranslationSuccess onSuccess, OnTranslationFail onFail) {
if (!translateQueue.isAlive()) {
translateQueue.start();
}
translateQueue.postRunnable(() -> {
String uri = "";
HttpURLConnection connection = null;
long start = SystemClock.elapsedRealtime();
try {
uri = "https://translate.googleapis.com/translate_a/single?client=gtx&sl=";
uri += Uri.encode(fromLanguage);
uri += "&tl=";
uri += Uri.encode(toLanguage);
uri += "&dt=t&ie=UTF-8&oe=UTF-8&otf=1&ssel=0&tsel=0&kc=7&dt=at&dt=bd&dt=ex&dt=ld&dt=md&dt=qca&dt=rw&dt=rm&dt=ss&q=";
uri += Uri.encode(text.toString());
connection = (HttpURLConnection) new URI(uri).toURL().openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36");
connection.setRequestProperty("Content-Type", "application/json");
StringBuilder textBuilder = new StringBuilder();
try (Reader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charset.forName("UTF-8")))) {
int c;
while ((c = reader.read()) != -1) {
textBuilder.append((char) c);
}
}
String jsonString = textBuilder.toString();
JSONTokener tokener = new JSONTokener(jsonString);
JSONArray array = new JSONArray(tokener);
JSONArray array1 = array.getJSONArray(0);
String sourceLanguage = null;
try {
sourceLanguage = array.getString(2);
} catch (Exception e2) {}
if (sourceLanguage != null && sourceLanguage.contains("-")) {
sourceLanguage = sourceLanguage.substring(0, sourceLanguage.indexOf("-"));
}
StringBuilder result = new StringBuilder();
for (int i = 0; i < array1.length(); ++i) {
String blockText = array1.getJSONArray(i).getString(0);
if (blockText != null && !blockText.equals("null")) {
result.append(blockText);
}
}
if (text.length() > 0 && text.charAt(0) == '\n') {
result.insert(0, "\n");
}
final String finalResult = result.toString();
final String finalSourceLanguage = sourceLanguage;
long elapsed = SystemClock.elapsedRealtime() - start;
AndroidUtilities.runOnUIThread(() -> {
if (onSuccess != null) {
onSuccess.run(finalResult, finalSourceLanguage);
}
}, Math.max(0, minDuration - elapsed));
} catch (Exception e) {
try {
Log.e("translate", "failed to translate a text " + (connection != null ? connection.getResponseCode() : null) + " " + (connection != null ? connection.getResponseMessage() : null));
} catch (IOException ioException) {
ioException.printStackTrace();
}
e.printStackTrace();
if (onFail != null) {
try {
final boolean rateLimit = connection != null && connection.getResponseCode() == 429;
AndroidUtilities.runOnUIThread(() -> {
onFail.run(rateLimit);
});
} catch (Exception e2) {
AndroidUtilities.runOnUIThread(() -> {
onFail.run(false);
});
}
}
}
});
}
private static void translateText(int currentAccount, TLRPC.InputPeer peer, int msg_id, String from_lang, String to_lang) {
TLRPC.TL_messages_translateText req = new TLRPC.TL_messages_translateText();
req.peer = peer;
req.msg_id = msg_id;
req.flags |= 1;
if (from_lang != null) {
req.from_lang = from_lang;
req.flags |= 4;
}
req.to_lang = to_lang;
try {
ConnectionsManager.getInstance(currentAccount).sendRequest(req, (error, res) -> {});
} catch (Exception e) {
FileLog.e(e);
}
}
public static TranslateAlert2 showAlert(Context context, BaseFragment fragment, int currentAccount, TLRPC.InputPeer peer, int msgId, String fromLanguage, String toLanguage, CharSequence text, boolean noforwards, OnLinkPress onLinkPress, Runnable onDismiss) {
if (peer != null) {
translateText(currentAccount, peer, msgId, fromLanguage != null && fromLanguage.equals("und") ? null : fromLanguage, toLanguage);
}
TranslateAlert2 alert = new TranslateAlert2(fragment, context, fromLanguage, toLanguage, text, noforwards, onLinkPress, onDismiss);
if (fragment != null) {
if (fragment.getParentActivity() != null) {
fragment.showDialog(alert);
}
} else {
alert.show();
}
return alert;
}
public static TranslateAlert2 showAlert(Context context, BaseFragment fragment, String fromLanguage, String toLanguage, CharSequence text, boolean noforwards, OnLinkPress onLinkPress, Runnable onDismiss) {
TranslateAlert2 alert = new TranslateAlert2(fragment, context, fromLanguage, toLanguage, text, noforwards, onLinkPress, onDismiss);
if (fragment != null) {
if (fragment.getParentActivity() != null) {
fragment.showDialog(alert);
}
} else {
alert.show();
}
return alert;
}
private static final int MOST_SPEC = View.MeasureSpec.makeMeasureSpec(999999, View.MeasureSpec.AT_MOST);
public static class TextBlocksLayout extends ViewGroup {
private TextView wholeTextView;
private final int fontSize;
private final int textColor;
public TextBlocksLayout(Context context, int fontSize, int textColor, TextView wholeTextView) {
super(context);
this.fontSize = fontSize;
this.textColor = textColor;
if (wholeTextView != null) {
wholeTextView.setPadding(LoadingTextView2.paddingHorizontal, LoadingTextView2.paddingVertical, LoadingTextView2.paddingHorizontal, LoadingTextView2.paddingVertical);
addView(this.wholeTextView = wholeTextView);
}
}
public void setWholeText(CharSequence wholeText) {
// having focus on that text view can cause jumping scroll to the top after loading a new block
// TODO(dkaraush): preserve selection after setting a new text
wholeTextView.clearFocus();
wholeTextView.setText(wholeText);
}
public LoadingTextView2 addBlock(CharSequence fromText) {
LoadingTextView2 textView = new LoadingTextView2(getContext(), fromText, getBlocksCount() > 0, fontSize, textColor);
addView(textView);
if (wholeTextView != null) {
wholeTextView.bringToFront();
}
return textView;
}
public int getBlocksCount() {
return getChildCount() - (wholeTextView != null ? 1 : 0);
}
public LoadingTextView2 getBlockAt(int i) {
View child = getChildAt(i);
if (child instanceof LoadingTextView2) {
return (LoadingTextView2) child;
}
return null;
}
public LoadingTextView2 getFirstUnloadedBlock() {
final int count = getBlocksCount();
for (int i = 0; i < count; ++i) {
LoadingTextView2 block = getBlockAt(i);
if (block != null && !block.loaded)
return block;
}
return null;
}
private static final int gap = -LoadingTextView2.paddingVertical * 4 + dp(.48f);
public int height() {
int height = 0;
final int count = getBlocksCount();
for (int i = 0; i < count; ++i) {
height += getBlockAt(i).height();
}
return getPaddingTop() + height + getPaddingBottom();
}
protected void onHeightUpdated(int height, int dy) {}
public void updateHeight() {
boolean updated;
int newHeight = height();
int dy = 0;
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) getLayoutParams();
if (lp == null) {
lp = new RecyclerView.LayoutParams(LayoutParams.MATCH_PARENT, newHeight);
updated = true;
} else {
updated = lp.height != newHeight;
dy = newHeight - lp.height;
lp.height = newHeight;
}
if (updated) {
this.setLayoutParams(lp);
onHeightUpdated(newHeight, dy);
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int count = getBlocksCount();
final int innerWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight(),
MeasureSpec.getMode(widthMeasureSpec)
);
for (int i = 0; i < count; ++i) {
LoadingTextView2 block = getBlockAt(i);
block.measure(innerWidthMeasureSpec, MOST_SPEC);
}
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height(), MeasureSpec.EXACTLY));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int y = 0;
final int count = getBlocksCount();
for (int i = 0; i < count; ++i) {
LoadingTextView2 block = getBlockAt(i);
final int blockHeight = block.height();
final int translationY = i > 0 ? gap : 0;
block.layout(getPaddingLeft(), getPaddingTop() + y + translationY, r - l - getPaddingRight(), getPaddingTop() + y + blockHeight + translationY);
y += blockHeight;
if (i > 0 && i < count - 1) {
y += gap;
}
}
wholeTextView.measure(
MeasureSpec.makeMeasureSpec(r - l - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(b - t - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY)
);
wholeTextView.layout(
getPaddingLeft(),
getPaddingTop(),
(r - l) - getPaddingRight(),
getPaddingTop() + wholeTextView.getMeasuredHeight()
);
}
}
public static class InlineLoadingTextView extends ViewGroup {
public static final int paddingHorizontal = dp(4),
paddingVertical = 0;
public boolean showLoadingText = true;
private final TextView fromTextView;
private final TextView toTextView;
private final ValueAnimator loadingAnimator;
private final long start = SystemClock.elapsedRealtime();
public InlineLoadingTextView(Context context, CharSequence fromText, int fontSize, int textColor) {
super(context);
setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical);
setClipChildren(false);
setWillNotDraw(false);
fromTextView = new TextView(context) {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(MOST_SPEC, MOST_SPEC);
}
};
fromTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize);
fromTextView.setTextColor(textColor);
fromTextView.setText(fromText);
fromTextView.setLines(1);
fromTextView.setMaxLines(1);
fromTextView.setSingleLine(true);
fromTextView.setEllipsize(null);
addView(fromTextView);
toTextView = new TextView(context) {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(MOST_SPEC, MOST_SPEC);
}
};
toTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize);
toTextView.setTextColor(textColor);
toTextView.setLines(1);
toTextView.setMaxLines(1);
toTextView.setSingleLine(true);
toTextView.setEllipsize(null);
addView(toTextView);
int c1 = Theme.getColor(Theme.key_dialogBackground),
c2 = Theme.getColor(Theme.key_dialogBackgroundGray);
LinearGradient gradient = new LinearGradient(0, 0, gradientWidth, 0, new int[]{ c1, c2, c1 }, new float[] { 0, 0.67f, 1f }, Shader.TileMode.REPEAT);
loadingPaint.setShader(gradient);
loadingAnimator = ValueAnimator.ofFloat(0f, 1f);
loadingAnimator.addUpdateListener(a -> invalidate());
loadingAnimator.setDuration(Long.MAX_VALUE);
loadingAnimator.start();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
fromTextView.measure(0, 0);
toTextView.measure(0, 0);
super.onMeasure(
MeasureSpec.makeMeasureSpec(
(int) AndroidUtilities.lerp(fromTextView.getMeasuredWidth(), toTextView.getMeasuredWidth(), loadingT) + getPaddingLeft() + getPaddingRight(),
MeasureSpec.EXACTLY
),
MeasureSpec.makeMeasureSpec(
Math.max(fromTextView.getMeasuredHeight(), toTextView.getMeasuredHeight()),
MeasureSpec.EXACTLY
)
);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
fromTextView.layout(getPaddingLeft(), getPaddingTop(), getPaddingLeft() + fromTextView.getMeasuredWidth(), getPaddingTop() + fromTextView.getMeasuredHeight());
toTextView.layout(getPaddingLeft(), getPaddingTop(), getPaddingLeft() + toTextView.getMeasuredWidth(), getPaddingTop() + toTextView.getMeasuredHeight());
updateWidth();
}
private void updateWidth() {
boolean updated;
int newWidth = (int) AndroidUtilities.lerp(fromTextView.getMeasuredWidth(), toTextView.getMeasuredWidth(), loadingT) + getPaddingLeft() + getPaddingRight();
int newHeight = Math.max(fromTextView.getMeasuredHeight(), toTextView.getMeasuredHeight());
LayoutParams lp = getLayoutParams();
if (lp == null) {
lp = new LinearLayout.LayoutParams(newWidth, newHeight);
updated = true;
} else {
updated = lp.width != newWidth || lp.height != newHeight;
lp.width = newWidth;
lp.height = newHeight;
}
if (updated)
setLayoutParams(lp);
}
protected void onLoadAnimation(float t) {}
public boolean loaded = false;
public float loadingT = 0f;
private ValueAnimator loadedAnimator = null;
public void loaded(CharSequence loadedText) {
loaded(loadedText, 350,null);
}
public void loaded(CharSequence loadedText, Runnable onLoadEnd) {
loaded(loadedText, 350, onLoadEnd);
}
public void loaded(CharSequence loadedText, long duration, Runnable onLoadEnd) {
loaded = true;
toTextView.setText(loadedText);
if (loadingAnimator.isRunning()) {
loadingAnimator.cancel();
}
if (loadedAnimator == null) {
loadedAnimator = ValueAnimator.ofFloat(0f, 1f);
loadedAnimator.addUpdateListener(a -> {
loadingT = (float) a.getAnimatedValue();
updateWidth();
invalidate();
onLoadAnimation(loadingT);
});
loadedAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (onLoadEnd != null)
onLoadEnd.run();
}
});
loadedAnimator.setDuration(duration);
loadedAnimator.setInterpolator(CubicBezierInterpolator.EASE_BOTH);
loadedAnimator.start();
}
}
public void set(CharSequence loadedText) {
loaded = true;
toTextView.setText(loadedText);
if (loadingAnimator.isRunning()) {
loadingAnimator.cancel();
}
if (loadedAnimator != null) {
loadedAnimator.cancel();
loadedAnimator = null;
}
loadingT = 1f;
requestLayout();
updateWidth();
invalidate();
onLoadAnimation(1f);
}
private final RectF rect = new RectF();
private final Path inPath = new Path(),
tempPath = new Path(),
loadingPath = new Path(),
shadePath = new Path();
private final Paint loadingPaint = new Paint();
private final float gradientWidth = dp(350f);
@Override
protected void onDraw(Canvas canvas) {
float w = getWidth(), h = getHeight();
float cx = LocaleController.isRTL ? Math.max(w / 2f, w - 8f) : Math.min(w / 2f, 8f),
cy = Math.min(h / 2f, 8f),
R = (float) Math.sqrt(Math.max(
Math.max(cx*cx + cy*cy, (w-cx)*(w-cx) + cy*cy),
Math.max(cx*cx + (h-cy)*(h-cy), (w-cx)*(w-cx) + (h-cy)*(h-cy))
)),
r = loadingT * R;
inPath.reset();
inPath.addCircle(cx, cy, r, Path.Direction.CW);
canvas.save();
canvas.clipPath(inPath, Region.Op.DIFFERENCE);
loadingPaint.setAlpha((int) ((1f - loadingT) * 255));
float dx = gradientWidth - (((SystemClock.elapsedRealtime() - start) / 1000f * gradientWidth) % gradientWidth);
shadePath.reset();
shadePath.addRect(0, 0, w, h, Path.Direction.CW);
loadingPath.reset();
rect.set(0, 0, w, h);
loadingPath.addRoundRect(rect, dp(4), dp(4), Path.Direction.CW);
canvas.clipPath(loadingPath);
canvas.translate(-dx, 0);
shadePath.offset(dx, 0f, tempPath);
canvas.drawPath(tempPath, loadingPaint);
canvas.translate(dx, 0);
canvas.restore();
if (showLoadingText && fromTextView != null) {
canvas.save();
rect.set(0, 0, w, h);
canvas.clipPath(inPath, Region.Op.DIFFERENCE);
canvas.translate(paddingHorizontal, paddingVertical);
canvas.saveLayerAlpha(rect, (int) (255 * .08f), Canvas.ALL_SAVE_FLAG);
fromTextView.draw(canvas);
canvas.restore();
canvas.restore();
}
if (toTextView != null) {
canvas.save();
canvas.clipPath(inPath);
canvas.translate(paddingHorizontal, paddingVertical);
canvas.saveLayerAlpha(rect, (int) (255 * loadingT), Canvas.ALL_SAVE_FLAG);
toTextView.draw(canvas);
if (loadingT < 1f) {
canvas.restore();
}
canvas.restore();
}
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return false;
}
}
public static class LoadingTextView2 extends ViewGroup {
public static final int paddingHorizontal = dp(4),
paddingVertical = dp(1.5f);
public boolean showLoadingText = true;
private final TextView fromTextView;
private final TextView toTextView;
private final boolean scaleFromZero;
private final ValueAnimator loadingAnimator;
private final long start = SystemClock.elapsedRealtime();
private float scaleT = 1f;
public LoadingTextView2(Context context, CharSequence fromText, boolean scaleFromZero, int fontSize, int textColor) {
super(context);
setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical);
setClipChildren(false);
setWillNotDraw(false);
fromTextView = new TextView(context) {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, MOST_SPEC);
}
};
fromTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize);
fromTextView.setTextColor(textColor);
fromTextView.setText(fromText);
fromTextView.setLines(0);
fromTextView.setMaxLines(0);
fromTextView.setSingleLine(false);
fromTextView.setEllipsize(null);
addView(fromTextView);
toTextView = new TextView(context) {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, MOST_SPEC);
}
};
toTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, fontSize);
toTextView.setTextColor(textColor);
toTextView.setLines(0);
toTextView.setMaxLines(0);
toTextView.setSingleLine(false);
toTextView.setEllipsize(null);
addView(toTextView);
int c1 = Theme.getColor(Theme.key_dialogBackground),
c2 = Theme.getColor(Theme.key_dialogBackgroundGray);
LinearGradient gradient = new LinearGradient(0, 0, gradientWidth, 0, new int[]{ c1, c2, c1 }, new float[] { 0, 0.67f, 1f }, Shader.TileMode.REPEAT);
loadingPaint.setShader(gradient);
this.scaleFromZero = scaleFromZero;
loadingAnimator = ValueAnimator.ofFloat(0f, 1f);
if (scaleFromZero)
scaleT = 0;
loadingAnimator.addUpdateListener(a -> {
invalidate();
if (scaleFromZero) {
boolean scaleTWasNoFull = scaleT < 1f;
scaleT = Math.min(1, (SystemClock.elapsedRealtime() - start) / 400f);
if (scaleTWasNoFull) {
updateHeight();
}
}
});
loadingAnimator.setDuration(Long.MAX_VALUE);
loadingAnimator.start();
}
public int innerHeight() {
return (int) (AndroidUtilities.lerp(fromTextView.getMeasuredHeight(), toTextView.getMeasuredHeight(), loadingT) * scaleT);
}
public int height() {
return getPaddingTop() + innerHeight() + getPaddingBottom();
}
private void updateHeight() {
ViewParent parent = getParent();
if (parent instanceof TextBlocksLayout) {
((TextBlocksLayout) parent).updateHeight();
}
}
public boolean loaded = false;
private float loadingT = 0f;
private ValueAnimator loadedAnimator = null;
public void loaded(CharSequence loadedText, Runnable onLoadEnd) {
loaded = true;
toTextView.setText(loadedText);
layout();
if (loadingAnimator.isRunning()) {
loadingAnimator.cancel();
}
if (loadedAnimator == null) {
loadedAnimator = ValueAnimator.ofFloat(0f, 1f);
loadedAnimator.addUpdateListener(a -> {
loadingT = (float) a.getAnimatedValue();
updateHeight();
invalidate();
});
loadedAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (onLoadEnd != null)
onLoadEnd.run();
}
});
loadedAnimator.setDuration(350);
loadedAnimator.setInterpolator(CubicBezierInterpolator.EASE_BOTH);
loadedAnimator.start();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec),
innerWidth = width - getPaddingLeft() - getPaddingRight();
if (fromTextView.getMeasuredWidth() <= 0 || lastWidth != innerWidth) {
measureChild(fromTextView, innerWidth);
updateLoadingPath();
}
if (toTextView.getMeasuredWidth() <= 0 || lastWidth != innerWidth) {
measureChild(toTextView, innerWidth);
}
lastWidth = innerWidth;
super.onMeasure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height(), MeasureSpec.EXACTLY)
);
}
int lastWidth = 0;
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
layout(r - l - getPaddingLeft() - getPaddingRight(), true);
}
private void layout(int width, boolean force) {
if (lastWidth != width || force) {
layout(lastWidth = width);
}
}
private void layout(int width) {
measureChild(fromTextView, width);
layoutChild(fromTextView, width);
updateLoadingPath();
measureChild(toTextView, width);
layoutChild(toTextView, width);
updateHeight();
}
private void layout() {
layout(lastWidth);
}
private void measureChild(View view, int width) {
view.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MOST_SPEC);
}
private void layoutChild(View view, int width) {
view.layout(getPaddingLeft(), getPaddingTop(), getPaddingLeft() + width, getPaddingTop() + view.getMeasuredHeight());
}
private RectF fetchedPathRect = new RectF();
private void updateLoadingPath() {
if (fromTextView != null && fromTextView.getMeasuredWidth() > 0) {
loadingPath.reset();
Layout loadingLayout = fromTextView.getLayout();
if (loadingLayout != null) {
CharSequence text = loadingLayout.getText();
final int lineCount = loadingLayout.getLineCount();
for (int i = 0; i < lineCount; ++i) {
float s = loadingLayout.getLineLeft(i),
e = loadingLayout.getLineRight(i),
l = Math.min(s, e),
r = Math.max(s, e);
int start = loadingLayout.getLineStart(i),
end = loadingLayout.getLineEnd(i);
boolean hasNonEmptyChar = false;
for (int j = start; j < end; ++j) {
char c = text.charAt(j);
if (c != '\n' && c != '\t' && c != ' ') {
hasNonEmptyChar = true;
break;
}
}
if (!hasNonEmptyChar)
continue;
fetchedPathRect.set(
l - paddingHorizontal,
loadingLayout.getLineTop(i) - paddingVertical,
r + paddingHorizontal,
loadingLayout.getLineBottom(i) + paddingVertical
);
loadingPath.addRoundRect(fetchedPathRect, dp(4), dp(4), Path.Direction.CW);
}
}
}
}
private final RectF rect = new RectF();
private final Path inPath = new Path(),
tempPath = new Path(),
loadingPath = new Path(),
shadePath = new Path();
private final Paint loadingPaint = new Paint();
private final float gradientWidth = dp(350f);
@Override
protected void onDraw(Canvas canvas) {
float w = getWidth(), h = getHeight();
float cx = LocaleController.isRTL ? Math.max(w / 2f, w - 8f) : Math.min(w / 2f, 8f),
cy = Math.min(h / 2f, 8f),
R = (float) Math.sqrt(Math.max(
Math.max(cx*cx + cy*cy, (w-cx)*(w-cx) + cy*cy),
Math.max(cx*cx + (h-cy)*(h-cy), (w-cx)*(w-cx) + (h-cy)*(h-cy))
)),
r = loadingT * R;
inPath.reset();
inPath.addCircle(cx, cy, r, Path.Direction.CW);
canvas.save();
canvas.clipPath(inPath, Region.Op.DIFFERENCE);
loadingPaint.setAlpha((int) ((1f - loadingT) * 255));
float dx = gradientWidth - (((SystemClock.elapsedRealtime() - start) / 1000f * gradientWidth) % gradientWidth);
shadePath.reset();
shadePath.addRect(0, 0, w, h, Path.Direction.CW);
canvas.translate(paddingHorizontal, paddingVertical);
canvas.clipPath(loadingPath);
canvas.translate(-paddingHorizontal, -paddingVertical);
canvas.translate(-dx, 0);
shadePath.offset(dx, 0f, tempPath);
canvas.drawPath(tempPath, loadingPaint);
canvas.translate(dx, 0);
canvas.restore();
if (showLoadingText && fromTextView != null) {
canvas.save();
rect.set(0, 0, w, h);
canvas.clipPath(inPath, Region.Op.DIFFERENCE);
canvas.translate(paddingHorizontal, paddingVertical);
canvas.saveLayerAlpha(rect, (int) (255 * .08f), Canvas.ALL_SAVE_FLAG);
fromTextView.draw(canvas);
canvas.restore();
canvas.restore();
}
if (toTextView != null) {
canvas.save();
canvas.clipPath(inPath);
canvas.translate(paddingHorizontal, paddingVertical);
canvas.saveLayerAlpha(rect, (int) (255 * loadingT), Canvas.ALL_SAVE_FLAG);
toTextView.draw(canvas);
if (loadingT < 1f) {
canvas.restore();
}
canvas.restore();
}
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return false;
}
}
}