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

916 lines
36 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.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Build;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.webkit.WebView;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.core.math.MathUtils;
import androidx.core.view.GestureDetectorCompat;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import androidx.recyclerview.widget.ChatListItemAnimator;
import org.json.JSONObject;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.LocaleController;
import org.telegram.messenger.MediaDataController;
import org.telegram.messenger.MessagesController;
import org.telegram.messenger.NotificationCenter;
import org.telegram.messenger.R;
import org.telegram.messenger.UserObject;
import org.telegram.tgnet.ConnectionsManager;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.ActionBar.ActionBar;
import org.telegram.ui.ActionBar.ActionBarMenu;
import org.telegram.ui.ActionBar.ActionBarMenuItem;
import org.telegram.ui.ActionBar.BaseFragment;
import org.telegram.ui.ActionBar.Theme;
import org.telegram.ui.ChatActivity;
public class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlertLayout implements NotificationCenter.NotificationCenterDelegate {
private final static int POLL_PERIOD = 60000;
private BotWebViewContainer webViewContainer;
private ValueAnimator webViewScrollAnimator;
private boolean ignoreLayout;
private long botId;
private long peerId;
private long queryId;
private boolean silent;
private int replyToMsgId;
private int currentAccount;
private String startCommand;
private boolean needReload;
private WebProgressView progressView;
private WebViewSwipeContainer swipeContainer;
private ActionBarMenuItem otherItem;
private int measureOffsetY;
private long lastSwipeTime;
private boolean ignoreMeasure;
private boolean isBotButtonAvailable;
private boolean destroyed;
private Runnable pollRunnable = () -> {
if (!destroyed) {
TLRPC.TL_messages_prolongWebView prolongWebView = new TLRPC.TL_messages_prolongWebView();
prolongWebView.bot = MessagesController.getInstance(currentAccount).getInputUser(botId);
prolongWebView.peer = MessagesController.getInstance(currentAccount).getInputPeer(peerId);
prolongWebView.query_id = queryId;
prolongWebView.silent = silent;
if (replyToMsgId != 0) {
prolongWebView.reply_to_msg_id = replyToMsgId;
prolongWebView.flags |= 1;
}
ConnectionsManager.getInstance(currentAccount).sendRequest(prolongWebView, (response, error) -> AndroidUtilities.runOnUIThread(() -> {
if (destroyed) {
return;
}
if (error != null) {
parentAlert.dismiss();
} else {
AndroidUtilities.runOnUIThread(this.pollRunnable, POLL_PERIOD);
}
}));
}
};
public ChatAttachAlertBotWebViewLayout(ChatAttachAlert alert, Context context, Theme.ResourcesProvider resourcesProvider) {
super(alert, context, resourcesProvider);
ActionBarMenu menu = parentAlert.actionBar.createMenu();
otherItem = menu.addItem(0, R.drawable.ic_ab_other);
otherItem.addSubItem(R.id.menu_open_bot, R.drawable.msg_bot, LocaleController.getString(R.string.BotWebViewOpenBot));
otherItem.addSubItem(R.id.menu_reload_page, R.drawable.msg_retry, LocaleController.getString(R.string.BotWebViewReloadPage));
otherItem.addSubItem(R.id.menu_delete_bot, R.drawable.msg_delete, LocaleController.getString(R.string.BotWebViewDeleteBot));
parentAlert.actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() {
@Override
public void onItemClick(int id) {
if (id == -1) {
parentAlert.dismiss();
} else if (id == R.id.menu_open_bot) {
Bundle bundle = new Bundle();
bundle.putLong("user_id", botId);
parentAlert.baseFragment.presentFragment(new ChatActivity(bundle));
parentAlert.dismiss();
} else if (id == R.id.menu_reload_page) {
if (webViewContainer.getWebView() != null) {
webViewContainer.getWebView().animate().cancel();
webViewContainer.getWebView().animate().alpha(0).start();
}
progressView.setLoadProgress(0);
progressView.setAlpha(1f);
progressView.setVisibility(VISIBLE);
webViewContainer.setBotUser(MessagesController.getInstance(currentAccount).getUser(botId));
webViewContainer.loadFlicker(currentAccount, botId);
webViewContainer.reload();
} else if (id == R.id.menu_delete_bot) {
for (TLRPC.TL_attachMenuBot bot : MediaDataController.getInstance(currentAccount).getAttachMenuBots().bots) {
if (bot.bot_id == botId) {
parentAlert.onLongClickBotButton(bot, MessagesController.getInstance(currentAccount).getUser(botId));
break;
}
}
}
}
});
webViewContainer = new BotWebViewContainer(context, resourcesProvider, getThemedColor(Theme.key_dialogBackground)) {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
if (!isBotButtonAvailable) {
isBotButtonAvailable = true;
webViewContainer.restoreButtonData();
}
}
return super.dispatchTouchEvent(ev);
}
};
swipeContainer = new WebViewSwipeContainer(context) {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec) - ActionBar.getCurrentActionBarHeight() - AndroidUtilities.dp(84) + measureOffsetY, MeasureSpec.EXACTLY));
}
};
swipeContainer.addView(webViewContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
swipeContainer.setScrollListener(() -> {
parentAlert.updateLayout(this, true, 0);
webViewContainer.invalidateViewPortHeight();
lastSwipeTime = System.currentTimeMillis();
});
swipeContainer.setScrollEndListener(()-> webViewContainer.invalidateViewPortHeight(true));
swipeContainer.setDelegate(() -> parentAlert.dismiss());
addView(swipeContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT));
addView(progressView = new WebProgressView(context, resourcesProvider), LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM, 0, 0, 0, 84));
webViewContainer.setWebViewProgressListener(progress -> {
progressView.setLoadProgressAnimated(progress);
if (progress == 1f) {
ValueAnimator animator = ValueAnimator.ofFloat(1, 0).setDuration(200);
animator.setInterpolator(CubicBezierInterpolator.DEFAULT);
animator.addUpdateListener(animation -> progressView.setAlpha((Float) animation.getAnimatedValue()));
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
progressView.setVisibility(GONE);
}
});
animator.start();
requestEnableKeyboard();
}
});
NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.didSetNewTheme);
}
public boolean canExpandByRequest() {
return /* System.currentTimeMillis() - lastSwipeTime > 1000 && */ !swipeContainer.isSwipeInProgress();
}
public void setMeasureOffsetY(int measureOffsetY) {
this.measureOffsetY = measureOffsetY;
swipeContainer.requestLayout();
}
public void disallowSwipeOffsetAnimation() {
swipeContainer.setSwipeOffsetAnimationDisallowed(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (ignoreMeasure) {
setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
@Override
public void onPanTransitionStart(boolean keyboardVisible, int contentHeight) {
if (!keyboardVisible) {
return;
}
webViewContainer.setViewPortByMeasureSuppressed(true);
boolean doNotScroll = false;
float openOffset = -swipeContainer.getOffsetY() + swipeContainer.getTopActionBarOffsetY();
if (swipeContainer.getSwipeOffsetY() != openOffset) {
swipeContainer.stickTo(openOffset);
doNotScroll = true;
}
int oldh = contentHeight + parentAlert.sizeNotifierFrameLayout.measureKeyboardHeight();
setMeasuredDimension(getMeasuredWidth(), contentHeight);
ignoreMeasure = true;
swipeContainer.setSwipeOffsetAnimationDisallowed(true);
if (!doNotScroll) {
if (webViewScrollAnimator != null) {
webViewScrollAnimator.cancel();
webViewScrollAnimator = null;
}
if (webViewContainer.getWebView() != null) {
int fromY = webViewContainer.getWebView().getScrollY();
int toY = fromY + (oldh - contentHeight);
webViewScrollAnimator = ValueAnimator.ofInt(fromY, toY).setDuration(250);
webViewScrollAnimator.setInterpolator(ChatListItemAnimator.DEFAULT_INTERPOLATOR);
webViewScrollAnimator.addUpdateListener(animation -> {
int val = (int) animation.getAnimatedValue();
if (webViewContainer.getWebView() != null) {
webViewContainer.getWebView().setScrollY(val);
}
});
webViewScrollAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (webViewContainer.getWebView() != null) {
webViewContainer.getWebView().setScrollY(toY);
}
if (animation == webViewScrollAnimator) {
webViewScrollAnimator = null;
}
}
});
webViewScrollAnimator.start();
}
}
}
@Override
public void onPanTransitionEnd() {
ignoreMeasure = false;
swipeContainer.setSwipeOffsetAnimationDisallowed(false);
webViewContainer.setViewPortByMeasureSuppressed(false);
requestLayout();
}
@Override
void onShow(ChatAttachAlert.AttachAlertLayout previousLayout) {
parentAlert.actionBar.setTitle(UserObject.getUserName(MessagesController.getInstance(currentAccount).getUser(botId)));
swipeContainer.setSwipeOffsetY(0);
if (webViewContainer.getWebView() != null) {
webViewContainer.getWebView().scrollTo(0, 0);
}
if (parentAlert.getBaseFragment() != null) {
webViewContainer.setParentActivity(parentAlert.getBaseFragment().getParentActivity());
}
otherItem.setVisibility(VISIBLE);
}
@Override
void onShown() {
if (webViewContainer.isPageLoaded()) {
requestEnableKeyboard();
}
swipeContainer.setSwipeOffsetAnimationDisallowed(false);
AndroidUtilities.runOnUIThread(() -> webViewContainer.restoreButtonData());
}
private void requestEnableKeyboard() {
BaseFragment fragment = parentAlert.getBaseFragment();
if (fragment instanceof ChatActivity && ((ChatActivity) fragment).contentView.measureKeyboardHeight() > AndroidUtilities.dp(20)) {
AndroidUtilities.hideKeyboard(parentAlert.baseFragment.getFragmentView());
AndroidUtilities.runOnUIThread(this::requestEnableKeyboard, 250);
return;
}
parentAlert.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
setFocusable(true);
parentAlert.setFocusable(true);
}
@Override
void onHidden() {
super.onHidden();
parentAlert.setFocusable(false);
parentAlert.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
}
@Override
int getCurrentItemTop() {
return (int) (swipeContainer.getSwipeOffsetY() + swipeContainer.getOffsetY());
}
@Override
public void setTranslationY(float translationY) {
super.setTranslationY(translationY);
parentAlert.getSheetContainer().invalidate();
}
public String getStartCommand() {
return startCommand;
}
public void requestWebView(int currentAccount, long peerId, long botId, boolean silent, int replyToMsgId) {
requestWebView(currentAccount, peerId, botId, silent, replyToMsgId, null);
}
public void requestWebView(int currentAccount, long peerId, long botId, boolean silent, int replyToMsgId, String startCommand) {
this.currentAccount = currentAccount;
this.peerId = peerId;
this.botId = botId;
this.silent = silent;
this.replyToMsgId = replyToMsgId;
this.startCommand = startCommand;
webViewContainer.setBotUser(MessagesController.getInstance(currentAccount).getUser(botId));
webViewContainer.loadFlicker(currentAccount, botId);
TLRPC.TL_messages_requestWebView req = new TLRPC.TL_messages_requestWebView();
req.peer = MessagesController.getInstance(currentAccount).getInputPeer(peerId);
req.bot = MessagesController.getInstance(currentAccount).getInputUser(botId);
req.silent = silent;
if (startCommand != null) {
req.start_param = startCommand;
req.flags |= 8;
}
if (replyToMsgId != 0) {
req.reply_to_msg_id = replyToMsgId;
req.flags |= 1;
}
try {
JSONObject jsonObject = new JSONObject();
jsonObject.put("bg_color", getThemedColor(Theme.key_dialogBackground));
jsonObject.put("text_color", getThemedColor(Theme.key_windowBackgroundWhiteBlackText));
jsonObject.put("hint_color", getThemedColor(Theme.key_windowBackgroundWhiteHintText));
jsonObject.put("link_color", getThemedColor(Theme.key_windowBackgroundWhiteLinkText));
jsonObject.put("button_color", getThemedColor(Theme.key_featuredStickers_addButton));
jsonObject.put("button_text_color", getThemedColor(Theme.key_featuredStickers_buttonText));
req.theme_params = new TLRPC.TL_dataJSON();
req.theme_params.data = jsonObject.toString();
req.flags |= 4;
} catch (Exception e) {
FileLog.e(e);
}
ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> {
if (response instanceof TLRPC.TL_webViewResultUrl) {
TLRPC.TL_webViewResultUrl resultUrl = (TLRPC.TL_webViewResultUrl) response;
queryId = resultUrl.query_id;
webViewContainer.loadUrl(resultUrl.url);
swipeContainer.setWebView(webViewContainer.getWebView());
AndroidUtilities.runOnUIThread(pollRunnable);
}
}));
NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.webViewResultSent);
}
@Override
void onDestroy() {
NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.webViewResultSent);
NotificationCenter.getGlobalInstance().removeObserver(this, NotificationCenter.didSetNewTheme);
ActionBarMenu menu = parentAlert.actionBar.createMenu();
otherItem.removeAllSubItems();
menu.removeView(otherItem);
webViewContainer.destroyWebView();
destroyed = true;
AndroidUtilities.cancelRunOnUIThread(pollRunnable);
}
@Override
void onHide() {
super.onHide();
otherItem.setVisibility(GONE);
isBotButtonAvailable = false;
if (webViewContainer.hasUserPermissions()) {
webViewContainer.destroyWebView();
needReload = true;
}
}
public boolean needReload() {
if (needReload) {
needReload = false;
return true;
}
return false;
}
@Override
int getListTopPadding() {
return (int) swipeContainer.getOffsetY();
}
@Override
int getFirstOffset() {
return getListTopPadding() + AndroidUtilities.dp(56);
}
@Override
void onPreMeasure(int availableWidth, int availableHeight) {
int padding;
if (!AndroidUtilities.isTablet() && AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y) {
padding = (int) (availableHeight / 3.5f);
} else {
padding = (availableHeight / 5 * 2);
}
parentAlert.setAllowNestedScroll(true);
if (padding < 0) {
padding = 0;
}
if (swipeContainer.getOffsetY() != padding) {
ignoreLayout = true;
swipeContainer.setOffsetY(padding);
ignoreLayout = false;
}
}
@Override
int getButtonsHideOffset() {
return (int) swipeContainer.getTopActionBarOffsetY() + AndroidUtilities.dp(12);
}
@Override
boolean onBackPressed() {
return webViewContainer.onBackPressed();
}
@Override
public void requestLayout() {
if (ignoreLayout) {
return;
}
super.requestLayout();
}
@Override
void scrollToTop() {
swipeContainer.stickTo(-swipeContainer.getOffsetY() + swipeContainer.getTopActionBarOffsetY());
}
@Override
boolean shouldHideBottomButtons() {
return false;
}
@Override
int needsActionBar() {
return 1;
}
public BotWebViewContainer getWebViewContainer() {
return webViewContainer;
}
public void setDelegate(BotWebViewContainer.Delegate delegate) {
webViewContainer.setDelegate(delegate);
}
public boolean isBotButtonAvailable() {
return isBotButtonAvailable;
}
@Override
public void didReceivedNotification(int id, int account, Object... args) {
if (id == NotificationCenter.webViewResultSent) {
long queryId = (long) args[0];
if (this.queryId == queryId) {
webViewContainer.destroyWebView();
needReload = true;
parentAlert.dismiss();
}
} else if (id == NotificationCenter.didSetNewTheme) {
webViewContainer.updateFlickerBackgroundColor(getThemedColor(Theme.key_dialogBackground));
}
}
public static class WebViewSwipeContainer extends FrameLayout {
public final static SimpleFloatPropertyCompat<WebViewSwipeContainer> SWIPE_OFFSET_Y = new SimpleFloatPropertyCompat<>("swipeOffsetY", WebViewSwipeContainer::getSwipeOffsetY, WebViewSwipeContainer::setSwipeOffsetY);
private GestureDetectorCompat gestureDetector;
private boolean isScrolling;
private boolean isSwipeDisallowed;
private float topActionBarOffsetY = ActionBar.getCurrentActionBarHeight();
private float offsetY = -1;
private float pendingOffsetY = -1;
private float swipeOffsetY;
private boolean isSwipeOffsetAnimationDisallowed;
private ValueAnimator offsetYAnimator;
private boolean flingInProgress;
private WebView webView;
private Runnable scrollListener;
private Runnable scrollEndListener;
private Delegate delegate;
private SpringAnimation scrollAnimator;
private int swipeStickyRange;
public WebViewSwipeContainer(@NonNull Context context) {
super(context);
int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
gestureDetector = new GestureDetectorCompat(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (isSwipeDisallowed) {
return false;
}
if (velocityY >= 700 && (webView == null || webView.getScrollY() == 0)) {
flingInProgress = true;
if (swipeOffsetY >= swipeStickyRange) {
if (delegate != null) {
delegate.onDismiss();
}
} else {
stickTo(0);
}
return true;
} else if (velocityY <= -700 && swipeOffsetY > -offsetY + topActionBarOffsetY) {
flingInProgress = true;
stickTo(-offsetY + topActionBarOffsetY);
return true;
}
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if (!isScrolling && !isSwipeDisallowed) {
if (Math.abs(distanceY) >= touchSlop && Math.abs(distanceY) * 1.5f >= Math.abs(distanceX) && (swipeOffsetY != -offsetY + topActionBarOffsetY || webView == null || distanceY < 0 && webView.getScrollY() == 0)) {
isScrolling = true;
MotionEvent ev = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0);
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).dispatchTouchEvent(ev);
}
ev.recycle();
return true;
} else if (webView != null && webView.canScrollHorizontally(distanceX >= 0 ? 1 : -1)) {
isSwipeDisallowed = true;
}
}
if (isScrolling) {
if (distanceY < 0) {
if (swipeOffsetY > -offsetY + topActionBarOffsetY) {
swipeOffsetY -= distanceY;
} else if (webView != null) {
float newWebScrollY = webView.getScrollY() + distanceY;
webView.setScrollY((int) MathUtils.clamp(newWebScrollY, 0, Math.max(webView.getContentHeight(), webView.getHeight()) - topActionBarOffsetY));
if (newWebScrollY < 0) {
swipeOffsetY -= newWebScrollY;
}
} else {
swipeOffsetY -= distanceY;
}
} else {
swipeOffsetY -= distanceY;
if (webView != null && swipeOffsetY < -offsetY + topActionBarOffsetY) {
float newWebScrollY = webView.getScrollY() - (swipeOffsetY + offsetY - topActionBarOffsetY);
webView.setScrollY((int) MathUtils.clamp(newWebScrollY, 0, Math.max(webView.getContentHeight(), webView.getHeight()) - topActionBarOffsetY));
}
}
swipeOffsetY = MathUtils.clamp(swipeOffsetY, -offsetY + topActionBarOffsetY, getHeight() - offsetY + topActionBarOffsetY);
invalidateTranslation();
return true;
}
return true;
}
});
updateStickyRange();
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updateStickyRange();
}
private void updateStickyRange() {
swipeStickyRange = AndroidUtilities.dp(AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y ? 8 : 64);
}
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
super.requestDisallowInterceptTouchEvent(disallowIntercept);
if (disallowIntercept) {
isSwipeDisallowed = true;
isScrolling = false;
}
}
public void setSwipeOffsetAnimationDisallowed(boolean swipeOffsetAnimationDisallowed) {
isSwipeOffsetAnimationDisallowed = swipeOffsetAnimationDisallowed;
}
public void setScrollListener(Runnable scrollListener) {
this.scrollListener = scrollListener;
}
public void setScrollEndListener(Runnable scrollEndListener) {
this.scrollEndListener = scrollEndListener;
}
public void setWebView(WebView webView) {
this.webView = webView;
}
public void setTopActionBarOffsetY(float topActionBarOffsetY) {
this.topActionBarOffsetY = topActionBarOffsetY;
invalidateTranslation();
}
public void setSwipeOffsetY(float swipeOffsetY) {
this.swipeOffsetY = swipeOffsetY;
invalidateTranslation();
}
public void setOffsetY(float offsetY) {
float wasOffsetY = this.offsetY;
boolean wasOnTop = swipeOffsetY == -wasOffsetY + topActionBarOffsetY;
if (wasOffsetY != -1 && !isSwipeOffsetAnimationDisallowed) {
offsetYAnimator = ValueAnimator.ofFloat(wasOffsetY, offsetY).setDuration(200);
offsetYAnimator.setInterpolator(CubicBezierInterpolator.DEFAULT);
offsetYAnimator.addUpdateListener(animation -> {
this.offsetY = (float) animation.getAnimatedValue();
if (wasOnTop) {
swipeOffsetY = -this.offsetY + topActionBarOffsetY;
} else {
swipeOffsetY = MathUtils.clamp(swipeOffsetY, -this.offsetY + topActionBarOffsetY, getHeight() - this.offsetY + topActionBarOffsetY);
}
if (scrollAnimator != null && scrollAnimator.getSpring().getFinalPosition() == -wasOffsetY + topActionBarOffsetY) {
scrollAnimator.getSpring().setFinalPosition(-offsetY + topActionBarOffsetY);
}
invalidateTranslation();
});
offsetYAnimator.addListener(new AnimatorListenerAdapter() {
private boolean isCanceled;
@Override
public void onAnimationEnd(Animator animation) {
if (offsetYAnimator == animation) {
offsetYAnimator = null;
if (!isCanceled) {
WebViewSwipeContainer.this.offsetY = offsetY;
invalidateTranslation();
} else {
pendingOffsetY = offsetY;
}
}
}
@Override
public void onAnimationCancel(Animator animation) {
isCanceled = true;
}
});
offsetYAnimator.start();
} else {
this.offsetY = offsetY;
if (wasOnTop) {
this.swipeOffsetY = -this.offsetY + topActionBarOffsetY;
}
invalidateTranslation();
}
}
private void invalidateTranslation() {
setTranslationY(Math.max(topActionBarOffsetY, offsetY + swipeOffsetY));
if (scrollListener != null) {
scrollListener.run();
}
}
public float getTopActionBarOffsetY() {
return topActionBarOffsetY;
}
public float getOffsetY() {
return offsetY;
}
public float getSwipeOffsetY() {
return swipeOffsetY;
}
public void setDelegate(Delegate delegate) {
this.delegate = delegate;
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (isScrolling && ev.getActionIndex() != 0) {
return false;
}
MotionEvent rawEvent = MotionEvent.obtain(ev);
int index = ev.getActionIndex();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
rawEvent.setLocation(ev.getRawX(index), ev.getRawY(index));
} else {
float offsetX = ev.getRawX() - ev.getX(), offsetY = ev.getRawY() - ev.getY();
rawEvent.setLocation(ev.getX(index) + offsetX, ev.getY(index) + offsetY);
}
boolean detector = gestureDetector.onTouchEvent(rawEvent);
rawEvent.recycle();
if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL) {
isSwipeDisallowed = false;
isScrolling = false;
if (flingInProgress) {
flingInProgress = false;
} else {
if (swipeOffsetY <= -swipeStickyRange) {
if (swipeOffsetY != -offsetY + topActionBarOffsetY) {
stickTo(-offsetY + topActionBarOffsetY);
}
} else if (swipeOffsetY > -swipeStickyRange && swipeOffsetY <= swipeStickyRange) {
if (swipeOffsetY != 0) {
stickTo(0);
}
} else {
if (delegate != null) {
delegate.onDismiss();
}
}
}
}
boolean superTouch = super.dispatchTouchEvent(ev);
if (!superTouch && !detector && ev.getAction() == MotionEvent.ACTION_DOWN) {
return true;
}
return superTouch || detector;
}
public void stickTo(float offset) {
stickTo(offset, null);
}
public void stickTo(float offset, Runnable callback) {
if (swipeOffsetY == offset || scrollAnimator != null && scrollAnimator.getSpring().getFinalPosition() == offset) {
if (callback != null) {
callback.run();
}
if (scrollEndListener != null) {
scrollEndListener.run();
}
return;
}
if (offsetYAnimator != null) {
offsetYAnimator.cancel();
}
if (scrollAnimator != null) {
scrollAnimator.cancel();
}
scrollAnimator = new SpringAnimation(this, SWIPE_OFFSET_Y, offset)
.setSpring(new SpringForce(offset)
.setStiffness(1400)
.setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY))
.addEndListener((animation, canceled, value, velocity) -> {
if (animation == scrollAnimator) {
scrollAnimator = null;
if (callback != null) {
callback.run();
}
if (scrollEndListener != null) {
scrollEndListener.run();
}
if (pendingOffsetY != -1) {
boolean wasDisallowed = isSwipeOffsetAnimationDisallowed;
isSwipeOffsetAnimationDisallowed = true;
setOffsetY(pendingOffsetY);
pendingOffsetY = -1;
isSwipeOffsetAnimationDisallowed = wasDisallowed;
}
}
});
scrollAnimator.start();
}
public boolean isSwipeInProgress() {
return isScrolling;
}
public interface Delegate {
/**
* Called to dismiss parent layout
*/
void onDismiss();
}
}
public final static class WebProgressView extends View {
private final SimpleFloatPropertyCompat<WebProgressView> LOAD_PROGRESS_PROPERTY = new SimpleFloatPropertyCompat<>("loadProgress", obj -> obj.loadProgress, WebProgressView::setLoadProgress).setMultiplier(100f);
private Paint bluePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private float loadProgress;
private SpringAnimation springAnimation;
private Theme.ResourcesProvider resourcesProvider;
public WebProgressView(Context context, Theme.ResourcesProvider resourcesProvider) {
super(context);
this.resourcesProvider = resourcesProvider;
bluePaint.setColor(getThemedColor(Theme.key_featuredStickers_addButton));
bluePaint.setStyle(Paint.Style.STROKE);
bluePaint.setStrokeWidth(AndroidUtilities.dp(2));
bluePaint.setStrokeCap(Paint.Cap.ROUND);
}
protected int getThemedColor(String key) {
Integer color = resourcesProvider != null ? resourcesProvider.getColor(key) : null;
return color != null ? color : Theme.getColor(key);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
springAnimation = new SpringAnimation(this, LOAD_PROGRESS_PROPERTY)
.setSpring(new SpringForce()
.setStiffness(400f)
.setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
springAnimation.cancel();
springAnimation = null;
}
public void setLoadProgressAnimated(float loadProgress) {
if (springAnimation == null) {
setLoadProgress(loadProgress);
return;
}
springAnimation.getSpring().setFinalPosition(loadProgress * 100f);
springAnimation.start();
}
public void setLoadProgress(float loadProgress) {
this.loadProgress = loadProgress;
invalidate();
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
float y = getHeight() - bluePaint.getStrokeWidth() / 2f;
canvas.drawLine(0, y, getWidth() * loadProgress, y, bluePaint);
}
}
}