Java tutorial
/* * This is the source code of Telegram for Android v. 3.x.x. * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; import android.Manifest; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.database.DataSetObserver; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.SurfaceTexture; import android.graphics.Typeface; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.text.Layout; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; import android.text.style.MetricAffectingSpan; import android.util.TypedValue; import android.view.GestureDetector; import android.view.Gravity; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.SoundEffectConstants; import android.view.TextureView; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowManager; import android.view.animation.DecelerateInterpolator; import android.webkit.CookieManager; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.Emoji; import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.ImageLoader; import org.telegram.messenger.ImageReceiver; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MediaController; import org.telegram.messenger.MessageObject; import org.telegram.messenger.MessagesController; import org.telegram.messenger.MessagesStorage; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; import org.telegram.messenger.browser.Browser; import org.telegram.messenger.exoplayer2.C; import org.telegram.messenger.exoplayer2.ExoPlayer; import org.telegram.messenger.exoplayer2.ui.AspectRatioFrameLayout; import org.telegram.messenger.support.widget.GridLayoutManager; import org.telegram.messenger.support.widget.LinearLayoutManager; import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; 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.AlertDialog; import org.telegram.ui.ActionBar.BackDrawable; import org.telegram.ui.ActionBar.BottomSheet; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.AnimatedFileDrawable; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.ClippingImageView; import org.telegram.ui.Components.ContextProgressView; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.LinkPath; import org.telegram.ui.Components.RadialProgress; import org.telegram.ui.Components.RecyclerListView; import org.telegram.ui.Components.Scroller; import org.telegram.ui.Components.SeekBar; import org.telegram.ui.Components.ShareAlert; import org.telegram.ui.Components.TextPaintSpan; import org.telegram.ui.Components.TextPaintUrlSpan; import org.telegram.ui.Components.VideoPlayer; import org.telegram.ui.Components.WebPlayerView; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.Locale; @TargetApi(16) public class ArticleViewer implements NotificationCenter.NotificationCenterDelegate, GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener { private Activity parentActivity; private ArrayList<BlockEmbedCell> createdWebViews = new ArrayList<>(); private View customView; private FrameLayout fullscreenVideoContainer; private TextureView fullscreenTextureView; private AspectRatioFrameLayout fullscreenAspectRatioView; private WebChromeClient.CustomViewCallback customViewCallback; private Object lastInsets; private boolean isVisible; private boolean collapsed; private boolean attachedToWindow; private int animationInProgress; private Runnable animationEndRunnable; private long transitionAnimationStartTime; private TLRPC.WebPage currentPage; private ArrayList<TLRPC.WebPage> pagesStack = new ArrayList<>(); private WindowManager.LayoutParams windowLayoutParams; private WindowView windowView; private View barBackground; private FrameLayout containerView; private View photoContainerBackground; private FrameLayoutDrawer photoContainerView; private WebpageAdapter adapter; private FrameLayout headerView; private ImageView backButton; private ImageView shareButton; private FrameLayout shareContainer; private ContextProgressView progressView; private BackDrawable backDrawable; private RecyclerListView listView; private LinearLayoutManager layoutManager; private Dialog visibleDialog; private Paint backgroundPaint; private Drawable layerShadowDrawable; private Paint scrimPaint; private AnimatorSet progressViewAnimation; private WebPlayerView currentPlayingVideo; private WebPlayerView fullscreenedVideo; private Drawable slideDotDrawable; private Drawable slideDotBigDrawable; private int openUrlReqId; private int previewsReqId; private int currentHeaderHeight; private boolean checkingForLongPress = false; private CheckForLongPress pendingCheckForLongPress = null; private int pressCount = 0; private CheckForTap pendingCheckForTap = null; private TextPaintUrlSpan pressedLink; private StaticLayout pressedLinkOwnerLayout; private View pressedLinkOwnerView; private LinkPath urlPath = new LinkPath(); public ArrayList<TLRPC.PageBlock> blocks = new ArrayList<>(); public ArrayList<TLRPC.PageBlock> photoBlocks = new ArrayList<>(); public HashMap<String, Integer> anchors = new HashMap<>(); @SuppressLint("StaticFieldLeak") private static volatile ArticleViewer Instance = null; public static ArticleViewer getInstance() { ArticleViewer localInstance = Instance; if (localInstance == null) { synchronized (ArticleViewer.class) { localInstance = Instance; if (localInstance == null) { Instance = localInstance = new ArticleViewer(); } } } return localInstance; } private class FrameLayoutDrawer extends FrameLayout { public FrameLayoutDrawer(Context context) { super(context); } /*@Override public boolean onInterceptTouchEvent(MotionEvent event) { boolean result = super.onInterceptTouchEvent(event); if (!result) { processTouchEvent(event); result = true; } return result; }*/ @Override public boolean onTouchEvent(MotionEvent event) { processTouchEvent(event); return true; } @Override protected void onDraw(Canvas canvas) { drawContent(canvas); } @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child != aspectRatioFrameLayout && super.drawChild(canvas, child, drawingTime); } } private final class CheckForTap implements Runnable { public void run() { if (pendingCheckForLongPress == null) { pendingCheckForLongPress = new CheckForLongPress(); } pendingCheckForLongPress.currentPressCount = ++pressCount; if (windowView != null) { windowView.postDelayed(pendingCheckForLongPress, ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout()); } } } private class WindowView extends FrameLayout { private Runnable attachRunnable; private boolean selfLayout; private int startedTrackingPointerId; private boolean maybeStartTracking; private boolean startedTracking; private int startedTrackingX; private int startedTrackingY; private VelocityTracker tracker; private boolean closeAnimationInProgress; private float innerTranslationX; private float alpha; public WindowView(Context context) { super(context); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSize = View.MeasureSpec.getSize(widthMeasureSpec); int heightSize = View.MeasureSpec.getSize(heightMeasureSpec); if (Build.VERSION.SDK_INT >= 21 && lastInsets != null) { setMeasuredDimension(widthSize, heightSize); WindowInsets insets = (WindowInsets) lastInsets; if (AndroidUtilities.incorrectDisplaySizeFix) { if (heightSize > AndroidUtilities.displaySize.y) { heightSize = AndroidUtilities.displaySize.y; } heightSize += AndroidUtilities.statusBarHeight; } heightSize -= insets.getSystemWindowInsetBottom(); widthSize -= insets.getSystemWindowInsetRight() + insets.getSystemWindowInsetLeft(); if (insets.getSystemWindowInsetRight() != 0) { barBackground.measure( View.MeasureSpec.makeMeasureSpec(insets.getSystemWindowInsetRight(), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(heightSize, View.MeasureSpec.EXACTLY)); } else if (insets.getSystemWindowInsetLeft() != 0) { barBackground.measure( View.MeasureSpec.makeMeasureSpec(insets.getSystemWindowInsetLeft(), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(heightSize, View.MeasureSpec.EXACTLY)); } else { barBackground.measure(View.MeasureSpec.makeMeasureSpec(widthSize, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(insets.getSystemWindowInsetBottom(), View.MeasureSpec.EXACTLY)); } } else { setMeasuredDimension(widthSize, heightSize); } containerView.measure(View.MeasureSpec.makeMeasureSpec(widthSize, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(heightSize, View.MeasureSpec.EXACTLY)); photoContainerView.measure(View.MeasureSpec.makeMeasureSpec(widthSize, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(heightSize, View.MeasureSpec.EXACTLY)); photoContainerBackground.measure(View.MeasureSpec.makeMeasureSpec(widthSize, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(heightSize, View.MeasureSpec.EXACTLY)); fullscreenVideoContainer.measure(View.MeasureSpec.makeMeasureSpec(widthSize, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(heightSize, View.MeasureSpec.EXACTLY)); ViewGroup.LayoutParams layoutParams = animatingImageView.getLayoutParams(); animatingImageView.measure( View.MeasureSpec.makeMeasureSpec(layoutParams.width, View.MeasureSpec.AT_MOST), View.MeasureSpec.makeMeasureSpec(layoutParams.height, View.MeasureSpec.AT_MOST)); } @SuppressWarnings("DrawAllocation") @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { if (selfLayout) { return; } int x; if (Build.VERSION.SDK_INT >= 21 && lastInsets != null) { WindowInsets insets = (WindowInsets) lastInsets; x = insets.getSystemWindowInsetLeft(); if (insets.getSystemWindowInsetRight() != 0) { barBackground.layout(right - left - insets.getSystemWindowInsetRight(), 0, right - left, bottom - top); } else if (insets.getSystemWindowInsetLeft() != 0) { barBackground.layout(0, 0, insets.getSystemWindowInsetLeft(), bottom - top); } else { barBackground.layout(0, bottom - top - insets.getStableInsetBottom(), right - left, bottom - top); } } else { x = 0; } containerView.layout(x, 0, x + containerView.getMeasuredWidth(), containerView.getMeasuredHeight()); photoContainerView.layout(x, 0, x + photoContainerView.getMeasuredWidth(), photoContainerView.getMeasuredHeight()); photoContainerBackground.layout(x, 0, x + photoContainerBackground.getMeasuredWidth(), photoContainerBackground.getMeasuredHeight()); fullscreenVideoContainer.layout(x, 0, x + fullscreenVideoContainer.getMeasuredWidth(), fullscreenVideoContainer.getMeasuredHeight()); animatingImageView.layout(0, 0, animatingImageView.getMeasuredWidth(), animatingImageView.getMeasuredHeight()); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); attachedToWindow = true; } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); attachedToWindow = false; } @Override public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { handleTouchEvent(null); super.requestDisallowInterceptTouchEvent(disallowIntercept); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return !collapsed && (handleTouchEvent(ev) || super.onInterceptTouchEvent(ev)); } @Override public boolean onTouchEvent(MotionEvent event) { return !collapsed && (handleTouchEvent(event) || super.onTouchEvent(event)); } public void setInnerTranslationX(float value) { innerTranslationX = value; if (parentActivity instanceof LaunchActivity) { ((LaunchActivity) parentActivity).drawerLayoutContainer .setAllowDrawContent(!isVisible || alpha != 1.0f || innerTranslationX != 0); } invalidate(); } @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { int width = getMeasuredWidth(); int translationX = (int) innerTranslationX; final int restoreCount = canvas.save(); canvas.clipRect(translationX, 0, width, getHeight()); final boolean result = super.drawChild(canvas, child, drawingTime); canvas.restoreToCount(restoreCount); if (translationX != 0 && child == containerView) { float opacity = Math.min(0.8f, (width - translationX) / (float) width); if (opacity < 0) { opacity = 0; } scrimPaint.setColor((int) (((0x99000000 & 0xff000000) >>> 24) * opacity) << 24); canvas.drawRect(0, 0, translationX, getHeight(), scrimPaint); final float alpha = Math.max(0, Math.min((width - translationX) / (float) AndroidUtilities.dp(20), 1.0f)); layerShadowDrawable.setBounds(translationX - layerShadowDrawable.getIntrinsicWidth(), child.getTop(), translationX, child.getBottom()); layerShadowDrawable.setAlpha((int) (0xff * alpha)); layerShadowDrawable.draw(canvas); } return result; } public float getInnerTranslationX() { return innerTranslationX; } private void prepareForMoving(MotionEvent ev) { maybeStartTracking = false; startedTracking = true; startedTrackingX = (int) ev.getX(); } public boolean handleTouchEvent(MotionEvent event) { if (!isPhotoVisible && !closeAnimationInProgress && fullscreenVideoContainer.getVisibility() != VISIBLE) { if (event != null && event.getAction() == MotionEvent.ACTION_DOWN && !startedTracking && !maybeStartTracking) { startedTrackingPointerId = event.getPointerId(0); maybeStartTracking = true; startedTrackingX = (int) event.getX(); startedTrackingY = (int) event.getY(); if (tracker != null) { tracker.clear(); } } else if (event != null && event.getAction() == MotionEvent.ACTION_MOVE && event.getPointerId(0) == startedTrackingPointerId) { if (tracker == null) { tracker = VelocityTracker.obtain(); } int dx = Math.max(0, (int) (event.getX() - startedTrackingX)); int dy = Math.abs((int) event.getY() - startedTrackingY); tracker.addMovement(event); if (maybeStartTracking && !startedTracking && dx >= AndroidUtilities.getPixelsInCM(0.4f, true) && Math.abs(dx) / 3 > dy) { prepareForMoving(event); } else if (startedTracking) { containerView.setTranslationX(dx); setInnerTranslationX(dx); } } else if (event != null && event.getPointerId(0) == startedTrackingPointerId && (event.getAction() == MotionEvent.ACTION_CANCEL || event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_POINTER_UP)) { if (tracker == null) { tracker = VelocityTracker.obtain(); } tracker.computeCurrentVelocity(1000); if (!startedTracking) { float velX = tracker.getXVelocity(); float velY = tracker.getYVelocity(); if (velX >= 3500 && velX > Math.abs(velY)) { prepareForMoving(event); } } if (startedTracking) { float x = containerView.getX(); AnimatorSet animatorSet = new AnimatorSet(); float velX = tracker.getXVelocity(); float velY = tracker.getYVelocity(); final boolean backAnimation = x < containerView.getMeasuredWidth() / 3.0f && (velX < 3500 || velX < velY); float distToMove; if (!backAnimation) { distToMove = containerView.getMeasuredWidth() - x; animatorSet.playTogether( ObjectAnimator.ofFloat(containerView, "translationX", containerView.getMeasuredWidth()), ObjectAnimator.ofFloat(this, "innerTranslationX", (float) containerView.getMeasuredWidth())); } else { distToMove = x; animatorSet.playTogether(ObjectAnimator.ofFloat(containerView, "translationX", 0), ObjectAnimator.ofFloat(this, "innerTranslationX", 0.0f)); } animatorSet.setDuration( Math.max((int) (200.0f / containerView.getMeasuredWidth() * distToMove), 50)); animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animator) { if (!backAnimation) { saveCurrentPagePosition(); onClosed(); } startedTracking = false; closeAnimationInProgress = false; } }); animatorSet.start(); closeAnimationInProgress = true; } else { maybeStartTracking = false; startedTracking = false; } if (tracker != null) { tracker.recycle(); tracker = null; } } else if (event == null) { maybeStartTracking = false; startedTracking = false; if (tracker != null) { tracker.recycle(); tracker = null; } } return startedTracking; } return false; } @Override protected void onDraw(Canvas canvas) { canvas.drawRect(innerTranslationX, 0, getMeasuredWidth(), getMeasuredHeight(), backgroundPaint); } @Override public void setAlpha(float value) { backgroundPaint.setAlpha((int) (255 * value)); alpha = value; if (parentActivity instanceof LaunchActivity) { ((LaunchActivity) parentActivity).drawerLayoutContainer .setAllowDrawContent(!isVisible || alpha != 1.0f || innerTranslationX != 0); } invalidate(); } @Override public float getAlpha() { return alpha; } } class CheckForLongPress implements Runnable { public int currentPressCount; public void run() { if (checkingForLongPress && windowView != null) { checkingForLongPress = false; windowView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); if (pressedLink != null) { final String urlFinal = pressedLink.getUrl(); BottomSheet.Builder builder = new BottomSheet.Builder(parentActivity); builder.setTitle(urlFinal); builder.setItems( new CharSequence[] { LocaleController.getString("Open", R.string.Open), LocaleController.getString("Copy", R.string.Copy) }, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, final int which) { if (parentActivity == null) { return; } if (which == 0) { Browser.openUrl(parentActivity, urlFinal); } else if (which == 1) { String url = urlFinal; if (url.startsWith("mailto:")) { url = url.substring(7); } else if (url.startsWith("tel:")) { url = url.substring(4); } AndroidUtilities.addToClipboard(url); } } }); showDialog(builder.create()); hideActionBar(); pressedLink = null; pressedLinkOwnerLayout = null; pressedLinkOwnerView.invalidate(); } } } } private void setRichTextParents(TLRPC.RichText parentRichText, TLRPC.RichText richText) { if (richText == null) { return; } richText.parentRichText = parentRichText; if (richText instanceof TLRPC.TL_textFixed) { setRichTextParents(richText, ((TLRPC.TL_textFixed) richText).text); } else if (richText instanceof TLRPC.TL_textItalic) { setRichTextParents(richText, ((TLRPC.TL_textItalic) richText).text); } else if (richText instanceof TLRPC.TL_textBold) { setRichTextParents(richText, ((TLRPC.TL_textBold) richText).text); } else if (richText instanceof TLRPC.TL_textUnderline) { setRichTextParents(richText, ((TLRPC.TL_textUnderline) richText).text); } else if (richText instanceof TLRPC.TL_textStrike) { setRichTextParents(parentRichText, ((TLRPC.TL_textStrike) richText).text); } else if (richText instanceof TLRPC.TL_textEmail) { setRichTextParents(richText, ((TLRPC.TL_textEmail) richText).text); } else if (richText instanceof TLRPC.TL_textUrl) { setRichTextParents(richText, ((TLRPC.TL_textUrl) richText).text); } else if (richText instanceof TLRPC.TL_textConcat) { int count = richText.texts.size(); for (int a = 0; a < count; a++) { setRichTextParents(richText, richText.texts.get(a)); } } } private void updateInterfaceForCurrentPage(boolean back) { if (currentPage == null || currentPage.cached_page == null) { return; } blocks.clear(); photoBlocks.clear(); int numBlocks = 0; for (int a = 0; a < currentPage.cached_page.blocks.size(); a++) { TLRPC.PageBlock block = currentPage.cached_page.blocks.get(a); if (block instanceof TLRPC.TL_pageBlockUnsupported) { continue; } else if (block instanceof TLRPC.TL_pageBlockAnchor) { anchors.put(block.name, blocks.size()); continue; } setRichTextParents(null, block.text); setRichTextParents(null, block.caption); if (block instanceof TLRPC.TL_pageBlockAuthorDate) { setRichTextParents(null, ((TLRPC.TL_pageBlockAuthorDate) block).author); } else if (block instanceof TLRPC.TL_pageBlockCollage) { TLRPC.TL_pageBlockCollage innerBlock = (TLRPC.TL_pageBlockCollage) block; for (int i = 0; i < innerBlock.items.size(); i++) { setRichTextParents(null, innerBlock.items.get(i).text); setRichTextParents(null, innerBlock.items.get(i).caption); } } else if (block instanceof TLRPC.TL_pageBlockList) { TLRPC.TL_pageBlockList innerBlock = (TLRPC.TL_pageBlockList) block; for (int i = 0; i < innerBlock.items.size(); i++) { setRichTextParents(null, innerBlock.items.get(i)); } } else if (block instanceof TLRPC.TL_pageBlockSlideshow) { TLRPC.TL_pageBlockSlideshow innerBlock = (TLRPC.TL_pageBlockSlideshow) block; for (int i = 0; i < innerBlock.items.size(); i++) { setRichTextParents(null, innerBlock.items.get(i).text); setRichTextParents(null, innerBlock.items.get(i).caption); } } if (a == 0) { block.first = true; } addAllMediaFromBlock(block); blocks.add(block); if (block instanceof TLRPC.TL_pageBlockEmbedPost) { if (!block.blocks.isEmpty()) { block.level = -1; for (int b = 0; b < block.blocks.size(); b++) { TLRPC.PageBlock innerBlock = block.blocks.get(b); if (innerBlock instanceof TLRPC.TL_pageBlockUnsupported) { continue; } else if (innerBlock instanceof TLRPC.TL_pageBlockAnchor) { anchors.put(innerBlock.name, blocks.size()); continue; } innerBlock.level = 1; if (b == block.blocks.size() - 1) { innerBlock.bottom = true; } blocks.add(innerBlock); addAllMediaFromBlock(innerBlock); } } if (!(block.caption instanceof TLRPC.TL_textEmpty)) { TLRPC.TL_pageBlockParagraph caption = new TLRPC.TL_pageBlockParagraph(); caption.caption = block.caption; blocks.add(caption); } } } adapter.notifyDataSetChanged(); if (pagesStack.size() == 1 || back) { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("articles", Activity.MODE_PRIVATE); String key = "article" + currentPage.id; int position = preferences.getInt(key, -1); int offset; if (preferences.getBoolean(key + "r", true) == AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y) { offset = preferences.getInt(key + "o", 0) - listView.getPaddingTop(); } else { offset = AndroidUtilities.dp(10); } if (position != -1) { layoutManager.scrollToPositionWithOffset(position, offset); } } else { layoutManager.scrollToPositionWithOffset(0, 0); } } private void addPageToStack(TLRPC.WebPage webPage, String anchor) { saveCurrentPagePosition(); currentPage = webPage; pagesStack.add(webPage); updateInterfaceForCurrentPage(false); if (anchor != null) { Integer row = anchors.get(anchor); if (row != null) { layoutManager.scrollToPositionWithOffset(row, 0); } } } private boolean removeLastPageFromStack() { if (pagesStack.size() < 2) { return false; } pagesStack.remove(pagesStack.size() - 1); currentPage = pagesStack.get(pagesStack.size() - 1); updateInterfaceForCurrentPage(true); return true; } protected void startCheckLongPress() { if (checkingForLongPress) { return; } checkingForLongPress = true; if (pendingCheckForTap == null) { pendingCheckForTap = new CheckForTap(); } windowView.postDelayed(pendingCheckForTap, ViewConfiguration.getTapTimeout()); } protected void cancelCheckLongPress() { checkingForLongPress = false; if (pendingCheckForLongPress != null) { windowView.removeCallbacks(pendingCheckForLongPress); } if (pendingCheckForTap != null) { windowView.removeCallbacks(pendingCheckForTap); } } private static final int TEXT_FLAG_REGULAR = 0; private static final int TEXT_FLAG_MEDIUM = 1; private static final int TEXT_FLAG_ITALIC = 2; private static final int TEXT_FLAG_MONO = 4; private static final int TEXT_FLAG_URL = 8; private static final int TEXT_FLAG_UNDERLINE = 16; private static final int TEXT_FLAG_STRIKE = 32; private static TextPaint errorTextPaint; private static HashMap<Integer, TextPaint> captionTextPaints = new HashMap<>(); private static HashMap<Integer, TextPaint> titleTextPaints = new HashMap<>(); private static HashMap<Integer, TextPaint> headerTextPaints = new HashMap<>(); private static HashMap<Integer, TextPaint> subtitleTextPaints = new HashMap<>(); private static HashMap<Integer, TextPaint> subheaderTextPaints = new HashMap<>(); private static HashMap<Integer, TextPaint> authorTextPaints = new HashMap<>(); private static HashMap<Integer, TextPaint> footerTextPaints = new HashMap<>(); private static HashMap<Integer, TextPaint> paragraphTextPaints = new HashMap<>(); private static HashMap<Integer, TextPaint> listTextPaints = new HashMap<>(); private static HashMap<Integer, TextPaint> preformattedTextPaints = new HashMap<>(); private static HashMap<Integer, TextPaint> quoteTextPaints = new HashMap<>(); private static HashMap<Integer, TextPaint> subquoteTextPaints = new HashMap<>(); private static HashMap<Integer, TextPaint> embedTextPaints = new HashMap<>(); private static HashMap<Integer, TextPaint> slideshowTextPaints = new HashMap<>(); private static HashMap<Integer, TextPaint> embedPostTextPaints = new HashMap<>(); private static HashMap<Integer, TextPaint> embedPostCaptionTextPaints = new HashMap<>(); private static HashMap<Integer, TextPaint> videoTextPaints = new HashMap<>(); private static TextPaint embedPostAuthorPaint; private static TextPaint embedPostDatePaint; private static Paint preformattedBackgroundPaint; private static Paint quoteLinePaint; private static Paint dividerPaint; private static Paint urlPaint; private int getTextFlags(TLRPC.RichText richText) { if (richText instanceof TLRPC.TL_textFixed) { return getTextFlags(richText.parentRichText) | TEXT_FLAG_MONO; } else if (richText instanceof TLRPC.TL_textItalic) { return getTextFlags(richText.parentRichText) | TEXT_FLAG_ITALIC; } else if (richText instanceof TLRPC.TL_textBold) { return getTextFlags(richText.parentRichText) | TEXT_FLAG_MEDIUM; } else if (richText instanceof TLRPC.TL_textUnderline) { return getTextFlags(richText.parentRichText) | TEXT_FLAG_UNDERLINE; } else if (richText instanceof TLRPC.TL_textStrike) { return getTextFlags(richText.parentRichText) | TEXT_FLAG_STRIKE; } else if (richText instanceof TLRPC.TL_textEmail) { return getTextFlags(richText.parentRichText) | TEXT_FLAG_URL; } else if (richText instanceof TLRPC.TL_textUrl) { return getTextFlags(richText.parentRichText) | TEXT_FLAG_URL; } else if (richText != null) { return getTextFlags(richText.parentRichText); } return TEXT_FLAG_REGULAR; } private CharSequence getText(TLRPC.RichText parentRichText, TLRPC.RichText richText, TLRPC.PageBlock parentBlock) { if (richText instanceof TLRPC.TL_textFixed) { return getText(parentRichText, ((TLRPC.TL_textFixed) richText).text, parentBlock); } else if (richText instanceof TLRPC.TL_textItalic) { return getText(parentRichText, ((TLRPC.TL_textItalic) richText).text, parentBlock); } else if (richText instanceof TLRPC.TL_textBold) { return getText(parentRichText, ((TLRPC.TL_textBold) richText).text, parentBlock); } else if (richText instanceof TLRPC.TL_textUnderline) { return getText(parentRichText, ((TLRPC.TL_textUnderline) richText).text, parentBlock); } else if (richText instanceof TLRPC.TL_textStrike) { return getText(parentRichText, ((TLRPC.TL_textStrike) richText).text, parentBlock); } else if (richText instanceof TLRPC.TL_textEmail) { SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder( getText(parentRichText, ((TLRPC.TL_textEmail) richText).text, parentBlock)); MetricAffectingSpan innerSpans[] = spannableStringBuilder.getSpans(0, spannableStringBuilder.length(), MetricAffectingSpan.class); spannableStringBuilder.setSpan( new TextPaintUrlSpan(innerSpans == null || innerSpans.length == 0 ? getTextPaint(parentRichText, richText, parentBlock) : null, getUrl(richText)), 0, spannableStringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); return spannableStringBuilder; } else if (richText instanceof TLRPC.TL_textUrl) { SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder( getText(parentRichText, ((TLRPC.TL_textUrl) richText).text, parentBlock)); MetricAffectingSpan innerSpans[] = spannableStringBuilder.getSpans(0, spannableStringBuilder.length(), MetricAffectingSpan.class); spannableStringBuilder.setSpan( new TextPaintUrlSpan(innerSpans == null || innerSpans.length == 0 ? getTextPaint(parentRichText, richText, parentBlock) : null, getUrl(richText)), 0, spannableStringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); return spannableStringBuilder; } else if (richText instanceof TLRPC.TL_textPlain) { return ((TLRPC.TL_textPlain) richText).text; } else if (richText instanceof TLRPC.TL_textEmpty) { return ""; } else if (richText instanceof TLRPC.TL_textConcat) { SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(); int count = richText.texts.size(); for (int a = 0; a < count; a++) { TLRPC.RichText innerRichText = richText.texts.get(a); CharSequence innerText = getText(parentRichText, innerRichText, parentBlock); int flags = getTextFlags(innerRichText); int startLength = spannableStringBuilder.length(); spannableStringBuilder.append(innerText); if (flags != 0 && !(innerText instanceof SpannableStringBuilder)) { if ((flags & TEXT_FLAG_URL) != 0) { String url = getUrl(innerRichText); if (url == null) { url = getUrl(parentRichText); } spannableStringBuilder.setSpan( new TextPaintUrlSpan(getTextPaint(parentRichText, innerRichText, parentBlock), url), startLength, spannableStringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { spannableStringBuilder.setSpan( new TextPaintSpan(getTextPaint(parentRichText, innerRichText, parentBlock)), startLength, spannableStringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } } return spannableStringBuilder; } return "not supported " + richText; } private String getUrl(TLRPC.RichText richText) { if (richText instanceof TLRPC.TL_textFixed) { return getUrl(((TLRPC.TL_textFixed) richText).text); } else if (richText instanceof TLRPC.TL_textItalic) { return getUrl(((TLRPC.TL_textItalic) richText).text); } else if (richText instanceof TLRPC.TL_textBold) { return getUrl(((TLRPC.TL_textBold) richText).text); } else if (richText instanceof TLRPC.TL_textUnderline) { return getUrl(((TLRPC.TL_textUnderline) richText).text); } else if (richText instanceof TLRPC.TL_textStrike) { return getUrl(((TLRPC.TL_textStrike) richText).text); } else if (richText instanceof TLRPC.TL_textEmail) { return ((TLRPC.TL_textEmail) richText).email; } else if (richText instanceof TLRPC.TL_textUrl) { return ((TLRPC.TL_textUrl) richText).url; } return null; } private TextPaint getTextPaint(TLRPC.RichText parentRichText, TLRPC.RichText richText, TLRPC.PageBlock parentBlock) { int flags = getTextFlags(richText); HashMap<Integer, TextPaint> currentMap = null; int textSize = AndroidUtilities.dp(14); int textColor = 0xffff0000; if (parentBlock instanceof TLRPC.TL_pageBlockPhoto) { currentMap = captionTextPaints; textSize = AndroidUtilities.dp(14); textColor = 0xff838c96; } else if (parentBlock instanceof TLRPC.TL_pageBlockTitle) { currentMap = titleTextPaints; textSize = AndroidUtilities.dp(24); textColor = 0xff000000; } else if (parentBlock instanceof TLRPC.TL_pageBlockAuthorDate) { currentMap = authorTextPaints; textSize = AndroidUtilities.dp(14); textColor = 0xff838c96; } else if (parentBlock instanceof TLRPC.TL_pageBlockFooter) { currentMap = footerTextPaints; textSize = AndroidUtilities.dp(14); textColor = 0xff838c96; } else if (parentBlock instanceof TLRPC.TL_pageBlockSubtitle) { currentMap = subtitleTextPaints; textSize = AndroidUtilities.dp(21); textColor = 0xff000000; } else if (parentBlock instanceof TLRPC.TL_pageBlockHeader) { currentMap = headerTextPaints; textSize = AndroidUtilities.dp(21); textColor = 0xff000000; } else if (parentBlock instanceof TLRPC.TL_pageBlockSubheader) { currentMap = subheaderTextPaints; textSize = AndroidUtilities.dp(18); textColor = 0xff000000; } else if (parentBlock instanceof TLRPC.TL_pageBlockBlockquote || parentBlock instanceof TLRPC.TL_pageBlockPullquote) { if (parentBlock.text == parentRichText) { currentMap = quoteTextPaints; textSize = AndroidUtilities.dp(15); textColor = 0xff000000; } else if (parentBlock.caption == parentRichText) { currentMap = subquoteTextPaints; textSize = AndroidUtilities.dp(14); textColor = 0xff838c96; } } else if (parentBlock instanceof TLRPC.TL_pageBlockPreformatted) { currentMap = preformattedTextPaints; textSize = AndroidUtilities.dp(14); textColor = 0xff000000; } else if (parentBlock instanceof TLRPC.TL_pageBlockParagraph) { if (parentBlock.caption == parentRichText) { currentMap = embedPostCaptionTextPaints; textSize = AndroidUtilities.dp(14); textColor = 0xff838c96; } else { currentMap = paragraphTextPaints; textSize = AndroidUtilities.dp(16); textColor = 0xff000000; } } else if (parentBlock instanceof TLRPC.TL_pageBlockList) { currentMap = listTextPaints; textSize = AndroidUtilities.dp(15); textColor = 0xff000000; } else if (parentBlock instanceof TLRPC.TL_pageBlockEmbed) { currentMap = embedTextPaints; textSize = AndroidUtilities.dp(14); textColor = 0xff838c96; } else if (parentBlock instanceof TLRPC.TL_pageBlockSlideshow) { currentMap = slideshowTextPaints; textSize = AndroidUtilities.dp(14); textColor = 0xff838c96; } else if (parentBlock instanceof TLRPC.TL_pageBlockEmbedPost) { if (richText != null) { currentMap = embedPostTextPaints; textSize = AndroidUtilities.dp(14); textColor = 0xff000000; } } else if (parentBlock instanceof TLRPC.TL_pageBlockVideo) { currentMap = videoTextPaints; textSize = AndroidUtilities.dp(14); textColor = 0xff000000; } if (currentMap == null) { if (errorTextPaint == null) { errorTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); errorTextPaint.setColor(0xffff0000); } errorTextPaint.setTextSize(AndroidUtilities.dp(14)); return errorTextPaint; } TextPaint paint = currentMap.get(flags); if (paint == null) { paint = new TextPaint(Paint.ANTI_ALIAS_FLAG); if ((flags & TEXT_FLAG_MONO) != 0) { paint.setTypeface(AndroidUtilities.getTypeface("fonts/rmono.ttf")); } else { if (parentBlock instanceof TLRPC.TL_pageBlockTitle || parentBlock instanceof TLRPC.TL_pageBlockHeader || parentBlock instanceof TLRPC.TL_pageBlockSubtitle || parentBlock instanceof TLRPC.TL_pageBlockSubheader) { if ((flags & TEXT_FLAG_MEDIUM) != 0 && (flags & TEXT_FLAG_ITALIC) != 0) { paint.setTypeface(Typeface.create("serif", Typeface.BOLD_ITALIC)); } else if ((flags & TEXT_FLAG_MEDIUM) != 0) { paint.setTypeface(Typeface.create("serif", Typeface.BOLD)); } else if ((flags & TEXT_FLAG_ITALIC) != 0) { paint.setTypeface(Typeface.create("serif", Typeface.ITALIC)); } else { paint.setTypeface(Typeface.create("serif", Typeface.NORMAL)); } } else { if ((flags & TEXT_FLAG_MEDIUM) != 0 && (flags & TEXT_FLAG_ITALIC) != 0) { paint.setTypeface(AndroidUtilities.getTypeface("fonts/rmediumitalic.ttf")); } else if ((flags & TEXT_FLAG_MEDIUM) != 0) { paint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); } else if ((flags & TEXT_FLAG_ITALIC) != 0) { paint.setTypeface(AndroidUtilities.getTypeface("fonts/ritalic.ttf")); } } } if ((flags & TEXT_FLAG_STRIKE) != 0) { paint.setFlags(paint.getFlags() | TextPaint.STRIKE_THRU_TEXT_FLAG); } if ((flags & TEXT_FLAG_UNDERLINE) != 0) { paint.setFlags(paint.getFlags() | TextPaint.UNDERLINE_TEXT_FLAG); } if ((flags & TEXT_FLAG_URL) != 0) { textColor = 0xff4d83b3; } paint.setColor(textColor); currentMap.put(flags, paint); } paint.setTextSize(textSize); return paint; } private StaticLayout createLayoutForText(CharSequence plainText, TLRPC.RichText richText, int width, TLRPC.PageBlock parentBlock) { if (plainText == null && (richText == null || richText instanceof TLRPC.TL_textEmpty)) { return null; } if (quoteLinePaint == null) { quoteLinePaint = new Paint(); quoteLinePaint.setColor(0xff000000); preformattedBackgroundPaint = new Paint(); preformattedBackgroundPaint.setColor(0xfff5f8fc); urlPaint = new Paint(); urlPaint.setColor(0x3362a9e3); } CharSequence text; if (plainText != null) { text = plainText; } else { text = getText(richText, richText, parentBlock); } if (TextUtils.isEmpty(text)) { return null; } TextPaint paint; if (parentBlock instanceof TLRPC.TL_pageBlockEmbedPost && richText == null) { if (parentBlock.author == plainText) { if (embedPostAuthorPaint == null) { embedPostAuthorPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); embedPostAuthorPaint.setColor(0xff000000); } embedPostAuthorPaint.setTextSize(AndroidUtilities.dp(15)); paint = embedPostAuthorPaint; } else { if (embedPostDatePaint == null) { embedPostDatePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); embedPostDatePaint.setColor(0xff8f97a0); } embedPostDatePaint.setTextSize(AndroidUtilities.dp(14)); paint = embedPostDatePaint; } } else { paint = getTextPaint(richText, richText, parentBlock); } if (parentBlock instanceof TLRPC.TL_pageBlockPullquote) { return new StaticLayout(text, paint, width, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); } else { return new StaticLayout(text, paint, width, Layout.Alignment.ALIGN_NORMAL, 1.0f, AndroidUtilities.dp(4), false); } } private void drawLayoutLink(Canvas canvas, StaticLayout layout) { if (canvas == null || pressedLink == null || pressedLinkOwnerLayout != layout) { return; } if (pressedLink != null) { canvas.drawPath(urlPath, urlPaint); } } private boolean checkLayoutForLinks(MotionEvent event, View parentView, StaticLayout layout, int layoutX, int layoutY) { if (parentView == null || layout == null) { return false; } CharSequence text = layout.getText(); if (!(text instanceof Spannable)) { return false; } int x = (int) event.getX(); int y = (int) event.getY(); boolean removeLink = false; if (event.getAction() == MotionEvent.ACTION_DOWN) { if (x >= layoutX && x <= layoutX + layout.getWidth() && y >= layoutY && y <= layoutY + layout.getHeight()) { try { int checkX = x - layoutX; int checkY = y - layoutY; final int line = layout.getLineForVertical(checkY); final int off = layout.getOffsetForHorizontal(line, checkX); final float left = layout.getLineLeft(line); if (left <= checkX && left + layout.getLineWidth(line) >= checkX) { Spannable buffer = (Spannable) layout.getText(); TextPaintUrlSpan[] link = buffer.getSpans(off, off, TextPaintUrlSpan.class); if (link != null && link.length > 0) { pressedLink = link[0]; int pressedStart = buffer.getSpanStart(pressedLink); int pressedEnd = buffer.getSpanEnd(pressedLink); for (int a = 1; a < link.length; a++) { TextPaintUrlSpan span = link[a]; int start = buffer.getSpanStart(span); int end = buffer.getSpanEnd(span); if (pressedStart > start || end > pressedEnd) { pressedLink = span; pressedStart = start; pressedEnd = end; } } pressedLinkOwnerLayout = layout; pressedLinkOwnerView = parentView; try { urlPath.setCurrentLayout(layout, pressedStart, 0); layout.getSelectionPath(pressedStart, pressedEnd, urlPath); parentView.invalidate(); } catch (Exception e) { FileLog.e(e); } } } } catch (Exception e) { FileLog.e(e); } } } else if (event.getAction() == MotionEvent.ACTION_UP) { if (pressedLink != null) { removeLink = true; String url = pressedLink.getUrl(); if (url != null) { int index; boolean isAnchor = false; final String anchor; if ((index = url.lastIndexOf('#')) != -1) { anchor = url.substring(index + 1); if (url.toLowerCase().contains(currentPage.url.toLowerCase())) { Integer row = anchors.get(anchor); if (row != null) { layoutManager.scrollToPositionWithOffset(row, 0); isAnchor = true; } } } else { anchor = null; } if (!isAnchor) { if (openUrlReqId == 0) { showProgressView(true); final TLRPC.TL_messages_getWebPage req = new TLRPC.TL_messages_getWebPage(); req.url = pressedLink.getUrl(); req.hash = 0; openUrlReqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override public void run(final TLObject response, TLRPC.TL_error error) { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { if (openUrlReqId == 0) { return; } openUrlReqId = 0; showProgressView(false); if (isVisible) { if (response instanceof TLRPC.TL_webPage && ((TLRPC.TL_webPage) response).cached_page instanceof TLRPC.TL_pageFull) { addPageToStack((TLRPC.TL_webPage) response, anchor); } else { Browser.openUrl(parentActivity, req.url); } } } }); } }); } } } } } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { removeLink = true; } if (removeLink && pressedLink != null) { pressedLink = null; pressedLinkOwnerLayout = null; pressedLinkOwnerView = null; parentView.invalidate(); } if (pressedLink != null && event.getAction() == MotionEvent.ACTION_DOWN) { startCheckLongPress(); } if (event.getAction() != MotionEvent.ACTION_DOWN && event.getAction() != MotionEvent.ACTION_MOVE) { cancelCheckLongPress(); } return pressedLink != null; } private TLRPC.Photo getPhotoWithId(long id) { if (currentPage == null || currentPage.cached_page == null) { return null; } if (currentPage.photo != null && currentPage.photo.id == id) { return currentPage.photo; } for (int a = 0; a < currentPage.cached_page.photos.size(); a++) { TLRPC.Photo photo = currentPage.cached_page.photos.get(a); if (photo.id == id) { return photo; } } return null; } private TLRPC.Document getDocumentWithId(long id) { if (currentPage == null || currentPage.cached_page == null) { return null; } if (currentPage.document != null && currentPage.document.id == id) { return currentPage.document; } for (int a = 0; a < currentPage.cached_page.videos.size(); a++) { TLRPC.Document document = currentPage.cached_page.videos.get(a); if (document.id == id) { return document; } } return null; } @SuppressWarnings("unchecked") @Override public void didReceivedNotification(int id, Object... args) { if (id == NotificationCenter.FileDidFailedLoad) { String location = (String) args[0]; for (int a = 0; a < 3; a++) { if (currentFileNames[a] != null && currentFileNames[a].equals(location)) { radialProgressViews[a].setProgress(1.0f, true); checkProgress(a, true); break; } } } else if (id == NotificationCenter.FileDidLoaded) { String location = (String) args[0]; for (int a = 0; a < 3; a++) { if (currentFileNames[a] != null && currentFileNames[a].equals(location)) { radialProgressViews[a].setProgress(1.0f, true); checkProgress(a, true); if (a == 0 && isMediaVideo(currentIndex)) { onActionClick(false); } break; } } } else if (id == NotificationCenter.FileLoadProgressChanged) { String location = (String) args[0]; for (int a = 0; a < 3; a++) { if (currentFileNames[a] != null && currentFileNames[a].equals(location)) { Float progress = (Float) args[1]; radialProgressViews[a].setProgress(progress, true); } } } else if (id == NotificationCenter.emojiDidLoaded) { if (captionTextView != null) { captionTextView.invalidate(); } } } public void setParentActivity(Activity activity) { if (parentActivity == activity) { return; } parentActivity = activity; backgroundPaint = new Paint(); backgroundPaint.setColor(0xffffffff); layerShadowDrawable = activity.getResources().getDrawable(R.drawable.layer_shadow); slideDotDrawable = activity.getResources().getDrawable(R.drawable.slide_dot_small); slideDotBigDrawable = activity.getResources().getDrawable(R.drawable.slide_dot_big); scrimPaint = new Paint(); windowView = new WindowView(activity); windowView.setWillNotDraw(false); windowView.setClipChildren(true); windowView.setFocusable(false); containerView = new FrameLayout(activity); windowView.addView(containerView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); containerView.setFitsSystemWindows(true); if (Build.VERSION.SDK_INT >= 21) { containerView.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() { @SuppressLint("NewApi") @Override public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { WindowInsets oldInsets = (WindowInsets) lastInsets; lastInsets = insets; if (oldInsets == null || !oldInsets.toString().equals(insets.toString())) { windowView.requestLayout(); } return insets.consumeSystemWindowInsets(); } }); } containerView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_FULLSCREEN); photoContainerBackground = new View(activity); photoContainerBackground.setVisibility(View.INVISIBLE); photoContainerBackground.setBackgroundDrawable(photoBackgroundDrawable); windowView.addView(photoContainerBackground, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); animatingImageView = new ClippingImageView(activity); animatingImageView.setAnimationValues(animationValues); animatingImageView.setVisibility(View.GONE); windowView.addView(animatingImageView, LayoutHelper.createFrame(40, 40)); photoContainerView = new FrameLayoutDrawer(activity) { @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); int y = bottom - top - captionTextView.getMeasuredHeight(); if (bottomLayout.getVisibility() == VISIBLE) { y -= bottomLayout.getMeasuredHeight(); } captionTextView.layout(0, y, captionTextView.getMeasuredWidth(), y + captionTextView.getMeasuredHeight()); } }; photoContainerView.setVisibility(View.INVISIBLE); photoContainerView.setWillNotDraw(false); windowView.addView(photoContainerView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); fullscreenVideoContainer = new FrameLayout(activity); fullscreenVideoContainer.setBackgroundColor(0xff000000); fullscreenVideoContainer.setVisibility(View.INVISIBLE); windowView.addView(fullscreenVideoContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); fullscreenAspectRatioView = new AspectRatioFrameLayout(activity); fullscreenAspectRatioView.setVisibility(View.GONE); fullscreenVideoContainer.addView(fullscreenAspectRatioView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER)); fullscreenTextureView = new TextureView(activity); if (Build.VERSION.SDK_INT >= 21) { barBackground = new View(activity); barBackground.setBackgroundColor(0xff000000); windowView.addView(barBackground); } listView = new RecyclerListView(activity); listView.setLayoutManager( layoutManager = new LinearLayoutManager(parentActivity, LinearLayoutManager.VERTICAL, false)); listView.setAdapter(adapter = new WebpageAdapter(parentActivity)); listView.setClipToPadding(false); listView.setPadding(0, AndroidUtilities.dp(56), 0, 0); listView.setTopGlowOffset(AndroidUtilities.dp(56)); listView.setGlowColor(0xfff5f6f7); containerView.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); listView.setOnItemLongClickListener(new RecyclerListView.OnItemLongClickListener() { @Override public boolean onItemClick(View view, int position) { return false; } }); listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override public void onItemClick(View view, int position) { if (position == blocks.size() && currentPage != null) { if (previewsReqId != 0) { return; } TLRPC.User user = MessagesController.getInstance().getUser("previews"); if (user != null) { openPreviewsChat(user, currentPage.id); } else { final long pageId = currentPage.id; showProgressView(true); TLRPC.TL_contacts_resolveUsername req = new TLRPC.TL_contacts_resolveUsername(); req.username = "previews"; previewsReqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override public void run(final TLObject response, final TLRPC.TL_error error) { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { if (previewsReqId == 0) { return; } previewsReqId = 0; showProgressView(false); if (response != null) { TLRPC.TL_contacts_resolvedPeer res = (TLRPC.TL_contacts_resolvedPeer) response; MessagesController.getInstance().putUsers(res.users, false); MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, false, true); if (!res.users.isEmpty()) { openPreviewsChat(res.users.get(0), pageId); } } } }); } }); } } } }); listView.setOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (listView.getChildCount() == 0) { return; } checkScroll(dy); } }); headerView = new FrameLayout(activity); headerView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return true; } }); headerView.setBackgroundColor(0xff000000); containerView.addView(headerView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 56)); backButton = new ImageView(activity); backButton.setScaleType(ImageView.ScaleType.CENTER); backDrawable = new BackDrawable(false); backDrawable.setAnimationTime(200.0f); backDrawable.setColor(0xffb3b3b3); backDrawable.setRotated(false); backButton.setImageDrawable(backDrawable); backButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); headerView.addView(backButton, LayoutHelper.createFrame(54, 56)); backButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { /*if (collapsed) { uncollapse(); } else { collapse(); }*/ close(true, true); } }); shareContainer = new FrameLayout(activity); headerView.addView(shareContainer, LayoutHelper.createFrame(48, 56, Gravity.TOP | Gravity.RIGHT)); shareContainer.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (currentPage == null || parentActivity == null) { return; } showDialog(new ShareAlert(parentActivity, null, currentPage.url, false, currentPage.url, true)); hideActionBar(); } }); shareButton = new ImageView(activity); shareButton.setScaleType(ImageView.ScaleType.CENTER); shareButton.setImageResource(R.drawable.ic_share_article); shareButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); shareContainer.addView(shareButton, LayoutHelper.createFrame(48, 56)); progressView = new ContextProgressView(activity, 2); progressView.setVisibility(View.GONE); shareContainer.addView(progressView, LayoutHelper.createFrame(48, 56)); windowLayoutParams = new WindowManager.LayoutParams(); windowLayoutParams.height = WindowManager.LayoutParams.MATCH_PARENT; windowLayoutParams.format = PixelFormat.TRANSLUCENT; windowLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT; windowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT; windowLayoutParams.type = WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; if (Build.VERSION.SDK_INT >= 21) { windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; } else { windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; } if (progressDrawables == null) { progressDrawables = new Drawable[4]; progressDrawables[0] = parentActivity.getResources().getDrawable(R.drawable.circle_big); progressDrawables[1] = parentActivity.getResources().getDrawable(R.drawable.cancel_big); progressDrawables[2] = parentActivity.getResources().getDrawable(R.drawable.load_big); progressDrawables[3] = parentActivity.getResources().getDrawable(R.drawable.play_big); } scroller = new Scroller(activity); blackPaint.setColor(0xff000000); actionBar = new ActionBar(activity); actionBar.setBackgroundColor(Theme.ACTION_BAR_PHOTO_VIEWER_COLOR); actionBar.setOccupyStatusBar(false); actionBar.setTitleColor(0xffffffff); actionBar.setItemsBackgroundColor(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR, false); actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setTitle(LocaleController.formatString("Of", R.string.Of, 1, 1)); photoContainerView.addView(actionBar, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { @Override public void onItemClick(int id) { if (id == -1) { closePhoto(true); } else if (id == gallery_menu_save) { if (Build.VERSION.SDK_INT >= 23 && parentActivity.checkSelfPermission( Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { parentActivity .requestPermissions(new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, 4); return; } File f = getMediaFile(currentIndex); if (f != null && f.exists()) { MediaController.saveFile(f.toString(), parentActivity, isMediaVideo(currentIndex) ? 1 : 0, null, null); } else { AlertDialog.Builder builder = new AlertDialog.Builder(parentActivity); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); builder.setMessage(LocaleController.getString("PleaseDownload", R.string.PleaseDownload)); showDialog(builder.create()); } } else if (id == gallery_menu_share) { onSharePressed(); } else if (id == gallery_menu_openin) { try { AndroidUtilities.openForView(getMedia(currentIndex), parentActivity); closePhoto(false); } catch (Exception e) { FileLog.e(e); } } } @Override public boolean canOpenMenu() { File f = getMediaFile(currentIndex); return f != null && f.exists(); } }); ActionBarMenu menu = actionBar.createMenu(); menu.addItem(gallery_menu_share, R.drawable.share); menuItem = menu.addItem(0, R.drawable.ic_ab_other); menuItem.setLayoutInScreen(true); menuItem.addSubItem(gallery_menu_openin, LocaleController.getString("OpenInExternalApp", R.string.OpenInExternalApp)); //menuItem.addSubItem(gallery_menu_share, LocaleController.getString("ShareFile", R.string.ShareFile), 0); menuItem.addSubItem(gallery_menu_save, LocaleController.getString("SaveToGallery", R.string.SaveToGallery)); bottomLayout = new FrameLayout(parentActivity); bottomLayout.setBackgroundColor(0x7f000000); photoContainerView.addView(bottomLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.BOTTOM | Gravity.LEFT)); captionTextViewOld = new TextView(activity); captionTextViewOld.setMaxLines(10); captionTextViewOld.setBackgroundColor(0x7f000000); captionTextViewOld.setPadding(AndroidUtilities.dp(20), AndroidUtilities.dp(8), AndroidUtilities.dp(20), AndroidUtilities.dp(8)); captionTextViewOld.setLinkTextColor(0xffffffff); captionTextViewOld.setTextColor(0xffffffff); captionTextViewOld.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); captionTextViewOld.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); captionTextViewOld.setVisibility(View.INVISIBLE); photoContainerView.addView(captionTextViewOld, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | Gravity.LEFT)); captionTextView = captionTextViewNew = new TextView(activity); captionTextViewNew.setMaxLines(10); captionTextViewNew.setBackgroundColor(0x7f000000); captionTextViewNew.setPadding(AndroidUtilities.dp(20), AndroidUtilities.dp(8), AndroidUtilities.dp(20), AndroidUtilities.dp(8)); captionTextViewNew.setLinkTextColor(0xffffffff); captionTextViewNew.setTextColor(0xffffffff); captionTextViewNew.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); captionTextViewNew.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); captionTextViewNew.setVisibility(View.INVISIBLE); photoContainerView.addView(captionTextViewNew, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | Gravity.LEFT)); radialProgressViews[0] = new RadialProgressView(activity, photoContainerView); radialProgressViews[0].setBackgroundState(0, false); radialProgressViews[1] = new RadialProgressView(activity, photoContainerView); radialProgressViews[1].setBackgroundState(0, false); radialProgressViews[2] = new RadialProgressView(activity, photoContainerView); radialProgressViews[2].setBackgroundState(0, false); videoPlayerSeekbar = new SeekBar(activity); videoPlayerSeekbar.setColors(0x66ffffff, 0xffffffff, 0xffffffff); videoPlayerSeekbar.setDelegate(new SeekBar.SeekBarDelegate() { @Override public void onSeekBarDrag(float progress) { if (videoPlayer != null) { videoPlayer.seekTo((int) (progress * videoPlayer.getDuration())); } } }); videoPlayerControlFrameLayout = new FrameLayout(activity) { @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); if (videoPlayerSeekbar.onTouch(event.getAction(), event.getX() - AndroidUtilities.dp(48), event.getY())) { getParent().requestDisallowInterceptTouchEvent(true); invalidate(); return true; } return super.onTouchEvent(event); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); long duration; if (videoPlayer != null) { duration = videoPlayer.getDuration(); if (duration == C.TIME_UNSET) { duration = 0; } } else { duration = 0; } duration /= 1000; int size = (int) Math .ceil(videoPlayerTime.getPaint().measureText(String.format("%02d:%02d / %02d:%02d", duration / 60, duration % 60, duration / 60, duration % 60))); videoPlayerSeekbar.setSize(getMeasuredWidth() - AndroidUtilities.dp(48 + 16) - size, getMeasuredHeight()); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); float progress = 0; if (videoPlayer != null) { progress = videoPlayer.getCurrentPosition() / (float) videoPlayer.getDuration(); } videoPlayerSeekbar.setProgress(progress); } @Override protected void onDraw(Canvas canvas) { canvas.save(); canvas.translate(AndroidUtilities.dp(48), 0); videoPlayerSeekbar.draw(canvas); canvas.restore(); } }; videoPlayerControlFrameLayout.setWillNotDraw(false); bottomLayout.addView(videoPlayerControlFrameLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); videoPlayButton = new ImageView(activity); videoPlayButton.setScaleType(ImageView.ScaleType.CENTER); videoPlayerControlFrameLayout.addView(videoPlayButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.TOP)); videoPlayButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (videoPlayer != null) { if (isPlaying) { videoPlayer.pause(); } else { videoPlayer.play(); } } } }); videoPlayerTime = new TextView(activity); videoPlayerTime.setTextColor(0xffffffff); videoPlayerTime.setGravity(Gravity.CENTER_VERTICAL); videoPlayerTime.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); videoPlayerControlFrameLayout.addView(videoPlayerTime, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.RIGHT | Gravity.TOP, 0, 0, 8, 0)); gestureDetector = new GestureDetector(activity, this); gestureDetector.setOnDoubleTapListener(this); ImageReceiver.ImageReceiverDelegate imageReceiverDelegate = new ImageReceiver.ImageReceiverDelegate() { @Override public void didSetImage(ImageReceiver imageReceiver, boolean set, boolean thumb) { if (imageReceiver == centerImage && set && scaleToFill()) { if (!wasLayout) { dontResetZoomOnFirstLayout = true; } else { setScaleToFill(); } } } }; centerImage.setParentView(photoContainerView); centerImage.setCrossfadeAlpha((byte) 2); centerImage.setInvalidateAll(true); centerImage.setDelegate(imageReceiverDelegate); leftImage.setParentView(photoContainerView); leftImage.setCrossfadeAlpha((byte) 2); leftImage.setInvalidateAll(true); leftImage.setDelegate(imageReceiverDelegate); rightImage.setParentView(photoContainerView); rightImage.setCrossfadeAlpha((byte) 2); rightImage.setInvalidateAll(true); rightImage.setDelegate(imageReceiverDelegate); } private void checkScroll(int dy) { int maxHeight = AndroidUtilities.dp(56); int minHeight = Math.max(AndroidUtilities.statusBarHeight, AndroidUtilities.dp(24)); float heightDiff = maxHeight - minHeight; int newHeight = currentHeaderHeight - dy; if (newHeight < minHeight) { newHeight = minHeight; } else if (newHeight > maxHeight) { newHeight = maxHeight; } currentHeaderHeight = newHeight; float scale = 0.8f + (currentHeaderHeight - minHeight) / heightDiff * 0.2f; int scaledHeight = (int) (maxHeight * scale); backButton.setScaleX(scale); backButton.setScaleY(scale); backButton.setTranslationY((maxHeight - currentHeaderHeight) / 2); shareContainer.setScaleX(scale); shareContainer.setScaleY(scale); shareContainer.setTranslationY((maxHeight - currentHeaderHeight) / 2); headerView.setTranslationY(currentHeaderHeight - maxHeight); listView.setTopGlowOffset(currentHeaderHeight); } private void openPreviewsChat(TLRPC.User user, long wid) { if (user == null || parentActivity == null) { return; } Bundle args = new Bundle(); args.putInt("user_id", user.id); args.putString("botUser", "webpage" + wid); ((LaunchActivity) parentActivity).presentFragment(new ChatActivity(args), false, true); close(false, true); } private void addAllMediaFromBlock(TLRPC.PageBlock block) { if (block instanceof TLRPC.TL_pageBlockPhoto || block instanceof TLRPC.TL_pageBlockVideo && isVideoBlock(block)) { photoBlocks.add(block); } else if (block instanceof TLRPC.TL_pageBlockSlideshow) { TLRPC.TL_pageBlockSlideshow slideshow = (TLRPC.TL_pageBlockSlideshow) block; int count = slideshow.items.size(); for (int a = 0; a < count; a++) { TLRPC.PageBlock innerBlock = slideshow.items.get(a); if (innerBlock instanceof TLRPC.TL_pageBlockPhoto || innerBlock instanceof TLRPC.TL_pageBlockVideo && isVideoBlock(block)) { photoBlocks.add(innerBlock); } } } else if (block instanceof TLRPC.TL_pageBlockCollage) { TLRPC.TL_pageBlockCollage collage = (TLRPC.TL_pageBlockCollage) block; int count = collage.items.size(); for (int a = 0; a < count; a++) { TLRPC.PageBlock innerBlock = collage.items.get(a); if (innerBlock instanceof TLRPC.TL_pageBlockPhoto || innerBlock instanceof TLRPC.TL_pageBlockVideo && isVideoBlock(block)) { photoBlocks.add(innerBlock); } } } else if (block instanceof TLRPC.TL_pageBlockCover && (block.cover instanceof TLRPC.TL_pageBlockPhoto || block.cover instanceof TLRPC.TL_pageBlockVideo && isVideoBlock(block.cover))) { photoBlocks.add(block.cover); } } public boolean open(MessageObject messageObject) { return open(messageObject, true); } private boolean open(final MessageObject messageObject, boolean first) { if (parentActivity == null || isVisible && !collapsed || messageObject == null) { return false; } if (first) { TLRPC.TL_messages_getWebPage req = new TLRPC.TL_messages_getWebPage(); req.url = messageObject.messageOwner.media.webpage.url; if (messageObject.messageOwner.media.webpage.cached_page instanceof TLRPC.TL_pagePart) { req.hash = 0; } else { req.hash = messageObject.messageOwner.media.webpage.hash; } ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override public void run(TLObject response, TLRPC.TL_error error) { if (response instanceof TLRPC.TL_webPage) { final TLRPC.TL_webPage webPage = (TLRPC.TL_webPage) response; if (webPage.cached_page == null) { return; } AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { if (!pagesStack.isEmpty() && pagesStack.get(0) == messageObject.messageOwner.media.webpage && webPage.cached_page != null) { messageObject.messageOwner.media.webpage = webPage; pagesStack.set(0, webPage); if (pagesStack.size() == 1) { currentPage = webPage; ApplicationLoader.applicationContext .getSharedPreferences("articles", Activity.MODE_PRIVATE).edit() .remove("article" + currentPage.id).commit(); updateInterfaceForCurrentPage(false); } } } }); HashMap<Long, TLRPC.WebPage> webpages = new HashMap<>(); webpages.put(webPage.id, webPage); MessagesStorage.getInstance().putWebPages(webpages); } } }); } pagesStack.clear(); collapsed = false; backDrawable.setRotation(0, false); containerView.setTranslationX(0); containerView.setTranslationY(0); listView.setTranslationY(0); listView.setAlpha(1.0f); windowView.setInnerTranslationX(0); actionBar.setVisibility(View.GONE); bottomLayout.setVisibility(View.GONE); captionTextViewNew.setVisibility(View.GONE); captionTextViewOld.setVisibility(View.GONE); shareContainer.setAlpha(0.0f); backButton.setAlpha(0.0f); layoutManager.scrollToPositionWithOffset(0, 0); checkScroll(-AndroidUtilities.dp(56)); TLRPC.WebPage webPage = messageObject.messageOwner.media.webpage; String webPageUrl = webPage.url.toLowerCase(); int index; String anchor = null; for (int a = 0; a < messageObject.messageOwner.entities.size(); a++) { TLRPC.MessageEntity entity = messageObject.messageOwner.entities.get(a); if (entity instanceof TLRPC.TL_messageEntityUrl) { try { String url = messageObject.messageOwner.message .substring(entity.offset, entity.offset + entity.length).toLowerCase(); if (url.contains(webPageUrl) || webPageUrl.contains(url)) { if ((index = url.lastIndexOf('#')) != -1) { anchor = url.substring(index + 1); } break; } } catch (Exception e) { FileLog.e(e); } } } addPageToStack(webPage, anchor); lastInsets = null; if (!isVisible) { WindowManager wm = (WindowManager) parentActivity.getSystemService(Context.WINDOW_SERVICE); if (attachedToWindow) { try { wm.removeView(windowView); } catch (Exception e) { //ignore } } try { if (Build.VERSION.SDK_INT >= 21) { windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; } windowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; windowView.setFocusable(false); containerView.setFocusable(false); wm.addView(windowView, windowLayoutParams); } catch (Exception e) { FileLog.e(e); return false; } } else { windowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; WindowManager wm = (WindowManager) parentActivity.getSystemService(Context.WINDOW_SERVICE); wm.updateViewLayout(windowView, windowLayoutParams); } isVisible = true; animationInProgress = 1; windowView.setAlpha(0); containerView.setAlpha(0); final AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(ObjectAnimator.ofFloat(windowView, "alpha", 0, 1.0f), ObjectAnimator.ofFloat(containerView, "alpha", 0.0f, 1.0f), ObjectAnimator.ofFloat(windowView, "translationX", AndroidUtilities.dp(56), 0)); animationEndRunnable = new Runnable() { @Override public void run() { if (containerView == null || windowView == null) { return; } if (Build.VERSION.SDK_INT >= 18) { containerView.setLayerType(View.LAYER_TYPE_NONE, null); } animationInProgress = 0; } }; animatorSet.setDuration(150); animatorSet.setInterpolator(interpolator); animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { NotificationCenter.getInstance().setAnimationInProgress(false); if (animationEndRunnable != null) { animationEndRunnable.run(); animationEndRunnable = null; } } }); } }); transitionAnimationStartTime = System.currentTimeMillis(); AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { NotificationCenter.getInstance() .setAllowedNotificationsDutingAnimation(new int[] { NotificationCenter.dialogsNeedReload, NotificationCenter.closeChats, NotificationCenter.mediaCountDidLoaded, NotificationCenter.mediaDidLoaded, NotificationCenter.dialogPhotosLoaded }); NotificationCenter.getInstance().setAnimationInProgress(true); animatorSet.start(); } }); if (Build.VERSION.SDK_INT >= 18) { containerView.setLayerType(View.LAYER_TYPE_HARDWARE, null); } showActionBar(200); return true; } private void hideActionBar() { AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(ObjectAnimator.ofFloat(backButton, "alpha", 0.0f), ObjectAnimator.ofFloat(shareContainer, "alpha", 0.0f)); animatorSet.setDuration(250); animatorSet.setInterpolator(new DecelerateInterpolator()); animatorSet.start(); } private void showActionBar(int delay) { AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(ObjectAnimator.ofFloat(backButton, "alpha", 1.0f), ObjectAnimator.ofFloat(shareContainer, "alpha", 1.0f)); animatorSet.setDuration(150); animatorSet.setStartDelay(delay); animatorSet.start(); } private void showProgressView(final boolean show) { if (progressViewAnimation != null) { progressViewAnimation.cancel(); } progressViewAnimation = new AnimatorSet(); if (show) { progressView.setVisibility(View.VISIBLE); shareContainer.setEnabled(false); progressViewAnimation.playTogether(ObjectAnimator.ofFloat(shareButton, "scaleX", 0.1f), ObjectAnimator.ofFloat(shareButton, "scaleY", 0.1f), ObjectAnimator.ofFloat(shareButton, "alpha", 0.0f), ObjectAnimator.ofFloat(progressView, "scaleX", 1.0f), ObjectAnimator.ofFloat(progressView, "scaleY", 1.0f), ObjectAnimator.ofFloat(progressView, "alpha", 1.0f)); } else { shareButton.setVisibility(View.VISIBLE); shareContainer.setEnabled(true); progressViewAnimation.playTogether(ObjectAnimator.ofFloat(progressView, "scaleX", 0.1f), ObjectAnimator.ofFloat(progressView, "scaleY", 0.1f), ObjectAnimator.ofFloat(progressView, "alpha", 0.0f), ObjectAnimator.ofFloat(shareButton, "scaleX", 1.0f), ObjectAnimator.ofFloat(shareButton, "scaleY", 1.0f), ObjectAnimator.ofFloat(shareButton, "alpha", 1.0f)); } progressViewAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (progressViewAnimation != null && progressViewAnimation.equals(animation)) { if (!show) { progressView.setVisibility(View.INVISIBLE); } else { shareButton.setVisibility(View.INVISIBLE); } } } @Override public void onAnimationCancel(Animator animation) { if (progressViewAnimation != null && progressViewAnimation.equals(animation)) { progressViewAnimation = null; } } }); progressViewAnimation.setDuration(150); progressViewAnimation.start(); } public void collapse() { if (parentActivity == null || !isVisible || checkAnimation()) { return; } if (fullscreenVideoContainer.getVisibility() == View.VISIBLE) { if (customView != null) { fullscreenVideoContainer.setVisibility(View.INVISIBLE); customViewCallback.onCustomViewHidden(); fullscreenVideoContainer.removeView(customView); customView = null; } else if (fullscreenedVideo != null) { fullscreenedVideo.exitFullscreen(); } } if (isPhotoVisible) { closePhoto(false); } try { if (visibleDialog != null) { visibleDialog.dismiss(); visibleDialog = null; } } catch (Exception e) { FileLog.e(e); } AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether( ObjectAnimator.ofFloat(containerView, "translationX", containerView.getMeasuredWidth() - AndroidUtilities.dp(56)), ObjectAnimator.ofFloat(containerView, "translationY", ActionBar.getCurrentActionBarHeight() + (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0)), ObjectAnimator.ofFloat(windowView, "alpha", 0.0f), ObjectAnimator.ofFloat(listView, "alpha", 0.0f), ObjectAnimator.ofFloat(listView, "translationY", -AndroidUtilities.dp(56)), ObjectAnimator.ofFloat(headerView, "translationY", 0), ObjectAnimator.ofFloat(backButton, "scaleX", 1.0f), ObjectAnimator.ofFloat(backButton, "scaleY", 1.0f), ObjectAnimator.ofFloat(backButton, "translationY", 0), ObjectAnimator.ofFloat(shareContainer, "scaleX", 1.0f), ObjectAnimator.ofFloat(shareContainer, "translationY", 0), ObjectAnimator.ofFloat(shareContainer, "scaleY", 1.0f)); collapsed = true; animationInProgress = 2; animationEndRunnable = new Runnable() { @Override public void run() { if (containerView == null) { return; } if (Build.VERSION.SDK_INT >= 18) { containerView.setLayerType(View.LAYER_TYPE_NONE, null); } animationInProgress = 0; //windowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; WindowManager wm = (WindowManager) parentActivity.getSystemService(Context.WINDOW_SERVICE); wm.updateViewLayout(windowView, windowLayoutParams); //onClosed(); //containerView.setScaleX(1.0f); //containerView.setScaleY(1.0f); } }; animatorSet.setInterpolator(new DecelerateInterpolator()); animatorSet.setDuration(250); animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (animationEndRunnable != null) { animationEndRunnable.run(); animationEndRunnable = null; } } }); transitionAnimationStartTime = System.currentTimeMillis(); if (Build.VERSION.SDK_INT >= 18) { containerView.setLayerType(View.LAYER_TYPE_HARDWARE, null); } backDrawable.setRotation(1, true); animatorSet.start(); } public void uncollapse() { if (parentActivity == null || !isVisible || checkAnimation()) { return; } /*windowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; WindowManager wm = (WindowManager) parentActivity.getSystemService(Context.WINDOW_SERVICE); wm.updateViewLayout(windowView, windowLayoutParams);*/ AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(ObjectAnimator.ofFloat(containerView, "translationX", 0), ObjectAnimator.ofFloat(containerView, "translationY", 0), ObjectAnimator.ofFloat(windowView, "alpha", 1.0f), ObjectAnimator.ofFloat(listView, "alpha", 1.0f), ObjectAnimator.ofFloat(listView, "translationY", 0), ObjectAnimator.ofFloat(headerView, "translationY", 0), ObjectAnimator.ofFloat(backButton, "scaleX", 1.0f), ObjectAnimator.ofFloat(backButton, "scaleY", 1.0f), ObjectAnimator.ofFloat(backButton, "translationY", 0), ObjectAnimator.ofFloat(shareContainer, "scaleX", 1.0f), ObjectAnimator.ofFloat(shareContainer, "translationY", 0), ObjectAnimator.ofFloat(shareContainer, "scaleY", 1.0f)); collapsed = false; animationInProgress = 2; animationEndRunnable = new Runnable() { @Override public void run() { if (containerView == null) { return; } if (Build.VERSION.SDK_INT >= 18) { containerView.setLayerType(View.LAYER_TYPE_NONE, null); } animationInProgress = 0; //onClosed(); } }; animatorSet.setDuration(250); animatorSet.setInterpolator(new DecelerateInterpolator()); animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (animationEndRunnable != null) { animationEndRunnable.run(); animationEndRunnable = null; } } }); transitionAnimationStartTime = System.currentTimeMillis(); if (Build.VERSION.SDK_INT >= 18) { containerView.setLayerType(View.LAYER_TYPE_HARDWARE, null); } backDrawable.setRotation(0, true); animatorSet.start(); } private void saveCurrentPagePosition() { if (currentPage == null) { return; } int position = layoutManager.findFirstVisibleItemPosition(); if (position != RecyclerView.NO_POSITION) { int offset; View view = layoutManager.findViewByPosition(position); if (view != null) { offset = view.getTop(); } else { offset = 0; } SharedPreferences.Editor editor = ApplicationLoader.applicationContext .getSharedPreferences("articles", Activity.MODE_PRIVATE).edit(); String key = "article" + currentPage.id; editor.putInt(key, position).putInt(key + "o", offset) .putBoolean(key + "r", AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y) .commit(); } } public void close(boolean byBackPress, boolean force) { if (parentActivity == null || !isVisible || checkAnimation()) { return; } if (fullscreenVideoContainer.getVisibility() == View.VISIBLE) { if (customView != null) { fullscreenVideoContainer.setVisibility(View.INVISIBLE); customViewCallback.onCustomViewHidden(); fullscreenVideoContainer.removeView(customView); customView = null; } else if (fullscreenedVideo != null) { fullscreenedVideo.exitFullscreen(); } if (!force) { return; } } if (isPhotoVisible) { closePhoto(!force); if (!force) { return; } } if (openUrlReqId != 0) { ConnectionsManager.getInstance().cancelRequest(openUrlReqId, true); openUrlReqId = 0; showProgressView(false); } if (previewsReqId != 0) { ConnectionsManager.getInstance().cancelRequest(previewsReqId, true); previewsReqId = 0; showProgressView(false); } saveCurrentPagePosition(); if (byBackPress && !force) { if (removeLastPageFromStack()) { return; } } try { if (visibleDialog != null) { visibleDialog.dismiss(); visibleDialog = null; } } catch (Exception e) { FileLog.e(e); } AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(ObjectAnimator.ofFloat(windowView, "alpha", 0), ObjectAnimator.ofFloat(containerView, "alpha", 0.0f), ObjectAnimator.ofFloat(windowView, "translationX", 0, AndroidUtilities.dp(56))); animationInProgress = 2; animationEndRunnable = new Runnable() { @Override public void run() { if (containerView == null) { return; } if (Build.VERSION.SDK_INT >= 18) { containerView.setLayerType(View.LAYER_TYPE_NONE, null); } animationInProgress = 0; onClosed(); } }; animatorSet.setDuration(150); animatorSet.setInterpolator(interpolator); animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (animationEndRunnable != null) { animationEndRunnable.run(); animationEndRunnable = null; } } }); transitionAnimationStartTime = System.currentTimeMillis(); if (Build.VERSION.SDK_INT >= 18) { containerView.setLayerType(View.LAYER_TYPE_HARDWARE, null); } animatorSet.start(); } private void onClosed() { isVisible = false; currentPage = null; blocks.clear(); photoBlocks.clear(); adapter.notifyDataSetChanged(); try { parentActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } catch (Exception e) { FileLog.e(e); } for (int a = 0; a < createdWebViews.size(); a++) { BlockEmbedCell cell = createdWebViews.get(a); cell.destroyWebView(false); } containerView.post(new Runnable() { @Override public void run() { try { if (windowView.getParent() != null) { WindowManager wm = (WindowManager) parentActivity.getSystemService(Context.WINDOW_SERVICE); wm.removeView(windowView); } } catch (Exception e) { FileLog.e(e); } } }); } private boolean checkAnimation() { if (animationInProgress != 0) { if (Math.abs(transitionAnimationStartTime - System.currentTimeMillis()) >= 500) { if (animationEndRunnable != null) { animationEndRunnable.run(); animationEndRunnable = null; } animationInProgress = 0; } } return animationInProgress != 0; } public void destroyArticleViewer() { if (parentActivity == null || windowView == null) { return; } releasePlayer(); try { if (windowView.getParent() != null) { WindowManager wm = (WindowManager) parentActivity.getSystemService(Context.WINDOW_SERVICE); wm.removeViewImmediate(windowView); } windowView = null; } catch (Exception e) { FileLog.e(e); } for (int a = 0; a < createdWebViews.size(); a++) { BlockEmbedCell cell = createdWebViews.get(a); cell.destroyWebView(true); } createdWebViews.clear(); try { parentActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } catch (Exception e) { FileLog.e(e); } Instance = null; } public boolean isVisible() { return isVisible; } public void showDialog(Dialog dialog) { if (parentActivity == null) { return; } try { if (visibleDialog != null) { visibleDialog.dismiss(); visibleDialog = null; } } catch (Exception e) { FileLog.e(e); } try { visibleDialog = dialog; visibleDialog.setCanceledOnTouchOutside(true); visibleDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { showActionBar(120); visibleDialog = null; } }); dialog.show(); } catch (Exception e) { FileLog.e(e); } } private class WebpageAdapter extends RecyclerListView.SelectionAdapter { private Context context; public WebpageAdapter(Context ctx) { context = ctx; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view; switch (viewType) { case 0: { view = new BlockParagraphCell(context); break; } case 1: { view = new BlockHeaderCell(context); break; } case 2: { view = new BlockDividerCell(context); break; } case 3: { view = new BlockEmbedCell(context); break; } case 4: { view = new BlockSubtitleCell(context); break; } case 5: { view = new BlockVideoCell(context, 0); break; } case 6: { view = new BlockPullquoteCell(context); break; } case 7: { view = new BlockBlockquoteCell(context); break; } case 8: { view = new BlockSlideshowCell(context); break; } case 9: { view = new BlockPhotoCell(context, 0); break; } case 10: { view = new BlockAuthorDateCell(context); break; } case 11: { view = new BlockTitleCell(context); break; } case 12: { view = new BlockListCell(context); break; } case 13: { view = new BlockFooterCell(context); break; } case 14: { view = new BlockPreformattedCell(context); break; } case 15: { view = new BlockSubheaderCell(context); break; } case 16: { view = new BlockEmbedPostCell(context); break; } case 17: { view = new BlockCollageCell(context); break; } case 90: default: { FrameLayout frameLayout = new FrameLayout(context) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(44), MeasureSpec.EXACTLY)); } }; TextView textView = new TextView(context); frameLayout.addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 34, Gravity.LEFT | Gravity.TOP, 0, 10, 0, 0)); textView.setTextColor(0xff78828d); textView.setBackgroundColor(0xffedeff0); textView.setText(LocaleController.getString("PreviewFeedback", R.string.PreviewFeedback)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); textView.setGravity(Gravity.CENTER); view = frameLayout; break; } } view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); return new RecyclerListView.Holder(view); } @Override public boolean isEnabled(RecyclerView.ViewHolder holder) { return false; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (position < blocks.size()) { TLRPC.PageBlock block = blocks.get(position); TLRPC.PageBlock originalBlock = block; if (block instanceof TLRPC.TL_pageBlockCover) { block = block.cover; } switch (holder.getItemViewType()) { case 0: { BlockParagraphCell cell = (BlockParagraphCell) holder.itemView; cell.setBlock((TLRPC.TL_pageBlockParagraph) block); break; } case 1: { BlockHeaderCell cell = (BlockHeaderCell) holder.itemView; cell.setBlock((TLRPC.TL_pageBlockHeader) block); break; } case 2: { BlockDividerCell cell = (BlockDividerCell) holder.itemView; break; } case 3: { BlockEmbedCell cell = (BlockEmbedCell) holder.itemView; cell.setBlock((TLRPC.TL_pageBlockEmbed) block); break; } case 4: { BlockSubtitleCell cell = (BlockSubtitleCell) holder.itemView; cell.setBlock((TLRPC.TL_pageBlockSubtitle) block); break; } case 5: { BlockVideoCell cell = (BlockVideoCell) holder.itemView; cell.setBlock((TLRPC.TL_pageBlockVideo) block, position == 0, position == blocks.size() - 1); cell.setParentBlock(originalBlock); break; } case 6: { BlockPullquoteCell cell = (BlockPullquoteCell) holder.itemView; cell.setBlock((TLRPC.TL_pageBlockPullquote) block); break; } case 7: { BlockBlockquoteCell cell = (BlockBlockquoteCell) holder.itemView; cell.setBlock((TLRPC.TL_pageBlockBlockquote) block); break; } case 8: { BlockSlideshowCell cell = (BlockSlideshowCell) holder.itemView; cell.setBlock((TLRPC.TL_pageBlockSlideshow) block); break; } case 9: { BlockPhotoCell cell = (BlockPhotoCell) holder.itemView; cell.setBlock((TLRPC.TL_pageBlockPhoto) block, position == 0, position == blocks.size() - 1); cell.setParentBlock(originalBlock); break; } case 10: { BlockAuthorDateCell cell = (BlockAuthorDateCell) holder.itemView; cell.setBlock((TLRPC.TL_pageBlockAuthorDate) block); break; } case 11: { BlockTitleCell cell = (BlockTitleCell) holder.itemView; cell.setBlock((TLRPC.TL_pageBlockTitle) block); break; } case 12: { BlockListCell cell = (BlockListCell) holder.itemView; cell.setBlock((TLRPC.TL_pageBlockList) block); break; } case 13: { BlockFooterCell cell = (BlockFooterCell) holder.itemView; cell.setBlock((TLRPC.TL_pageBlockFooter) block); break; } case 14: { BlockPreformattedCell cell = (BlockPreformattedCell) holder.itemView; cell.setBlock((TLRPC.TL_pageBlockPreformatted) block); break; } case 15: { BlockSubheaderCell cell = (BlockSubheaderCell) holder.itemView; cell.setBlock((TLRPC.TL_pageBlockSubheader) block); break; } case 16: { BlockEmbedPostCell cell = (BlockEmbedPostCell) holder.itemView; cell.setBlock((TLRPC.TL_pageBlockEmbedPost) block); break; } case 17: { BlockCollageCell cell = (BlockCollageCell) holder.itemView; cell.setBlock((TLRPC.TL_pageBlockCollage) block); break; } } } } private int getTypeForBlock(TLRPC.PageBlock block) { if (block instanceof TLRPC.TL_pageBlockParagraph) { return 0; } else if (block instanceof TLRPC.TL_pageBlockHeader) { return 1; } else if (block instanceof TLRPC.TL_pageBlockDivider) { return 2; } else if (block instanceof TLRPC.TL_pageBlockEmbed) { return 3; } else if (block instanceof TLRPC.TL_pageBlockSubtitle) { return 4; } else if (block instanceof TLRPC.TL_pageBlockVideo) { return 5; } else if (block instanceof TLRPC.TL_pageBlockPullquote) { return 6; } else if (block instanceof TLRPC.TL_pageBlockBlockquote) { return 7; } else if (block instanceof TLRPC.TL_pageBlockSlideshow) { return 8; } else if (block instanceof TLRPC.TL_pageBlockPhoto) { return 9; } else if (block instanceof TLRPC.TL_pageBlockAuthorDate) { return 10; } else if (block instanceof TLRPC.TL_pageBlockTitle) { return 11; } else if (block instanceof TLRPC.TL_pageBlockList) { return 12; } else if (block instanceof TLRPC.TL_pageBlockFooter) { return 13; } else if (block instanceof TLRPC.TL_pageBlockPreformatted) { return 14; } else if (block instanceof TLRPC.TL_pageBlockSubheader) { return 15; } else if (block instanceof TLRPC.TL_pageBlockEmbedPost) { return 16; } else if (block instanceof TLRPC.TL_pageBlockCollage) { return 17; } else if (block instanceof TLRPC.TL_pageBlockCover) { return getTypeForBlock(block.cover); } return 0; } @Override public int getItemViewType(int position) { if (position == blocks.size()) { return 90; } return getTypeForBlock(blocks.get(position)); } @Override public int getItemCount() { return currentPage != null && currentPage.cached_page != null ? blocks.size() + 1 : 0; } } private class BlockVideoCell extends View implements MediaController.FileDownloadProgressListener { private StaticLayout textLayout; private ImageReceiver imageView; private RadialProgress radialProgress; private int lastCreatedWidth; private int currentType; private boolean isFirst; private boolean isLast; private int textX; private int textY; private int buttonX; private int buttonY; private boolean photoPressed; private int buttonState; private int buttonPressed; private boolean cancelLoading; private int TAG; private TLRPC.TL_pageBlockVideo currentBlock; private TLRPC.PageBlock parentBlock; private TLRPC.Document currentDocument; private boolean isGif; public BlockVideoCell(Context context, int type) { super(context); imageView = new ImageReceiver(this); currentType = type; radialProgress = new RadialProgress(this); radialProgress.setProgressColor(Theme.ARTICLE_VIEWER_MEDIA_PROGRESS_COLOR); TAG = MediaController.getInstance().generateObserverTag(); } public void setBlock(TLRPC.TL_pageBlockVideo block, boolean first, boolean last) { currentBlock = block; parentBlock = null; cancelLoading = false; currentDocument = getDocumentWithId(currentBlock.video_id); isGif = MessageObject.isGifDocument(currentDocument)/* && currentBlock.autoplay*/; lastCreatedWidth = 0; isFirst = first; isLast = last; updateButtonState(false); requestLayout(); } public void setParentBlock(TLRPC.PageBlock block) { parentBlock = block; } @Override public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); float y = event.getY(); if (event.getAction() == MotionEvent.ACTION_DOWN && imageView.isInsideImage(x, y)) { if (buttonState != -1 && x >= buttonX && x <= buttonX + AndroidUtilities.dp(48) && y >= buttonY && y <= buttonY + AndroidUtilities.dp(48) || buttonState == 0) { buttonPressed = 1; invalidate(); } else { photoPressed = true; } } else if (event.getAction() == MotionEvent.ACTION_UP) { if (photoPressed) { photoPressed = false; openPhoto(currentBlock); } else if (buttonPressed == 1) { buttonPressed = 0; playSoundEffect(SoundEffectConstants.CLICK); didPressedButton(false); radialProgress.swapBackground(getDrawableForCurrentState()); invalidate(); } } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { photoPressed = false; } return photoPressed || buttonPressed != 0 || checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = 0; if (currentType == 1) { width = listView.getWidth(); height = ((View) getParent()).getMeasuredHeight(); } else if (currentType == 2) { height = width; } if (currentBlock != null) { int photoWidth = width; int photoX; int textWidth; if (currentType == 0 && currentBlock.level > 0) { textX = photoX = AndroidUtilities.dp(14 * currentBlock.level) + AndroidUtilities.dp(18); photoWidth -= photoX + AndroidUtilities.dp(18); textWidth = photoWidth; } else { photoX = 0; textX = AndroidUtilities.dp(18); textWidth = width - AndroidUtilities.dp(36); } if (currentDocument != null) { TLRPC.PhotoSize thumb = currentDocument.thumb; if (currentType == 0) { float scale; scale = photoWidth / (float) thumb.w; height = (int) (scale * thumb.h); if (parentBlock instanceof TLRPC.TL_pageBlockCover) { height = Math.min(height, photoWidth); } else { int maxHeight = (int) ((Math.max(listView.getMeasuredWidth(), listView.getMeasuredHeight()) - AndroidUtilities.dp(56)) * 0.9f); if (height > maxHeight) { height = maxHeight; scale = height / (float) thumb.h; photoWidth = (int) (scale * thumb.w); photoX += (width - photoX - photoWidth) / 2; } } } imageView.setImageCoords(photoX, (isFirst || currentType == 1 || currentType == 2 || currentBlock.level > 0) ? 0 : AndroidUtilities.dp(8), photoWidth, height); if (isGif) { String filter = String.format(Locale.US, "%d_%d", photoWidth, height); imageView.setImage(currentDocument, filter, thumb != null ? thumb.location : null, thumb != null ? "80_80_b" : null, currentDocument.size, null, true); } else { imageView.setImage(null, null, thumb != null ? thumb.location : null, thumb != null ? "80_80_b" : null, 0, null, true); } int size = AndroidUtilities.dp(48); buttonX = (int) (imageView.getImageX() + (imageView.getImageWidth() - size) / 2.0f); buttonY = (int) (imageView.getImageY() + (imageView.getImageHeight() - size) / 2.0f); radialProgress.setProgressRect(buttonX, buttonY, buttonX + size, buttonY + size); } if (currentType == 0 && lastCreatedWidth != width) { textLayout = createLayoutForText(null, currentBlock.caption, textWidth, currentBlock); if (textLayout != null) { height += AndroidUtilities.dp(8) + textLayout.getHeight(); } //lastCreatedWidth = width; } if (!isFirst && currentType == 0 && currentBlock.level <= 0) { height += AndroidUtilities.dp(8); } if (currentType != 2) { height += AndroidUtilities.dp(8); } } else { height = 1; } setMeasuredDimension(width, height); } @Override protected void onDraw(Canvas canvas) { if (currentBlock == null) { return; } imageView.draw(canvas); if (imageView.getVisible()) { radialProgress.draw(canvas); } if (textLayout != null) { canvas.save(); canvas.translate(textX, textY = imageView.getImageY() + imageView.getImageHeight() + AndroidUtilities.dp(8)); drawLayoutLink(canvas, textLayout); textLayout.draw(canvas); canvas.restore(); } if (currentBlock.level > 0) { canvas.drawRect(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(20), getMeasuredHeight() - (currentBlock.bottom ? AndroidUtilities.dp(6) : 0), quoteLinePaint); } } private Drawable getDrawableForCurrentState() { radialProgress.setAlphaForPrevious(true); if (buttonState >= 0 && buttonState < 4) { return Theme.chat_photoStatesDrawables[buttonState][buttonPressed]; } return null; } public void updateButtonState(boolean animated) { String fileName = FileLoader.getAttachFileName(currentDocument); File path = FileLoader.getPathToAttach(currentDocument, true); boolean fileExists = path.exists(); if (TextUtils.isEmpty(fileName)) { radialProgress.setBackground(null, false, false); return; } if (!fileExists) { MediaController.getInstance().addLoadingFileObserver(fileName, null, this); float setProgress = 0; boolean progressVisible = false; if (!FileLoader.getInstance().isLoadingFile(fileName)) { if (!cancelLoading && isGif) { progressVisible = true; buttonState = 1; } else { buttonState = 0; } } else { progressVisible = true; buttonState = 1; Float progress = ImageLoader.getInstance().getFileProgress(fileName); setProgress = progress != null ? progress : 0; } radialProgress.setBackground(getDrawableForCurrentState(), progressVisible, animated); radialProgress.setProgress(setProgress, false); invalidate(); } else { MediaController.getInstance().removeLoadingFileObserver(this); if (!isGif) { buttonState = 3; } else { buttonState = -1; } radialProgress.setBackground(getDrawableForCurrentState(), false, animated); invalidate(); } } private void didPressedButton(boolean animated) { if (buttonState == 0) { cancelLoading = false; radialProgress.setProgress(0, false); if (isGif) { imageView.setImage(currentDocument, null, currentDocument.thumb != null ? currentDocument.thumb.location : null, "80_80_b", currentDocument.size, null, true); } else { FileLoader.getInstance().loadFile(currentDocument, true, true); } buttonState = 1; radialProgress.setBackground(getDrawableForCurrentState(), true, animated); invalidate(); } else if (buttonState == 1) { cancelLoading = true; if (isGif) { imageView.cancelLoadImage(); } else { FileLoader.getInstance().cancelLoadFile(currentDocument); } buttonState = 0; radialProgress.setBackground(getDrawableForCurrentState(), false, animated); invalidate(); } else if (buttonState == 2) { imageView.setAllowStartAnimation(true); imageView.startAnimation(); buttonState = -1; radialProgress.setBackground(getDrawableForCurrentState(), false, animated); } else if (buttonState == 3) { openPhoto(currentBlock); } } @Override public void onFailedDownload(String fileName) { updateButtonState(false); } @Override public void onSuccessDownload(String fileName) { radialProgress.setProgress(1, true); if (isGif) { buttonState = 2; didPressedButton(true); } else { updateButtonState(true); } } @Override public void onProgressUpload(String fileName, float progress, boolean isEncrypted) { } @Override public void onProgressDownload(String fileName, float progress) { radialProgress.setProgress(progress, true); if (buttonState != 1) { updateButtonState(false); } } @Override public int getObserverTag() { return TAG; } } private class BlockEmbedPostCell extends View { private ImageReceiver avatarImageView; private AvatarDrawable avatarDrawable; private StaticLayout dateLayout; private StaticLayout nameLayout; private StaticLayout textLayout; private boolean avatarVisible; private int nameX; private int dateX; private int lastCreatedWidth; private int textX = AndroidUtilities.dp(18 + 14); private int textY = AndroidUtilities.dp(40 + 8 + 8); private int captionX = AndroidUtilities.dp(18); private int captionY; private int lineHeight; private TLRPC.TL_pageBlockEmbedPost currentBlock; public BlockEmbedPostCell(Context context) { super(context); avatarImageView = new ImageReceiver(this); avatarImageView.setRoundRadius(AndroidUtilities.dp(20)); avatarImageView.setImageCoords(AndroidUtilities.dp(18 + 14), AndroidUtilities.dp(8), AndroidUtilities.dp(40), AndroidUtilities.dp(40)); avatarDrawable = new AvatarDrawable(); } public void setBlock(TLRPC.TL_pageBlockEmbedPost block) { currentBlock = block; lastCreatedWidth = 0; requestLayout(); } @Override public boolean onTouchEvent(MotionEvent event) { return checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = 0; if (currentBlock != null) { if (lastCreatedWidth != width) { if (avatarVisible = (currentBlock.author_photo_id != 0)) { TLRPC.Photo photo = getPhotoWithId(currentBlock.author_photo_id); if (avatarVisible = (photo != null)) { avatarDrawable.setInfo(0, currentBlock.author, null, false); TLRPC.PhotoSize image = FileLoader.getClosestPhotoSizeWithSize(photo.sizes, AndroidUtilities.dp(40), true); avatarImageView.setImage(image.location, String.format(Locale.US, "%d_%d", 40, 40), avatarDrawable, 0, null, true); } } nameLayout = createLayoutForText(currentBlock.author, null, width - AndroidUtilities.dp(36 + 14 + (avatarVisible ? 40 + 14 : 0)), currentBlock); if (currentBlock.date != 0) { dateLayout = createLayoutForText( LocaleController.getInstance().chatFullDate.format((long) currentBlock.date * 1000), null, width - AndroidUtilities.dp(36 + 14 + (avatarVisible ? 40 + 14 : 0)), currentBlock); } else { dateLayout = null; } height = AndroidUtilities.dp(40 + 8 + 8); if (currentBlock.text != null) { textLayout = createLayoutForText(null, currentBlock.text, width - AndroidUtilities.dp(36 + 14), currentBlock); if (textLayout != null) { height += AndroidUtilities.dp(8) + textLayout.getHeight(); } } lineHeight = height; //lastCreatedWidth = width; } } else { height = 1; } setMeasuredDimension(width, height); } @Override protected void onDraw(Canvas canvas) { if (currentBlock == null) { return; } if (avatarVisible) { avatarImageView.draw(canvas); } if (nameLayout != null) { canvas.save(); canvas.translate(AndroidUtilities.dp(18 + 14 + (avatarVisible ? 40 + 14 : 0)), AndroidUtilities.dp(dateLayout != null ? 10 : 19)); nameLayout.draw(canvas); canvas.restore(); } if (dateLayout != null) { canvas.save(); canvas.translate(AndroidUtilities.dp(18 + 14 + (avatarVisible ? 40 + 14 : 0)), AndroidUtilities.dp(29)); dateLayout.draw(canvas); canvas.restore(); } if (textLayout != null) { canvas.save(); canvas.translate(textX, textY); drawLayoutLink(canvas, textLayout); textLayout.draw(canvas); canvas.restore(); } canvas.drawRect(AndroidUtilities.dp(18), AndroidUtilities.dp(6), AndroidUtilities.dp(20), lineHeight - (currentBlock.level != 0 ? 0 : AndroidUtilities.dp(6)), quoteLinePaint); } } private class BlockParagraphCell extends View { private StaticLayout textLayout; private int lastCreatedWidth; private int textX; private int textY; private TLRPC.TL_pageBlockParagraph currentBlock; public BlockParagraphCell(Context context) { super(context); } public void setBlock(TLRPC.TL_pageBlockParagraph block) { currentBlock = block; lastCreatedWidth = 0; requestLayout(); } @Override public boolean onTouchEvent(MotionEvent event) { return checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = 0; if (currentBlock != null) { if (currentBlock.level == 0) { if (currentBlock.caption != null) { textY = AndroidUtilities.dp(4); } else { textY = AndroidUtilities.dp(8); } textX = AndroidUtilities.dp(18); } else { textY = 0; textX = AndroidUtilities.dp(18 + 14 * currentBlock.level); } if (lastCreatedWidth != width) { if (currentBlock.text != null) { textLayout = createLayoutForText(null, currentBlock.text, width - AndroidUtilities.dp(18) - textX, currentBlock); } else if (currentBlock.caption != null) { textLayout = createLayoutForText(null, currentBlock.caption, width - AndroidUtilities.dp(18) - textX, currentBlock); } if (textLayout != null) { height = textLayout.getHeight(); if (currentBlock.level > 0) { height += AndroidUtilities.dp(8); } else { height += AndroidUtilities.dp(8 + 8); } } //lastCreatedWidth = width; } } else { height = 1; } setMeasuredDimension(width, height); } @Override protected void onDraw(Canvas canvas) { if (currentBlock == null) { return; } if (textLayout != null) { canvas.save(); canvas.translate(textX, textY); drawLayoutLink(canvas, textLayout); textLayout.draw(canvas); canvas.restore(); } if (currentBlock.level > 0) { canvas.drawRect(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(20), getMeasuredHeight() - (currentBlock.bottom ? AndroidUtilities.dp(6) : 0), quoteLinePaint); } } } private class BlockEmbedCell extends FrameLayout { private TouchyWebView webView; private WebPlayerView videoView; private StaticLayout textLayout; private int lastCreatedWidth; private int textX; private int textY; private int listX; private TLRPC.TL_pageBlockEmbed currentBlock; public class TouchyWebView extends WebView { public TouchyWebView(Context context) { super(context); setFocusable(false); } @Override public boolean onTouchEvent(MotionEvent event) { if (currentBlock.allow_scrolling) { requestDisallowInterceptTouchEvent(true); } else { windowView.requestDisallowInterceptTouchEvent(true); } return super.onTouchEvent(event); } } @SuppressLint("SetJavaScriptEnabled") public BlockEmbedCell(final Context context) { super(context); setWillNotDraw(false); videoView = new WebPlayerView(context, false, false, new WebPlayerView.WebPlayerViewDelegate() { @Override public void onInitFailed() { webView.setVisibility(VISIBLE); videoView.setVisibility(INVISIBLE); videoView.loadVideo(null, null, null, false); HashMap<String, String> args = new HashMap<>(); args.put("Referer", "http://youtube.com"); webView.loadUrl(currentBlock.url, args); } @Override public void onVideoSizeChanged(float aspectRatio, int rotation) { fullscreenAspectRatioView.setAspectRatio(aspectRatio, rotation); } @Override public void onInlineSurfaceTextureReady() { } @Override public TextureView onSwitchToFullscreen(View controlsView, boolean fullscreen, float aspectRatio, int rotation, boolean byButton) { if (fullscreen) { fullscreenAspectRatioView.addView(fullscreenTextureView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); fullscreenAspectRatioView.setVisibility(View.VISIBLE); fullscreenAspectRatioView.setAspectRatio(aspectRatio, rotation); fullscreenedVideo = videoView; fullscreenVideoContainer.addView(controlsView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); fullscreenVideoContainer.setVisibility(VISIBLE); } else { fullscreenAspectRatioView.removeView(fullscreenTextureView); fullscreenedVideo = null; fullscreenAspectRatioView.setVisibility(View.GONE); fullscreenVideoContainer.setVisibility(INVISIBLE); } return fullscreenTextureView; } @Override public void prepareToSwitchInlineMode(boolean inline, Runnable switchInlineModeRunnable, float aspectRatio, boolean animated) { } @Override public TextureView onSwitchInlineMode(View controlsView, boolean inline, float aspectRatio, int rotation, boolean animated) { return null; } @Override public void onSharePressed() { if (parentActivity == null) { return; } showDialog( new ShareAlert(parentActivity, null, currentBlock.url, false, currentBlock.url, true)); } @Override public void onPlayStateChanged(WebPlayerView playerView, boolean playing) { if (playing) { if (currentPlayingVideo != null && currentPlayingVideo != playerView) { currentPlayingVideo.pause(); } currentPlayingVideo = playerView; try { parentActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } catch (Exception e) { FileLog.e(e); } } else { if (currentPlayingVideo == playerView) { currentPlayingVideo = null; } try { parentActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } catch (Exception e) { FileLog.e(e); } } } @Override public boolean checkInlinePermissons() { return false; } @Override public ViewGroup getTextureViewContainer() { return null; } }); addView(videoView); createdWebViews.add(this); webView = new TouchyWebView(context); webView.getSettings().setJavaScriptEnabled(true); webView.getSettings().setDomStorageEnabled(true); webView.getSettings().setAllowContentAccess(true); if (Build.VERSION.SDK_INT >= 17) { webView.getSettings().setMediaPlaybackRequiresUserGesture(false); } if (Build.VERSION.SDK_INT >= 21) { webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); CookieManager cookieManager = CookieManager.getInstance(); cookieManager.setAcceptThirdPartyCookies(webView, true); } webView.setWebChromeClient(new WebChromeClient() { @Override public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) { onShowCustomView(view, callback); } @Override public void onShowCustomView(View view, CustomViewCallback callback) { if (customView != null) { callback.onCustomViewHidden(); return; } customView = view; fullscreenVideoContainer.setVisibility(VISIBLE); fullscreenVideoContainer.addView(view, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); customViewCallback = callback; } @Override public void onHideCustomView() { super.onHideCustomView(); if (customView == null) { return; } fullscreenVideoContainer.setVisibility(INVISIBLE); fullscreenVideoContainer.removeView(customView); if (customViewCallback != null && !customViewCallback.getClass().getName().contains(".chromium.")) { customViewCallback.onCustomViewHidden(); } customView = null; } }); webView.setWebViewClient(new WebViewClient() { @Override public void onLoadResource(WebView view, String url) { super.onLoadResource(view, url); } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); //progressBar.setVisibility(INVISIBLE); } /*@TargetApi(Build.VERSION_CODES.LOLLIPOP) TODO check @Override public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { Browser.openUrl(parentActivity, request.getUrl()); return true; } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { Browser.openUrl(parentActivity, url); return true; }*/ }); addView(webView); } public void destroyWebView(boolean completely) { try { webView.stopLoading(); webView.loadUrl("about:blank"); if (completely) { webView.destroy(); } currentBlock = null; } catch (Exception e) { FileLog.e(e); } videoView.destroy(); } public void setBlock(TLRPC.TL_pageBlockEmbed block) { /*if (currentBlock == block) { return; }*/ TLRPC.TL_pageBlockEmbed previousBlock = currentBlock; currentBlock = block; lastCreatedWidth = 0; if (previousBlock != currentBlock) { try { webView.loadUrl("about:blank"); } catch (Exception e) { FileLog.e(e); } try { if (currentBlock.html != null) { webView.loadData(currentBlock.html, "text/html", "UTF-8"); } else { TLRPC.Photo thumb = currentBlock.poster_photo_id != 0 ? getPhotoWithId(currentBlock.poster_photo_id) : null; boolean handled = videoView.loadVideo(block.url, thumb, null, currentBlock.autoplay); if (handled) { webView.setVisibility(INVISIBLE); videoView.setVisibility(VISIBLE); } else { webView.setVisibility(VISIBLE); videoView.setVisibility(INVISIBLE); videoView.loadVideo(null, null, null, false); HashMap<String, String> args = new HashMap<>(); args.put("Referer", "http://youtube.com"); webView.loadUrl(currentBlock.url, args); } } } catch (Exception e) { FileLog.e(e); } } requestLayout(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (!isVisible) { currentBlock = null; } } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); } @Override public boolean onTouchEvent(MotionEvent event) { return checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height; if (currentBlock != null) { int listWidth = width; int textWidth; if (currentBlock.level > 0) { textX = listX = AndroidUtilities.dp(14 * currentBlock.level) + AndroidUtilities.dp(18); listWidth -= listX + AndroidUtilities.dp(18); textWidth = listWidth; } else { listX = 0; textX = AndroidUtilities.dp(18); textWidth = width - AndroidUtilities.dp(36); } float scale; if (currentBlock.w == 0) { scale = 1; } else { scale = width / (float) currentBlock.w; } height = (int) (currentBlock.w == 0 ? AndroidUtilities.dp(currentBlock.h) * scale : currentBlock.h * scale); webView.measure(MeasureSpec.makeMeasureSpec(listWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); if (videoView.getParent() == this) { videoView.measure(MeasureSpec.makeMeasureSpec(listWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height + AndroidUtilities.dp(10), MeasureSpec.EXACTLY)); } if (lastCreatedWidth != width) { textLayout = createLayoutForText(null, currentBlock.caption, textWidth, currentBlock); if (textLayout != null) { textY = AndroidUtilities.dp(8) + height; height += AndroidUtilities.dp(8) + textLayout.getHeight(); } //lastCreatedWidth = width; } height += AndroidUtilities.dp(5); if (currentBlock.level > 0 && !currentBlock.bottom) { height += AndroidUtilities.dp(8); } else if (currentBlock.level == 0 && textLayout != null) { height += AndroidUtilities.dp(8); } } else { height = 1; } setMeasuredDimension(width, height); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { webView.layout(listX, 0, listX + webView.getMeasuredWidth(), webView.getMeasuredHeight()); if (videoView.getParent() == this) { videoView.layout(listX, 0, listX + videoView.getMeasuredWidth(), videoView.getMeasuredHeight()); } } @Override protected void onDraw(Canvas canvas) { if (currentBlock == null) { return; } if (textLayout != null) { canvas.save(); canvas.translate(textX, textY); drawLayoutLink(canvas, textLayout); textLayout.draw(canvas); canvas.restore(); } if (currentBlock.level > 0) { canvas.drawRect(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(20), getMeasuredHeight() - (currentBlock.bottom ? AndroidUtilities.dp(6) : 0), quoteLinePaint); } } } private class BlockCollageCell extends FrameLayout { private RecyclerListView innerListView; private GridLayoutManager gridLayoutManager; private RecyclerView.Adapter adapter; private StaticLayout textLayout; private int listX; private int textX; private int textY; private boolean inLayout; private TLRPC.TL_pageBlockCollage currentBlock; private int lastCreatedWidth; public BlockCollageCell(Context context) { super(context); innerListView = new RecyclerListView(context) { @Override public void requestLayout() { if (inLayout) { return; } super.requestLayout(); } }; innerListView.setGlowColor(0xfff5f6f7); innerListView.addItemDecoration(new RecyclerView.ItemDecoration() { @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { outRect.top = outRect.left = 0; outRect.bottom = outRect.right = AndroidUtilities.dp(2); } }); innerListView.setLayoutManager(gridLayoutManager = new GridLayoutManager(context, 3)); innerListView.setAdapter(adapter = new RecyclerView.Adapter() { @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view; switch (viewType) { case 0: { view = new BlockPhotoCell(getContext(), 2); break; } case 1: default: { view = new BlockVideoCell(getContext(), 2); break; } } return new RecyclerListView.Holder(view); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { switch (holder.getItemViewType()) { case 0: { BlockPhotoCell cell = (BlockPhotoCell) holder.itemView; cell.setBlock((TLRPC.TL_pageBlockPhoto) currentBlock.items.get(position), true, true); break; } case 1: default: { BlockVideoCell cell = (BlockVideoCell) holder.itemView; cell.setBlock((TLRPC.TL_pageBlockVideo) currentBlock.items.get(position), true, true); break; } } } @Override public int getItemCount() { if (currentBlock == null) { return 0; } return currentBlock.items.size(); } @Override public int getItemViewType(int position) { TLRPC.PageBlock block = currentBlock.items.get(position); if (block instanceof TLRPC.TL_pageBlockPhoto) { return 0; } else { return 1; } } }); addView(innerListView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); setWillNotDraw(false); } public void setBlock(TLRPC.TL_pageBlockCollage block) { currentBlock = block; lastCreatedWidth = 0; adapter.notifyDataSetChanged(); requestLayout(); } @Override public boolean onTouchEvent(MotionEvent event) { return checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { inLayout = true; int width = MeasureSpec.getSize(widthMeasureSpec); int height; if (currentBlock != null) { int listWidth = width; int textWidth; if (currentBlock.level > 0) { textX = listX = AndroidUtilities.dp(14 * currentBlock.level) + AndroidUtilities.dp(18); listWidth -= listX + AndroidUtilities.dp(18); textWidth = listWidth; } else { listX = 0; textX = AndroidUtilities.dp(18); textWidth = width - AndroidUtilities.dp(36); } int countPerRow = listWidth / AndroidUtilities.dp(100); int rowCount = (int) Math.ceil(currentBlock.items.size() / (float) countPerRow); int itemSize = listWidth / countPerRow; gridLayoutManager.setSpanCount(countPerRow); innerListView.measure( MeasureSpec.makeMeasureSpec(itemSize * countPerRow + AndroidUtilities.dp(2), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(itemSize * rowCount, MeasureSpec.EXACTLY)); height = rowCount * itemSize - AndroidUtilities.dp(2); if (lastCreatedWidth != width) { textLayout = createLayoutForText(null, currentBlock.caption, textWidth, currentBlock); if (textLayout != null) { textY = height + AndroidUtilities.dp(8); height += AndroidUtilities.dp(8) + textLayout.getHeight(); } //lastCreatedWidth = width; } if (currentBlock.level > 0 && !currentBlock.bottom) { height += AndroidUtilities.dp(8); } } else { height = 1; } setMeasuredDimension(width, height); inLayout = false; } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { innerListView.layout(listX, 0, listX + innerListView.getMeasuredWidth(), innerListView.getMeasuredHeight()); } @Override protected void onDraw(Canvas canvas) { if (currentBlock == null) { return; } if (textLayout != null) { canvas.save(); canvas.translate(textX, textY); drawLayoutLink(canvas, textLayout); textLayout.draw(canvas); canvas.restore(); } if (currentBlock.level > 0) { canvas.drawRect(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(20), getMeasuredHeight() - (currentBlock.bottom ? AndroidUtilities.dp(6) : 0), quoteLinePaint); } } } private class BlockSlideshowCell extends FrameLayout { private ViewPager innerListView; private PagerAdapter adapter; private View dotsContainer; private TLRPC.TL_pageBlockSlideshow currentBlock; private StaticLayout textLayout; private int lastCreatedWidth; private int textX = AndroidUtilities.dp(18); private int textY; public BlockSlideshowCell(Context context) { super(context); if (dotsPaint == null) { dotsPaint = new Paint(Paint.ANTI_ALIAS_FLAG); dotsPaint.setColor(0xffffffff); } innerListView = new ViewPager(context) { @Override public boolean onTouchEvent(MotionEvent ev) { return super.onTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { windowView.requestDisallowInterceptTouchEvent(true); return super.onInterceptTouchEvent(ev); } }; innerListView.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(int position) { dotsContainer.invalidate(); } @Override public void onPageScrollStateChanged(int state) { } }); innerListView.setAdapter(adapter = new PagerAdapter() { class ObjectContainer { private TLRPC.PageBlock block; private View view; } @Override public int getCount() { if (currentBlock == null) { return 0; } return currentBlock.items.size(); } @Override public boolean isViewFromObject(View view, Object object) { return ((ObjectContainer) object).view == view; } @Override public int getItemPosition(Object object) { ObjectContainer objectContainer = (ObjectContainer) object; if (currentBlock.items.contains(objectContainer.block)) { return POSITION_UNCHANGED; } return POSITION_NONE; } @Override public Object instantiateItem(ViewGroup container, int position) { View view; TLRPC.PageBlock block = currentBlock.items.get(position); if (block instanceof TLRPC.TL_pageBlockPhoto) { view = new BlockPhotoCell(getContext(), 1); ((BlockPhotoCell) view).setBlock((TLRPC.TL_pageBlockPhoto) block, true, true); } else { view = new BlockVideoCell(getContext(), 1); ((BlockVideoCell) view).setBlock((TLRPC.TL_pageBlockVideo) block, true, true); } container.addView(view); ObjectContainer objectContainer = new ObjectContainer(); objectContainer.view = view; objectContainer.block = block; return objectContainer; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView(((ObjectContainer) object).view); } @Override public void unregisterDataSetObserver(DataSetObserver observer) { if (observer != null) { super.unregisterDataSetObserver(observer); } } }); AndroidUtilities.setViewPagerEdgeEffectColor(innerListView, 0xfff5f6f7); addView(innerListView); dotsContainer = new View(context) { @Override protected void onDraw(Canvas canvas) { if (currentBlock == null) { return; } int selected = innerListView.getCurrentItem(); for (int a = 0; a < currentBlock.items.size(); a++) { int cx = AndroidUtilities.dp(4) + AndroidUtilities.dp(13) * a; Drawable drawable = selected == a ? slideDotBigDrawable : slideDotDrawable; drawable.setBounds(cx - AndroidUtilities.dp(5), 0, cx + AndroidUtilities.dp(5), AndroidUtilities.dp(10)); drawable.draw(canvas); } } }; addView(dotsContainer); setWillNotDraw(false); } public void setBlock(TLRPC.TL_pageBlockSlideshow block) { currentBlock = block; lastCreatedWidth = 0; innerListView.setCurrentItem(0, false); adapter.notifyDataSetChanged(); requestLayout(); } @Override public boolean onTouchEvent(MotionEvent event) { return checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height; if (currentBlock != null) { height = AndroidUtilities.dp(310); innerListView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); int count = currentBlock.items.size(); dotsContainer.measure( MeasureSpec.makeMeasureSpec(count * AndroidUtilities.dp(7) + (count - 1) * AndroidUtilities.dp(6) + AndroidUtilities.dp(4), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(10), MeasureSpec.EXACTLY)); if (lastCreatedWidth != width) { textY = height + AndroidUtilities.dp(16); textLayout = createLayoutForText(null, currentBlock.caption, width - AndroidUtilities.dp(36), currentBlock); if (textLayout != null) { height += AndroidUtilities.dp(8) + textLayout.getHeight(); } //lastCreatedWidth = width; } height += AndroidUtilities.dp(16); } else { height = 1; } setMeasuredDimension(width, height); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { innerListView.layout(0, AndroidUtilities.dp(8), innerListView.getMeasuredWidth(), AndroidUtilities.dp(8) + innerListView.getMeasuredHeight()); int y = innerListView.getBottom() - AndroidUtilities.dp(7 + 16); int x = (right - left - dotsContainer.getMeasuredWidth()) / 2; dotsContainer.layout(x, y, x + dotsContainer.getMeasuredWidth(), y + dotsContainer.getMeasuredHeight()); } @Override protected void onDraw(Canvas canvas) { if (currentBlock == null) { return; } if (textLayout != null) { canvas.save(); canvas.translate(textX, textY); drawLayoutLink(canvas, textLayout); textLayout.draw(canvas); canvas.restore(); } } } private class BlockListCell extends View { private ArrayList<StaticLayout> textLayouts = new ArrayList<>(); private ArrayList<StaticLayout> textNumLayouts = new ArrayList<>(); private ArrayList<Integer> textYLayouts = new ArrayList<>(); private int lastCreatedWidth; private TLRPC.TL_pageBlockList currentBlock; public BlockListCell(Context context) { super(context); } public void setBlock(TLRPC.TL_pageBlockList block) { currentBlock = block; lastCreatedWidth = 0; requestLayout(); } @Override public boolean onTouchEvent(MotionEvent event) { int count = textLayouts.size(); int textX = AndroidUtilities.dp(36); for (int a = 0; a < count; a++) { StaticLayout textLayout = textLayouts.get(a); if (checkLayoutForLinks(event, this, textLayout, textX, textYLayouts.get(a))) { return true; } } return super.onTouchEvent(event); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = 0; if (currentBlock != null) { if (lastCreatedWidth != width) { textLayouts.clear(); textYLayouts.clear(); textNumLayouts.clear(); int count = currentBlock.items.size(); for (int a = 0; a < count; a++) { TLRPC.RichText item = currentBlock.items.get(a); height += AndroidUtilities.dp(8); StaticLayout textLayout = createLayoutForText(null, item, width - AndroidUtilities.dp(36 + 18), currentBlock); textYLayouts.add(height); textLayouts.add(textLayout); if (textLayout != null) { height += textLayout.getHeight(); } String num; if (currentBlock.ordered) { num = String.format(Locale.US, "%d.", a + 1); } else { num = ""; } textLayout = createLayoutForText(num, item, width - AndroidUtilities.dp(36 + 18), currentBlock); textNumLayouts.add(textLayout); } height += AndroidUtilities.dp(8); //lastCreatedWidth = width; } } else { height = 1; } setMeasuredDimension(width, height); } @Override protected void onDraw(Canvas canvas) { if (currentBlock == null) { return; } int count = textLayouts.size(); for (int a = 0; a < count; a++) { StaticLayout textLayout = textLayouts.get(a); StaticLayout textLayout2 = textNumLayouts.get(a); canvas.save(); canvas.translate(AndroidUtilities.dp(18), textYLayouts.get(a)); if (textLayout2 != null) { textLayout2.draw(canvas); } canvas.translate(AndroidUtilities.dp(18), 0); drawLayoutLink(canvas, textLayout); if (textLayout != null) { textLayout.draw(canvas); } canvas.restore(); } } } private class BlockHeaderCell extends View { private StaticLayout textLayout; private int lastCreatedWidth; private int textX = AndroidUtilities.dp(18); private int textY = AndroidUtilities.dp(8); private TLRPC.TL_pageBlockHeader currentBlock; public BlockHeaderCell(Context context) { super(context); } public void setBlock(TLRPC.TL_pageBlockHeader block) { currentBlock = block; lastCreatedWidth = 0; requestLayout(); } @Override public boolean onTouchEvent(MotionEvent event) { return checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = 0; if (currentBlock != null) { if (lastCreatedWidth != width) { textLayout = createLayoutForText(null, currentBlock.text, width - AndroidUtilities.dp(36), currentBlock); if (textLayout != null) { height += AndroidUtilities.dp(8 + 8) + textLayout.getHeight(); } //lastCreatedWidth = width; } } else { height = 1; } setMeasuredDimension(width, height); } @Override protected void onDraw(Canvas canvas) { if (currentBlock == null) { return; } if (textLayout != null) { canvas.save(); canvas.translate(textX, textY); drawLayoutLink(canvas, textLayout); textLayout.draw(canvas); canvas.restore(); } } } private class BlockDividerCell extends View { private RectF rect = new RectF(); public BlockDividerCell(Context context) { super(context); if (dividerPaint == null) { dividerPaint = new Paint(); dividerPaint.setColor(0xffcdd1d5); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), AndroidUtilities.dp(2 + 16)); } @Override protected void onDraw(Canvas canvas) { int width = getMeasuredWidth() / 3; rect.set(width, AndroidUtilities.dp(8), width * 2, AndroidUtilities.dp(10)); canvas.drawRoundRect(rect, AndroidUtilities.dp(1), AndroidUtilities.dp(1), dividerPaint); } } private class BlockSubtitleCell extends View { private StaticLayout textLayout; private int lastCreatedWidth; private int textX = AndroidUtilities.dp(18); private int textY = AndroidUtilities.dp(8); private TLRPC.TL_pageBlockSubtitle currentBlock; public BlockSubtitleCell(Context context) { super(context); } public void setBlock(TLRPC.TL_pageBlockSubtitle block) { currentBlock = block; lastCreatedWidth = 0; requestLayout(); } @Override public boolean onTouchEvent(MotionEvent event) { return checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = 0; if (currentBlock != null) { if (lastCreatedWidth != width) { textLayout = createLayoutForText(null, currentBlock.text, width - AndroidUtilities.dp(36), currentBlock); if (textLayout != null) { height += AndroidUtilities.dp(8 + 8) + textLayout.getHeight(); } //lastCreatedWidth = width; } } else { height = 1; } setMeasuredDimension(width, height); } @Override protected void onDraw(Canvas canvas) { if (currentBlock == null) { return; } if (textLayout != null) { canvas.save(); canvas.translate(textX, textY); drawLayoutLink(canvas, textLayout); textLayout.draw(canvas); canvas.restore(); } } } private class BlockPullquoteCell extends View { private StaticLayout textLayout; private StaticLayout textLayout2; private int textY2; private int lastCreatedWidth; private int textX = AndroidUtilities.dp(18); private int textY = AndroidUtilities.dp(8); private TLRPC.TL_pageBlockPullquote currentBlock; public BlockPullquoteCell(Context context) { super(context); } public void setBlock(TLRPC.TL_pageBlockPullquote block) { currentBlock = block; lastCreatedWidth = 0; requestLayout(); } @Override public boolean onTouchEvent(MotionEvent event) { return checkLayoutForLinks(event, this, textLayout, textX, textY) || checkLayoutForLinks(event, this, textLayout2, textX, textY2) || super.onTouchEvent(event); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = 0; if (currentBlock != null) { if (lastCreatedWidth != width) { textLayout = createLayoutForText(null, currentBlock.text, width - AndroidUtilities.dp(36), currentBlock); if (textLayout != null) { height += AndroidUtilities.dp(8) + textLayout.getHeight(); } textLayout2 = createLayoutForText(null, currentBlock.caption, width - AndroidUtilities.dp(36), currentBlock); if (textLayout2 != null) { textY2 = height + AndroidUtilities.dp(2); height += AndroidUtilities.dp(8) + textLayout2.getHeight(); } if (height != 0) { height += AndroidUtilities.dp(8); } //lastCreatedWidth = width; } } else { height = 1; } setMeasuredDimension(width, height); } @Override protected void onDraw(Canvas canvas) { if (currentBlock == null) { return; } if (textLayout != null) { canvas.save(); canvas.translate(textX, textY); drawLayoutLink(canvas, textLayout); textLayout.draw(canvas); canvas.restore(); } if (textLayout2 != null) { canvas.save(); canvas.translate(textX, textY2); drawLayoutLink(canvas, textLayout2); textLayout2.draw(canvas); canvas.restore(); } } } private class BlockBlockquoteCell extends View { private StaticLayout textLayout; private StaticLayout textLayout2; private int textY2; private int lastCreatedWidth; private int textX = AndroidUtilities.dp(18 + 14); private int textY = AndroidUtilities.dp(8); private TLRPC.TL_pageBlockBlockquote currentBlock; public BlockBlockquoteCell(Context context) { super(context); } public void setBlock(TLRPC.TL_pageBlockBlockquote block) { currentBlock = block; lastCreatedWidth = 0; requestLayout(); } @Override public boolean onTouchEvent(MotionEvent event) { return checkLayoutForLinks(event, this, textLayout, textX, textY) || checkLayoutForLinks(event, this, textLayout2, textX, textY2) || super.onTouchEvent(event); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = 0; if (currentBlock != null) { if (lastCreatedWidth != width) { textLayout = createLayoutForText(null, currentBlock.text, width - AndroidUtilities.dp(36 + 14), currentBlock); if (textLayout != null) { height += AndroidUtilities.dp(8) + textLayout.getHeight(); } textLayout2 = createLayoutForText(null, currentBlock.caption, width - AndroidUtilities.dp(36 + 14), currentBlock); if (textLayout2 != null) { textY2 = height + AndroidUtilities.dp(2); height += AndroidUtilities.dp(8) + textLayout2.getHeight(); } if (height != 0) { height += AndroidUtilities.dp(8); } //lastCreatedWidth = width; } } else { height = 1; } setMeasuredDimension(width, height); } @Override protected void onDraw(Canvas canvas) { if (currentBlock == null) { return; } if (textLayout != null) { canvas.save(); canvas.translate(textX, textY); drawLayoutLink(canvas, textLayout); textLayout.draw(canvas); canvas.restore(); } if (textLayout2 != null) { canvas.save(); canvas.translate(textX, textY2); drawLayoutLink(canvas, textLayout2); textLayout2.draw(canvas); canvas.restore(); } canvas.drawRect(AndroidUtilities.dp(18), AndroidUtilities.dp(6), AndroidUtilities.dp(20), getMeasuredHeight() - AndroidUtilities.dp(6), quoteLinePaint); } } private class BlockPhotoCell extends View { private StaticLayout textLayout; private ImageReceiver imageView; private int lastCreatedWidth; private int currentType; private boolean isFirst; private boolean isLast; private int textX; private int textY; private boolean photoPressed; private TLRPC.TL_pageBlockPhoto currentBlock; private TLRPC.PageBlock parentBlock; public BlockPhotoCell(Context context, int type) { super(context); imageView = new ImageReceiver(this); currentType = type; //imageView.setAspectFit(currentType == 1); } public void setBlock(TLRPC.TL_pageBlockPhoto block, boolean first, boolean last) { parentBlock = null; currentBlock = block; lastCreatedWidth = 0; isFirst = first; isLast = last; requestLayout(); } public void setParentBlock(TLRPC.PageBlock block) { parentBlock = block; } @Override public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); float y = event.getY(); if (event.getAction() == MotionEvent.ACTION_DOWN && imageView.isInsideImage(x, y)) { photoPressed = true; } else if (event.getAction() == MotionEvent.ACTION_UP && photoPressed) { photoPressed = false; openPhoto(currentBlock); } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { photoPressed = false; } return photoPressed || checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = 0; if (currentType == 1) { width = listView.getWidth(); height = ((View) getParent()).getMeasuredHeight(); } else if (currentType == 2) { height = width; } if (currentBlock != null) { TLRPC.Photo photo = getPhotoWithId(currentBlock.photo_id); int photoWidth = width; int photoX; int textWidth; if (currentType == 0 && currentBlock.level > 0) { textX = photoX = AndroidUtilities.dp(14 * currentBlock.level) + AndroidUtilities.dp(18); photoWidth -= photoX + AndroidUtilities.dp(18); textWidth = photoWidth; } else { photoX = 0; textX = AndroidUtilities.dp(18); textWidth = width - AndroidUtilities.dp(36); } if (photo != null) { TLRPC.PhotoSize image = FileLoader.getClosestPhotoSizeWithSize(photo.sizes, AndroidUtilities.getPhotoSize()); TLRPC.PhotoSize thumb = FileLoader.getClosestPhotoSizeWithSize(photo.sizes, 80, true); if (image == thumb) { thumb = null; } if (currentType == 0) { float scale; scale = photoWidth / (float) image.w; height = (int) (scale * image.h); if (parentBlock instanceof TLRPC.TL_pageBlockCover) { height = Math.min(height, photoWidth); } else { int maxHeight = (int) ((Math.max(listView.getMeasuredWidth(), listView.getMeasuredHeight()) - AndroidUtilities.dp(56)) * 0.9f); if (height > maxHeight) { height = maxHeight; scale = height / (float) image.h; photoWidth = (int) (scale * image.w); photoX += (width - photoX - photoWidth) / 2; } } } imageView.setImageCoords(photoX, (isFirst || currentType == 1 || currentType == 2 || currentBlock.level > 0) ? 0 : AndroidUtilities.dp(8), photoWidth, height); String filter; if (currentType == 0) { filter = null; } else { filter = String.format(Locale.US, "%d_%d", photoWidth, height); } imageView.setImage(image.location, filter, thumb != null ? thumb.location : null, thumb != null ? "80_80_b" : null, image.size, null, true); } if (currentType == 0 && lastCreatedWidth != width) { textLayout = createLayoutForText(null, currentBlock.caption, textWidth, currentBlock); if (textLayout != null) { height += AndroidUtilities.dp(8) + textLayout.getHeight(); } //lastCreatedWidth = width; } if (!isFirst && currentType == 0 && currentBlock.level <= 0) { height += AndroidUtilities.dp(8); } if (currentType != 2) { height += AndroidUtilities.dp(8); } } else { height = 1; } setMeasuredDimension(width, height); } @Override protected void onDraw(Canvas canvas) { if (currentBlock == null) { return; } imageView.draw(canvas); if (textLayout != null) { canvas.save(); canvas.translate(textX, textY = imageView.getImageY() + imageView.getImageHeight() + AndroidUtilities.dp(8)); drawLayoutLink(canvas, textLayout); textLayout.draw(canvas); canvas.restore(); } if (currentBlock.level > 0) { canvas.drawRect(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(20), getMeasuredHeight() - (currentBlock.bottom ? AndroidUtilities.dp(6) : 0), quoteLinePaint); } } } private class BlockAuthorDateCell extends View { private StaticLayout textLayout; private int lastCreatedWidth; private int textX = AndroidUtilities.dp(18); private int textY = AndroidUtilities.dp(8); private TLRPC.TL_pageBlockAuthorDate currentBlock; public BlockAuthorDateCell(Context context) { super(context); } public void setBlock(TLRPC.TL_pageBlockAuthorDate block) { currentBlock = block; lastCreatedWidth = 0; requestLayout(); } @Override public boolean onTouchEvent(MotionEvent event) { return checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = 0; if (currentBlock != null) { if (lastCreatedWidth != width) { CharSequence text; CharSequence author = getText(currentBlock.author, currentBlock.author, currentBlock); //TODO support styles Spannable spannableAuthor; MetricAffectingSpan spans[]; if (author instanceof Spannable) { spannableAuthor = (Spannable) author; spans = spannableAuthor.getSpans(0, author.length(), MetricAffectingSpan.class); } else { spannableAuthor = null; spans = null; } if (currentBlock.published_date != 0 && !TextUtils.isEmpty(author)) { text = LocaleController.formatString("ArticleDateByAuthor", R.string.ArticleDateByAuthor, LocaleController.getInstance().chatFullDate .format((long) currentBlock.published_date * 1000), author); } else if (!TextUtils.isEmpty(author)) { text = LocaleController.formatString("ArticleByAuthor", R.string.ArticleByAuthor, author); } else { text = LocaleController.getInstance().chatFullDate .format((long) currentBlock.published_date * 1000); } try { if (spans != null && spans.length > 0) { int idx = TextUtils.indexOf(text, author); if (idx != -1) { Spannable spannable = Spannable.Factory.getInstance().newSpannable(author); text = spannable; for (int a = 0; a < spans.length; a++) { spannable.setSpan(spans[a], idx + spannableAuthor.getSpanStart(spans[a]), idx + spannableAuthor.getSpanEnd(spans[a]), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } } } catch (Exception e) { FileLog.e(e); } textLayout = createLayoutForText(text, null, width - AndroidUtilities.dp(36), currentBlock); if (textLayout != null) { height += AndroidUtilities.dp(8 + 8) + textLayout.getHeight(); } //lastCreatedWidth = width; } } else { height = 1; } setMeasuredDimension(width, height); } @Override protected void onDraw(Canvas canvas) { if (currentBlock == null) { return; } if (textLayout != null) { canvas.save(); canvas.translate(textX, textY); drawLayoutLink(canvas, textLayout); textLayout.draw(canvas); canvas.restore(); } } } private class BlockTitleCell extends View { private StaticLayout textLayout; private int lastCreatedWidth; private TLRPC.TL_pageBlockTitle currentBlock; private int textX = AndroidUtilities.dp(18); private int textY; public BlockTitleCell(Context context) { super(context); } public void setBlock(TLRPC.TL_pageBlockTitle block) { currentBlock = block; lastCreatedWidth = 0; requestLayout(); } @Override public boolean onTouchEvent(MotionEvent event) { return checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = 0; if (currentBlock != null) { if (lastCreatedWidth != width) { textLayout = createLayoutForText(null, currentBlock.text, width - AndroidUtilities.dp(36), currentBlock); if (textLayout != null) { height += AndroidUtilities.dp(8 + 8) + textLayout.getHeight(); } if (currentBlock.first) { height += AndroidUtilities.dp(8); textY = AndroidUtilities.dp(16); } else { textY = AndroidUtilities.dp(8); } //lastCreatedWidth = width; } } else { height = 1; } setMeasuredDimension(width, height); } @Override protected void onDraw(Canvas canvas) { if (currentBlock == null) { return; } if (textLayout != null) { canvas.save(); canvas.translate(textX, textY); drawLayoutLink(canvas, textLayout); textLayout.draw(canvas); canvas.restore(); } } } private class BlockFooterCell extends View { private StaticLayout textLayout; private int lastCreatedWidth; private int textX = AndroidUtilities.dp(18); private int textY = AndroidUtilities.dp(8); private TLRPC.TL_pageBlockFooter currentBlock; public BlockFooterCell(Context context) { super(context); } public void setBlock(TLRPC.TL_pageBlockFooter block) { currentBlock = block; lastCreatedWidth = 0; requestLayout(); } @Override public boolean onTouchEvent(MotionEvent event) { return checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = 0; if (currentBlock != null) { if (currentBlock.level == 0) { textY = AndroidUtilities.dp(8); textX = AndroidUtilities.dp(18); } else { textY = 0; textX = AndroidUtilities.dp(18 + 14 * currentBlock.level); } if (lastCreatedWidth != width) { textLayout = createLayoutForText(null, currentBlock.text, width - AndroidUtilities.dp(18) - textX, currentBlock); if (textLayout != null) { height = textLayout.getHeight(); if (currentBlock.level > 0) { height += AndroidUtilities.dp(8); } else { height += AndroidUtilities.dp(8 + 8); } } //lastCreatedWidth = width; } } else { height = 1; } setMeasuredDimension(width, height); } @Override protected void onDraw(Canvas canvas) { if (currentBlock == null) { return; } if (textLayout != null) { canvas.save(); canvas.translate(textX, textY); drawLayoutLink(canvas, textLayout); textLayout.draw(canvas); canvas.restore(); } if (currentBlock.level > 0) { canvas.drawRect(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(20), getMeasuredHeight() - (currentBlock.bottom ? AndroidUtilities.dp(6) : 0), quoteLinePaint); } } } private class BlockPreformattedCell extends View { private StaticLayout textLayout; private int lastCreatedWidth; private TLRPC.TL_pageBlockPreformatted currentBlock; public BlockPreformattedCell(Context context) { super(context); } public void setBlock(TLRPC.TL_pageBlockPreformatted block) { currentBlock = block; lastCreatedWidth = 0; requestLayout(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = 0; if (currentBlock != null) { if (lastCreatedWidth != width) { textLayout = createLayoutForText(null, currentBlock.text, width - AndroidUtilities.dp(24), currentBlock); if (textLayout != null) { height += AndroidUtilities.dp(8 + 8 + 8 + 8) + textLayout.getHeight(); } //lastCreatedWidth = width; } } else { height = 1; } setMeasuredDimension(width, height); } @Override protected void onDraw(Canvas canvas) { if (currentBlock == null) { return; } canvas.drawRect(0, AndroidUtilities.dp(8), getMeasuredWidth(), getMeasuredHeight() - AndroidUtilities.dp(8), preformattedBackgroundPaint); if (textLayout != null) { canvas.save(); canvas.translate(AndroidUtilities.dp(12), AndroidUtilities.dp(16)); textLayout.draw(canvas); canvas.restore(); } } } private class BlockSubheaderCell extends View { private StaticLayout textLayout; private int lastCreatedWidth; private int textX = AndroidUtilities.dp(18); private int textY = AndroidUtilities.dp(8); private TLRPC.TL_pageBlockSubheader currentBlock; public BlockSubheaderCell(Context context) { super(context); } public void setBlock(TLRPC.TL_pageBlockSubheader block) { currentBlock = block; lastCreatedWidth = 0; requestLayout(); } @Override public boolean onTouchEvent(MotionEvent event) { return checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = 0; if (currentBlock != null) { if (lastCreatedWidth != width) { textLayout = createLayoutForText(null, currentBlock.text, width - AndroidUtilities.dp(36), currentBlock); if (textLayout != null) { height += AndroidUtilities.dp(8 + 8) + textLayout.getHeight(); } //lastCreatedWidth = width; } } else { height = 1; } setMeasuredDimension(width, height); } @Override protected void onDraw(Canvas canvas) { if (currentBlock == null) { return; } if (textLayout != null) { canvas.save(); canvas.translate(textX, textY); drawLayoutLink(canvas, textLayout); textLayout.draw(canvas); canvas.restore(); } } } //------------ photo viewer private int coords[] = new int[2]; private boolean isPhotoVisible; private ActionBar actionBar; private boolean isActionBarVisible = true; private static Drawable[] progressDrawables; private ClippingImageView animatingImageView; private FrameLayout bottomLayout; private ActionBarMenuItem menuItem; private PhotoBackgroundDrawable photoBackgroundDrawable = new PhotoBackgroundDrawable(0xff000000); private Paint blackPaint = new Paint(); private RadialProgressView radialProgressViews[] = new RadialProgressView[3]; private AnimatorSet currentActionBarAnimation; private TextView captionTextView; private TextView captionTextViewOld; private TextView captionTextViewNew; private AnimatedFileDrawable currentAnimation; private AspectRatioFrameLayout aspectRatioFrameLayout; private TextureView videoTextureView; private VideoPlayer videoPlayer; private FrameLayout videoPlayerControlFrameLayout; private ImageView videoPlayButton; private TextView videoPlayerTime; private SeekBar videoPlayerSeekbar; private boolean textureUploaded; private boolean videoCrossfadeStarted; private float videoCrossfadeAlpha; private long videoCrossfadeAlphaLastTime; private boolean isPlaying; private Runnable updateProgressRunnable = new Runnable() { @Override public void run() { if (videoPlayer != null && videoPlayerSeekbar != null) { if (!videoPlayerSeekbar.isDragging()) { float progress = videoPlayer.getCurrentPosition() / (float) videoPlayer.getDuration(); videoPlayerSeekbar.setProgress(progress); videoPlayerControlFrameLayout.invalidate(); updateVideoPlayerTime(); } } if (isPlaying) { AndroidUtilities.runOnUIThread(updateProgressRunnable, 100); } } }; private float animationValues[][] = new float[2][8]; private int photoAnimationInProgress; private long photoTransitionAnimationStartTime; private Runnable photoAnimationEndRunnable; private PlaceProviderObject showAfterAnimation; private PlaceProviderObject hideAfterAnimation; private boolean disableShowCheck; private ImageReceiver leftImage = new ImageReceiver(); private ImageReceiver centerImage = new ImageReceiver(); private ImageReceiver rightImage = new ImageReceiver(); private int currentIndex; private TLRPC.PageBlock currentMedia; private String currentFileNames[] = new String[3]; private PlaceProviderObject currentPlaceObject; private Bitmap currentThumb; private boolean wasLayout; private boolean dontResetZoomOnFirstLayout; private boolean draggingDown; private float dragY; private float translationX; private float translationY; private float scale = 1; private float animateToX; private float animateToY; private float animateToScale; private float animationValue; private int currentRotation; private long animationStartTime; private AnimatorSet imageMoveAnimation; private GestureDetector gestureDetector; private DecelerateInterpolator interpolator = new DecelerateInterpolator(1.5f); private float pinchStartDistance; private float pinchStartScale = 1; private float pinchCenterX; private float pinchCenterY; private float pinchStartX; private float pinchStartY; private float moveStartX; private float moveStartY; private float minX; private float maxX; private float minY; private float maxY; private boolean canZoom = true; private boolean changingPage; private boolean zooming; private boolean moving; private boolean doubleTap; private boolean invalidCoords; private boolean canDragDown = true; private boolean zoomAnimation; private boolean discardTap; private int switchImageAfterAnimation; private VelocityTracker velocityTracker; private Scroller scroller; private ArrayList<TLRPC.PageBlock> imagesArr = new ArrayList<>(); private final static int gallery_menu_save = 1; private final static int gallery_menu_share = 2; private final static int gallery_menu_openin = 3; private static DecelerateInterpolator decelerateInterpolator; private static Paint progressPaint; private static Paint dotsPaint; private class PhotoBackgroundDrawable extends ColorDrawable { private Runnable drawRunnable; public PhotoBackgroundDrawable(int color) { super(color); } @Override public void setAlpha(int alpha) { if (parentActivity instanceof LaunchActivity) { ((LaunchActivity) parentActivity).drawerLayoutContainer .setAllowDrawContent(!isPhotoVisible || alpha != 255); } super.setAlpha(alpha); } @Override public void draw(Canvas canvas) { super.draw(canvas); if (getAlpha() != 0) { if (drawRunnable != null) { drawRunnable.run(); drawRunnable = null; } } } } private class RadialProgressView { private long lastUpdateTime = 0; private float radOffset = 0; private float currentProgress = 0; private float animationProgressStart = 0; private long currentProgressTime = 0; private float animatedProgressValue = 0; private RectF progressRect = new RectF(); private int backgroundState = -1; private View parent = null; private int size = AndroidUtilities.dp(64); private int previousBackgroundState = -2; private float animatedAlphaValue = 1.0f; private float alpha = 1.0f; private float scale = 1.0f; public RadialProgressView(Context context, View parentView) { if (decelerateInterpolator == null) { decelerateInterpolator = new DecelerateInterpolator(1.5f); progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); progressPaint.setStyle(Paint.Style.STROKE); progressPaint.setStrokeCap(Paint.Cap.ROUND); progressPaint.setStrokeWidth(AndroidUtilities.dp(3)); progressPaint.setColor(0xffffffff); } parent = parentView; } private void updateAnimation() { long newTime = System.currentTimeMillis(); long dt = newTime - lastUpdateTime; lastUpdateTime = newTime; if (animatedProgressValue != 1) { radOffset += 360 * dt / 3000.0f; float progressDiff = currentProgress - animationProgressStart; if (progressDiff > 0) { currentProgressTime += dt; if (currentProgressTime >= 300) { animatedProgressValue = currentProgress; animationProgressStart = currentProgress; currentProgressTime = 0; } else { animatedProgressValue = animationProgressStart + progressDiff * decelerateInterpolator.getInterpolation(currentProgressTime / 300.0f); } } parent.invalidate(); } if (animatedProgressValue >= 1 && previousBackgroundState != -2) { animatedAlphaValue -= dt / 200.0f; if (animatedAlphaValue <= 0) { animatedAlphaValue = 0.0f; previousBackgroundState = -2; } parent.invalidate(); } } public void setProgress(float value, boolean animated) { if (!animated) { animatedProgressValue = value; animationProgressStart = value; } else { animationProgressStart = animatedProgressValue; } currentProgress = value; currentProgressTime = 0; } public void setBackgroundState(int state, boolean animated) { lastUpdateTime = System.currentTimeMillis(); if (animated && backgroundState != state) { previousBackgroundState = backgroundState; animatedAlphaValue = 1.0f; } else { previousBackgroundState = -2; } backgroundState = state; parent.invalidate(); } public void setAlpha(float value) { alpha = value; } public void setScale(float value) { scale = value; } public void onDraw(Canvas canvas) { int sizeScaled = (int) (size * scale); int x = (getContainerViewWidth() - sizeScaled) / 2; int y = (getContainerViewHeight() - sizeScaled) / 2; if (previousBackgroundState >= 0 && previousBackgroundState < 4) { Drawable drawable = progressDrawables[previousBackgroundState]; if (drawable != null) { drawable.setAlpha((int) (255 * animatedAlphaValue * alpha)); drawable.setBounds(x, y, x + sizeScaled, y + sizeScaled); drawable.draw(canvas); } } if (backgroundState >= 0 && backgroundState < 4) { Drawable drawable = progressDrawables[backgroundState]; if (drawable != null) { if (previousBackgroundState != -2) { drawable.setAlpha((int) (255 * (1.0f - animatedAlphaValue) * alpha)); } else { drawable.setAlpha((int) (255 * alpha)); } drawable.setBounds(x, y, x + sizeScaled, y + sizeScaled); drawable.draw(canvas); } } if (backgroundState == 0 || backgroundState == 1 || previousBackgroundState == 0 || previousBackgroundState == 1) { int diff = AndroidUtilities.dp(4); if (previousBackgroundState != -2) { progressPaint.setAlpha((int) (255 * animatedAlphaValue * alpha)); } else { progressPaint.setAlpha((int) (255 * alpha)); } progressRect.set(x + diff, y + diff, x + sizeScaled - diff, y + sizeScaled - diff); canvas.drawArc(progressRect, -90 + radOffset, Math.max(4, 360 * animatedProgressValue), false, progressPaint); updateAnimation(); } } } public static class PlaceProviderObject { public ImageReceiver imageReceiver; public int viewX; public int viewY; public View parentView; public Bitmap thumb; public int index; public int size; public int radius; public int clipBottomAddition; public int clipTopAddition; public float scale = 1.0f; } private void onSharePressed() { if (parentActivity == null || currentMedia == null) { return; } try { File f = getMediaFile(currentIndex); if (f != null && f.exists()) { Intent intent = new Intent(Intent.ACTION_SEND); if (isMediaVideo(currentIndex)) { intent.setType("video/mp4"); } else { intent.setType("image/jpeg"); } intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f)); parentActivity.startActivityForResult( Intent.createChooser(intent, LocaleController.getString("ShareFile", R.string.ShareFile)), 500); } else { AlertDialog.Builder builder = new AlertDialog.Builder(parentActivity); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); builder.setMessage(LocaleController.getString("PleaseDownload", R.string.PleaseDownload)); showDialog(builder.create()); } } catch (Exception e) { FileLog.e(e); } } private void setScaleToFill() { float bitmapWidth = centerImage.getBitmapWidth(); float containerWidth = getContainerViewWidth(); float bitmapHeight = centerImage.getBitmapHeight(); float containerHeight = getContainerViewHeight(); float scaleFit = Math.min(containerHeight / bitmapHeight, containerWidth / bitmapWidth); float width = (int) (bitmapWidth * scaleFit); float height = (int) (bitmapHeight * scaleFit); scale = Math.max(containerWidth / width, containerHeight / height); updateMinMax(scale); } private void updateVideoPlayerTime() { String newText; if (videoPlayer == null) { newText = "00:00 / 00:00"; } else { long current = videoPlayer.getCurrentPosition() / 1000; long total = videoPlayer.getDuration(); total /= 1000; if (total != C.TIME_UNSET && current != C.TIME_UNSET) { newText = String.format("%02d:%02d / %02d:%02d", current / 60, current % 60, total / 60, total % 60); } else { newText = "00:00 / 00:00"; } } if (!TextUtils.equals(videoPlayerTime.getText(), newText)) { videoPlayerTime.setText(newText); } } @SuppressLint("NewApi") private void preparePlayer(File file, boolean playWhenReady) { if (parentActivity == null) { return; } releasePlayer(); if (videoTextureView == null) { aspectRatioFrameLayout = new AspectRatioFrameLayout(parentActivity); aspectRatioFrameLayout.setVisibility(View.INVISIBLE); photoContainerView.addView(aspectRatioFrameLayout, 0, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER)); videoTextureView = new TextureView(parentActivity); videoTextureView.setOpaque(false); aspectRatioFrameLayout.addView(videoTextureView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER)); } textureUploaded = false; videoCrossfadeStarted = false; videoTextureView.setAlpha(videoCrossfadeAlpha = 0.0f); videoPlayButton.setImageResource(R.drawable.inline_video_play); if (videoPlayer == null) { videoPlayer = new VideoPlayer(); videoPlayer.setTextureView(videoTextureView); videoPlayer.setDelegate(new VideoPlayer.VideoPlayerDelegate() { @Override public void onStateChanged(boolean playWhenReady, int playbackState) { if (videoPlayer == null) { return; } if (playbackState != ExoPlayer.STATE_ENDED && playbackState != ExoPlayer.STATE_IDLE) { try { parentActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } catch (Exception e) { FileLog.e(e); } } else { try { parentActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } catch (Exception e) { FileLog.e(e); } } if (playbackState == ExoPlayer.STATE_READY && aspectRatioFrameLayout.getVisibility() != View.VISIBLE) { aspectRatioFrameLayout.setVisibility(View.VISIBLE); } if (videoPlayer.isPlaying() && playbackState != ExoPlayer.STATE_ENDED) { if (!isPlaying) { isPlaying = true; videoPlayButton.setImageResource(R.drawable.inline_video_pause); AndroidUtilities.runOnUIThread(updateProgressRunnable); } } else if (isPlaying) { isPlaying = false; videoPlayButton.setImageResource(R.drawable.inline_video_play); AndroidUtilities.cancelRunOnUIThread(updateProgressRunnable); if (playbackState == ExoPlayer.STATE_ENDED) { if (!videoPlayerSeekbar.isDragging()) { videoPlayerSeekbar.setProgress(0.0f); videoPlayerControlFrameLayout.invalidate(); videoPlayer.seekTo(0); videoPlayer.pause(); } } } updateVideoPlayerTime(); } @Override public void onError(Exception e) { FileLog.e(e); } @Override public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { if (aspectRatioFrameLayout != null) { if (unappliedRotationDegrees == 90 || unappliedRotationDegrees == 270) { int temp = width; width = height; height = temp; } aspectRatioFrameLayout.setAspectRatio( height == 0 ? 1 : (width * pixelWidthHeightRatio) / height, unappliedRotationDegrees); } } @Override public void onRenderedFirstFrame() { if (!textureUploaded) { textureUploaded = true; containerView.invalidate(); } } @Override public boolean onSurfaceDestroyed(SurfaceTexture surfaceTexture) { return false; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { } }); long duration; if (videoPlayer != null) { duration = videoPlayer.getDuration(); if (duration == C.TIME_UNSET) { duration = 0; } } else { duration = 0; } duration /= 1000; int size = (int) Math.ceil(videoPlayerTime.getPaint().measureText(String.format("%02d:%02d / %02d:%02d", duration / 60, duration % 60, duration / 60, duration % 60))); } videoPlayer.preparePlayer(Uri.fromFile(file), "other"); bottomLayout.setVisibility(View.VISIBLE); videoPlayer.setPlayWhenReady(playWhenReady); } private void releasePlayer() { if (videoPlayer != null) { videoPlayer.releasePlayer(); videoPlayer = null; } try { parentActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } catch (Exception e) { FileLog.e(e); } if (aspectRatioFrameLayout != null) { photoContainerView.removeView(aspectRatioFrameLayout); aspectRatioFrameLayout = null; } if (videoTextureView != null) { videoTextureView = null; } if (isPlaying) { isPlaying = false; videoPlayButton.setImageResource(R.drawable.inline_video_play); AndroidUtilities.cancelRunOnUIThread(updateProgressRunnable); } bottomLayout.setVisibility(View.GONE); } private void toggleActionBar(boolean show, final boolean animated) { if (show) { actionBar.setVisibility(View.VISIBLE); if (videoPlayer != null) { bottomLayout.setVisibility(View.VISIBLE); } if (captionTextView.getTag() != null) { captionTextView.setVisibility(View.VISIBLE); } } isActionBarVisible = show; actionBar.setEnabled(show); bottomLayout.setEnabled(show); if (animated) { ArrayList<Animator> arrayList = new ArrayList<>(); arrayList.add(ObjectAnimator.ofFloat(actionBar, "alpha", show ? 1.0f : 0.0f)); arrayList.add(ObjectAnimator.ofFloat(bottomLayout, "alpha", show ? 1.0f : 0.0f)); if (captionTextView.getTag() != null) { arrayList.add(ObjectAnimator.ofFloat(captionTextView, "alpha", show ? 1.0f : 0.0f)); } currentActionBarAnimation = new AnimatorSet(); currentActionBarAnimation.playTogether(arrayList); if (!show) { currentActionBarAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (currentActionBarAnimation != null && currentActionBarAnimation.equals(animation)) { actionBar.setVisibility(View.GONE); if (videoPlayer != null) { bottomLayout.setVisibility(View.GONE); } if (captionTextView.getTag() != null) { captionTextView.setVisibility(View.INVISIBLE); } currentActionBarAnimation = null; } } }); } currentActionBarAnimation.setDuration(200); currentActionBarAnimation.start(); } else { actionBar.setAlpha(show ? 1.0f : 0.0f); bottomLayout.setAlpha(show ? 1.0f : 0.0f); if (captionTextView.getTag() != null) { captionTextView.setAlpha(show ? 1.0f : 0.0f); } if (!show) { actionBar.setVisibility(View.GONE); if (videoPlayer != null) { bottomLayout.setVisibility(View.GONE); } if (captionTextView.getTag() != null) { captionTextView.setVisibility(View.INVISIBLE); } } } } private String getFileName(int index) { TLObject media = getMedia(index); if (media instanceof TLRPC.Photo) { media = FileLoader.getClosestPhotoSizeWithSize(((TLRPC.Photo) media).sizes, AndroidUtilities.getPhotoSize()); } return FileLoader.getAttachFileName(media); } private TLObject getMedia(int index) { if (imagesArr.isEmpty() || index >= imagesArr.size() || index < 0) { return null; } TLRPC.PageBlock block = imagesArr.get(index); if (block.photo_id != 0) { return getPhotoWithId(block.photo_id); } else if (block.video_id != 0) { return getDocumentWithId(block.video_id); } return null; } private File getMediaFile(int index) { if (imagesArr.isEmpty() || index >= imagesArr.size() || index < 0) { return null; } TLRPC.PageBlock block = imagesArr.get(index); if (block.photo_id != 0) { TLRPC.Photo photo = getPhotoWithId(block.photo_id); if (photo != null) { TLRPC.PhotoSize sizeFull = FileLoader.getClosestPhotoSizeWithSize(photo.sizes, AndroidUtilities.getPhotoSize()); if (sizeFull != null) { return FileLoader.getPathToAttach(sizeFull, true); } } } else if (block.video_id != 0) { TLRPC.Document document = getDocumentWithId(block.video_id); if (document != null) { return FileLoader.getPathToAttach(document, true); } } return null; } private boolean isVideoBlock(TLRPC.PageBlock block) { if (block != null && block.video_id != 0) { TLRPC.Document document = getDocumentWithId(block.video_id); if (document != null) { return MessageObject.isVideoDocument(document); } } return false; } private boolean isMediaVideo(int index) { return !(imagesArr.isEmpty() || index >= imagesArr.size() || index < 0) && isVideoBlock(imagesArr.get(index)); } private TLRPC.FileLocation getFileLocation(int index, int size[]) { if (index < 0 || index >= imagesArr.size()) { return null; } TLObject media = getMedia(index); if (media instanceof TLRPC.Photo) { TLRPC.Photo photo = (TLRPC.Photo) media; TLRPC.PhotoSize sizeFull = FileLoader.getClosestPhotoSizeWithSize(photo.sizes, AndroidUtilities.getPhotoSize()); if (sizeFull != null) { size[0] = sizeFull.size; if (size[0] == 0) { size[0] = -1; } return sizeFull.location; } else { size[0] = -1; } } else if (media instanceof TLRPC.Document) { TLRPC.Document document = (TLRPC.Document) media; if (document.thumb != null) { size[0] = document.thumb.size; if (size[0] == 0) { size[0] = -1; } return document.thumb.location; } } return null; } private void onPhotoShow(int index, final PlaceProviderObject object) { currentIndex = -1; currentFileNames[0] = null; currentFileNames[1] = null; currentFileNames[2] = null; currentThumb = object != null ? object.thumb : null; menuItem.setVisibility(View.VISIBLE); menuItem.hideSubItem(gallery_menu_openin); actionBar.setTranslationY(0); captionTextView.setTag(null); captionTextView.setVisibility(View.INVISIBLE); for (int a = 0; a < 3; a++) { if (radialProgressViews[a] != null) { radialProgressViews[a].setBackgroundState(-1, false); } } setImageIndex(index, true); if (currentMedia != null && isMediaVideo(currentIndex)) { onActionClick(false); } } private void setImages() { if (photoAnimationInProgress == 0) { setIndexToImage(centerImage, currentIndex); setIndexToImage(rightImage, currentIndex + 1); setIndexToImage(leftImage, currentIndex - 1); } } private void setImageIndex(int index, boolean init) { if (currentIndex == index) { return; } if (!init) { currentThumb = null; } currentFileNames[0] = getFileName(index); currentFileNames[1] = getFileName(index + 1); currentFileNames[2] = getFileName(index - 1); int prevIndex = currentIndex; currentIndex = index; boolean isVideo = false; boolean sameImage = false; if (!imagesArr.isEmpty()) { if (currentIndex < 0 || currentIndex >= imagesArr.size()) { closePhoto(false); return; } TLRPC.PageBlock newMedia = imagesArr.get(currentIndex); sameImage = currentMedia != null && currentMedia == newMedia; currentMedia = newMedia; isVideo = isMediaVideo(currentIndex); if (isVideo) { menuItem.showSubItem(gallery_menu_openin); } setCurrentCaption(getText(currentMedia.caption, currentMedia.caption, currentMedia)); if (currentAnimation != null) { menuItem.setVisibility(View.GONE); menuItem.hideSubItem(gallery_menu_save); actionBar.setTitle(LocaleController.getString("AttachGif", R.string.AttachGif)); } else { menuItem.setVisibility(View.VISIBLE); if (imagesArr.size() == 1) { if (isVideo) { actionBar.setTitle(LocaleController.getString("AttachVideo", R.string.AttachVideo)); } else { actionBar.setTitle(LocaleController.getString("AttachPhoto", R.string.AttachPhoto)); } } else { actionBar.setTitle( LocaleController.formatString("Of", R.string.Of, currentIndex + 1, imagesArr.size())); } menuItem.showSubItem(gallery_menu_save); } } int count = listView.getChildCount(); for (int a = 0; a < count; a++) { View child = listView.getChildAt(a); if (child instanceof BlockSlideshowCell) { BlockSlideshowCell cell = (BlockSlideshowCell) child; int idx = cell.currentBlock.items.indexOf(currentMedia); if (idx != -1) { cell.innerListView.setCurrentItem(idx, false); break; } } } if (currentPlaceObject != null) { if (photoAnimationInProgress == 0) { currentPlaceObject.imageReceiver.setVisible(true, true); } else { showAfterAnimation = currentPlaceObject; } } currentPlaceObject = getPlaceForPhoto(currentMedia); if (currentPlaceObject != null) { if (photoAnimationInProgress == 0) { currentPlaceObject.imageReceiver.setVisible(false, true); } else { hideAfterAnimation = currentPlaceObject; } } if (!sameImage) { draggingDown = false; translationX = 0; translationY = 0; scale = 1; animateToX = 0; animateToY = 0; animateToScale = 1; animationStartTime = 0; imageMoveAnimation = null; if (aspectRatioFrameLayout != null) { aspectRatioFrameLayout.setVisibility(View.INVISIBLE); } releasePlayer(); pinchStartDistance = 0; pinchStartScale = 1; pinchCenterX = 0; pinchCenterY = 0; pinchStartX = 0; pinchStartY = 0; moveStartX = 0; moveStartY = 0; zooming = false; moving = false; doubleTap = false; invalidCoords = false; canDragDown = true; changingPage = false; switchImageAfterAnimation = 0; canZoom = (currentFileNames[0] != null && !isVideo && radialProgressViews[0].backgroundState != 0); updateMinMax(scale); } if (prevIndex == -1) { setImages(); for (int a = 0; a < 3; a++) { checkProgress(a, false); } } else { checkProgress(0, false); if (prevIndex > currentIndex) { ImageReceiver temp = rightImage; rightImage = centerImage; centerImage = leftImage; leftImage = temp; RadialProgressView tempProgress = radialProgressViews[0]; radialProgressViews[0] = radialProgressViews[2]; radialProgressViews[2] = tempProgress; setIndexToImage(leftImage, currentIndex - 1); checkProgress(1, false); checkProgress(2, false); } else if (prevIndex < currentIndex) { ImageReceiver temp = leftImage; leftImage = centerImage; centerImage = rightImage; rightImage = temp; RadialProgressView tempProgress = radialProgressViews[0]; radialProgressViews[0] = radialProgressViews[1]; radialProgressViews[1] = tempProgress; setIndexToImage(rightImage, currentIndex + 1); checkProgress(1, false); checkProgress(2, false); } } } private void setCurrentCaption(final CharSequence caption) { if (!TextUtils.isEmpty(caption)) { captionTextView = captionTextViewOld; captionTextViewOld = captionTextViewNew; captionTextViewNew = captionTextView; Theme.createChatResources(null, true); CharSequence str = Emoji.replaceEmoji(new SpannableStringBuilder(caption.toString()), captionTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(20), false); captionTextView.setTag(str); captionTextView.setText(str); captionTextView.setTextColor(0xffffffff); captionTextView.setAlpha(actionBar.getVisibility() == View.VISIBLE ? 1.0f : 0.0f); AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { captionTextViewOld.setTag(null); captionTextViewOld.setVisibility(View.INVISIBLE); captionTextViewNew.setVisibility( actionBar.getVisibility() == View.VISIBLE ? View.VISIBLE : View.INVISIBLE); } }); } else { captionTextView.setTextColor(0xffffffff); captionTextView.setTag(null); captionTextView.setVisibility(View.INVISIBLE); } } private void checkProgress(int a, boolean animated) { if (currentFileNames[a] != null) { int index = currentIndex; if (a == 1) { index += 1; } else if (a == 2) { index -= 1; } File f = getMediaFile(index); boolean isVideo = isMediaVideo(index); if (f != null && f.exists()) { if (isVideo) { radialProgressViews[a].setBackgroundState(3, animated); } else { radialProgressViews[a].setBackgroundState(-1, animated); } } else { if (isVideo) { if (!FileLoader.getInstance().isLoadingFile(currentFileNames[a])) { radialProgressViews[a].setBackgroundState(2, false); } else { radialProgressViews[a].setBackgroundState(1, false); } } else { radialProgressViews[a].setBackgroundState(0, animated); } Float progress = ImageLoader.getInstance().getFileProgress(currentFileNames[a]); if (progress == null) { progress = 0.0f; } radialProgressViews[a].setProgress(progress, false); } if (a == 0) { canZoom = (currentFileNames[0] != null && !isVideo && radialProgressViews[0].backgroundState != 0); } } else { radialProgressViews[a].setBackgroundState(-1, animated); } } private void setIndexToImage(ImageReceiver imageReceiver, int index) { imageReceiver.setOrientation(0, false); int size[] = new int[1]; TLRPC.FileLocation fileLocation = getFileLocation(index, size); if (fileLocation != null) { TLObject media = getMedia(index); if (media instanceof TLRPC.Photo) { TLRPC.Photo photo = (TLRPC.Photo) media; Bitmap placeHolder = null; if (currentThumb != null && imageReceiver == centerImage) { placeHolder = currentThumb; } if (size[0] == 0) { size[0] = -1; } TLRPC.PhotoSize thumbLocation = FileLoader.getClosestPhotoSizeWithSize(photo.sizes, 80); imageReceiver.setImage(fileLocation, null, null, placeHolder != null ? new BitmapDrawable(null, placeHolder) : null, thumbLocation != null ? thumbLocation.location : null, "b", size[0], null, true); } else if (isMediaVideo(index)) { if (!(fileLocation instanceof TLRPC.TL_fileLocationUnavailable)) { Bitmap placeHolder = null; if (currentThumb != null && imageReceiver == centerImage) { placeHolder = currentThumb; } imageReceiver.setImage(null, null, null, placeHolder != null ? new BitmapDrawable(null, placeHolder) : null, fileLocation, "b", 0, null, true); } else { imageReceiver.setImageBitmap( parentActivity.getResources().getDrawable(R.drawable.photoview_placeholder)); } } else if (currentAnimation != null) { imageReceiver.setImageBitmap(currentAnimation); currentAnimation.setSecondParentView(photoContainerView); } else { //TODO gif } } else { if (size[0] == 0) { imageReceiver.setImageBitmap((Bitmap) null); } else { imageReceiver.setImageBitmap( parentActivity.getResources().getDrawable(R.drawable.photoview_placeholder)); } } } public boolean isShowingImage(TLRPC.PageBlock object) { return isPhotoVisible && !disableShowCheck && object != null && currentMedia == object; } private boolean checkPhotoAnimation() { if (photoAnimationInProgress != 0) { if (Math.abs(photoTransitionAnimationStartTime - System.currentTimeMillis()) >= 500) { if (photoAnimationEndRunnable != null) { photoAnimationEndRunnable.run(); photoAnimationEndRunnable = null; } photoAnimationInProgress = 0; } } return photoAnimationInProgress != 0; } public boolean openPhoto(TLRPC.PageBlock block) { if (parentActivity == null || isPhotoVisible || checkPhotoAnimation() || block == null) { return false; } final PlaceProviderObject object = getPlaceForPhoto(block); if (object == null) { return false; } NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileDidFailedLoad); NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileDidLoaded); NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileLoadProgressChanged); NotificationCenter.getInstance().addObserver(this, NotificationCenter.emojiDidLoaded); if (velocityTracker == null) { velocityTracker = VelocityTracker.obtain(); } isPhotoVisible = true; toggleActionBar(true, false); actionBar.setAlpha(0.0f); bottomLayout.setAlpha(0.0f); captionTextView.setAlpha(0.0f); photoBackgroundDrawable.setAlpha(0); disableShowCheck = true; photoAnimationInProgress = 1; if (block != null) { currentAnimation = object.imageReceiver.getAnimation(); } int index = photoBlocks.indexOf(block); imagesArr.clear(); if (!(block instanceof TLRPC.TL_pageBlockVideo) || isVideoBlock(block)) { imagesArr.addAll(photoBlocks); } else { imagesArr.add(block); index = 0; } onPhotoShow(index, object); final Rect drawRegion = object.imageReceiver.getDrawRegion(); int orientation = object.imageReceiver.getOrientation(); int animatedOrientation = object.imageReceiver.getAnimatedOrientation(); if (animatedOrientation != 0) { orientation = animatedOrientation; } animatingImageView.setVisibility(View.VISIBLE); animatingImageView.setRadius(object.radius); animatingImageView.setOrientation(orientation); animatingImageView.setNeedRadius(object.radius != 0); animatingImageView.setImageBitmap(object.thumb); animatingImageView.setAlpha(1.0f); animatingImageView.setPivotX(0.0f); animatingImageView.setPivotY(0.0f); animatingImageView.setScaleX(object.scale); animatingImageView.setScaleY(object.scale); animatingImageView.setTranslationX(object.viewX + drawRegion.left * object.scale); animatingImageView.setTranslationY(object.viewY + drawRegion.top * object.scale); final ViewGroup.LayoutParams layoutParams = animatingImageView.getLayoutParams(); layoutParams.width = (drawRegion.right - drawRegion.left); layoutParams.height = (drawRegion.bottom - drawRegion.top); animatingImageView.setLayoutParams(layoutParams); float scaleX = (float) AndroidUtilities.displaySize.x / layoutParams.width; float scaleY = (float) (AndroidUtilities.displaySize.y + AndroidUtilities.statusBarHeight) / layoutParams.height; float scale = scaleX > scaleY ? scaleY : scaleX; float width = layoutParams.width * scale; float height = layoutParams.height * scale; float xPos = (AndroidUtilities.displaySize.x - width) / 2.0f; if (Build.VERSION.SDK_INT >= 21 && lastInsets != null) { xPos += ((WindowInsets) lastInsets).getSystemWindowInsetLeft(); } float yPos = ((AndroidUtilities.displaySize.y + AndroidUtilities.statusBarHeight) - height) / 2.0f; int clipHorizontal = Math.abs(drawRegion.left - object.imageReceiver.getImageX()); int clipVertical = Math.abs(drawRegion.top - object.imageReceiver.getImageY()); int coords2[] = new int[2]; object.parentView.getLocationInWindow(coords2); int clipTop = coords2[1] - (object.viewY + drawRegion.top) + object.clipTopAddition; if (clipTop < 0) { clipTop = 0; } int clipBottom = (object.viewY + drawRegion.top + layoutParams.height) - (coords2[1] + object.parentView.getHeight()) + object.clipBottomAddition; if (clipBottom < 0) { clipBottom = 0; } clipTop = Math.max(clipTop, clipVertical); clipBottom = Math.max(clipBottom, clipVertical); animationValues[0][0] = animatingImageView.getScaleX(); animationValues[0][1] = animatingImageView.getScaleY(); animationValues[0][2] = animatingImageView.getTranslationX(); animationValues[0][3] = animatingImageView.getTranslationY(); animationValues[0][4] = clipHorizontal * object.scale; animationValues[0][5] = clipTop * object.scale; animationValues[0][6] = clipBottom * object.scale; animationValues[0][7] = animatingImageView.getRadius(); animationValues[1][0] = scale; animationValues[1][1] = scale; animationValues[1][2] = xPos; animationValues[1][3] = yPos; animationValues[1][4] = 0; animationValues[1][5] = 0; animationValues[1][6] = 0; animationValues[1][7] = 0; photoContainerView.setVisibility(View.VISIBLE); photoContainerBackground.setVisibility(View.VISIBLE); animatingImageView.setAnimationProgress(0); final AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(ObjectAnimator.ofFloat(animatingImageView, "animationProgress", 0.0f, 1.0f), ObjectAnimator.ofInt(photoBackgroundDrawable, "alpha", 0, 255), ObjectAnimator.ofFloat(actionBar, "alpha", 0, 1.0f), ObjectAnimator.ofFloat(bottomLayout, "alpha", 0, 1.0f), ObjectAnimator.ofFloat(captionTextView, "alpha", 0, 1.0f)); photoAnimationEndRunnable = new Runnable() { @Override public void run() { if (photoContainerView == null) { return; } if (Build.VERSION.SDK_INT >= 18) { photoContainerView.setLayerType(View.LAYER_TYPE_NONE, null); } photoAnimationInProgress = 0; photoTransitionAnimationStartTime = 0; setImages(); photoContainerView.invalidate(); animatingImageView.setVisibility(View.GONE); if (showAfterAnimation != null) { showAfterAnimation.imageReceiver.setVisible(true, true); } if (hideAfterAnimation != null) { hideAfterAnimation.imageReceiver.setVisible(false, true); } } }; animatorSet.setDuration(200); animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { NotificationCenter.getInstance().setAnimationInProgress(false); if (photoAnimationEndRunnable != null) { photoAnimationEndRunnable.run(); photoAnimationEndRunnable = null; } } }); } }); photoTransitionAnimationStartTime = System.currentTimeMillis(); AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { NotificationCenter.getInstance() .setAllowedNotificationsDutingAnimation(new int[] { NotificationCenter.dialogsNeedReload, NotificationCenter.closeChats, NotificationCenter.mediaCountDidLoaded, NotificationCenter.mediaDidLoaded, NotificationCenter.dialogPhotosLoaded }); NotificationCenter.getInstance().setAnimationInProgress(true); animatorSet.start(); } }); if (Build.VERSION.SDK_INT >= 18) { photoContainerView.setLayerType(View.LAYER_TYPE_HARDWARE, null); } photoBackgroundDrawable.drawRunnable = new Runnable() { @Override public void run() { disableShowCheck = false; object.imageReceiver.setVisible(false, true); } }; return true; } public void closePhoto(boolean animated) { if (parentActivity == null || !isPhotoVisible || checkPhotoAnimation()) { return; } releasePlayer(); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.FileDidFailedLoad); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.FileDidLoaded); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.FileLoadProgressChanged); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.emojiDidLoaded); isActionBarVisible = false; if (velocityTracker != null) { velocityTracker.recycle(); velocityTracker = null; } final PlaceProviderObject object = getPlaceForPhoto(currentMedia); if (animated) { photoAnimationInProgress = 1; animatingImageView.setVisibility(View.VISIBLE); photoContainerView.invalidate(); AnimatorSet animatorSet = new AnimatorSet(); final ViewGroup.LayoutParams layoutParams = animatingImageView.getLayoutParams(); Rect drawRegion = null; int orientation = centerImage.getOrientation(); int animatedOrientation = 0; if (object != null && object.imageReceiver != null) { animatedOrientation = object.imageReceiver.getAnimatedOrientation(); } if (animatedOrientation != 0) { orientation = animatedOrientation; } animatingImageView.setOrientation(orientation); if (object != null) { animatingImageView.setNeedRadius(object.radius != 0); drawRegion = object.imageReceiver.getDrawRegion(); layoutParams.width = drawRegion.right - drawRegion.left; layoutParams.height = drawRegion.bottom - drawRegion.top; animatingImageView.setImageBitmap(object.thumb); } else { animatingImageView.setNeedRadius(false); layoutParams.width = centerImage.getImageWidth(); layoutParams.height = centerImage.getImageHeight(); animatingImageView.setImageBitmap(centerImage.getBitmap()); } animatingImageView.setLayoutParams(layoutParams); float scaleX = (float) AndroidUtilities.displaySize.x / layoutParams.width; float scaleY = (float) (AndroidUtilities.displaySize.y + AndroidUtilities.statusBarHeight) / layoutParams.height; float scale2 = scaleX > scaleY ? scaleY : scaleX; float width = layoutParams.width * scale * scale2; float height = layoutParams.height * scale * scale2; float xPos = (AndroidUtilities.displaySize.x - width) / 2.0f; if (Build.VERSION.SDK_INT >= 21 && lastInsets != null) { xPos += ((WindowInsets) lastInsets).getSystemWindowInsetLeft(); } float yPos = (AndroidUtilities.displaySize.y + AndroidUtilities.statusBarHeight - height) / 2.0f; animatingImageView.setTranslationX(xPos + translationX); animatingImageView.setTranslationY(yPos + translationY); animatingImageView.setScaleX(scale * scale2); animatingImageView.setScaleY(scale * scale2); if (object != null) { object.imageReceiver.setVisible(false, true); int clipHorizontal = Math.abs(drawRegion.left - object.imageReceiver.getImageX()); int clipVertical = Math.abs(drawRegion.top - object.imageReceiver.getImageY()); int coords2[] = new int[2]; object.parentView.getLocationInWindow(coords2); int clipTop = coords2[1] - (object.viewY + drawRegion.top) + object.clipTopAddition; if (clipTop < 0) { clipTop = 0; } int clipBottom = (object.viewY + drawRegion.top + (drawRegion.bottom - drawRegion.top)) - (coords2[1] + object.parentView.getHeight()) + object.clipBottomAddition; if (clipBottom < 0) { clipBottom = 0; } clipTop = Math.max(clipTop, clipVertical); clipBottom = Math.max(clipBottom, clipVertical); animationValues[0][0] = animatingImageView.getScaleX(); animationValues[0][1] = animatingImageView.getScaleY(); animationValues[0][2] = animatingImageView.getTranslationX(); animationValues[0][3] = animatingImageView.getTranslationY(); animationValues[0][4] = 0; animationValues[0][5] = 0; animationValues[0][6] = 0; animationValues[0][7] = 0; animationValues[1][0] = object.scale; animationValues[1][1] = object.scale; animationValues[1][2] = object.viewX + drawRegion.left * object.scale; animationValues[1][3] = object.viewY + drawRegion.top * object.scale; animationValues[1][4] = clipHorizontal * object.scale; animationValues[1][5] = clipTop * object.scale; animationValues[1][6] = clipBottom * object.scale; animationValues[1][7] = object.radius; animatorSet.playTogether( ObjectAnimator.ofFloat(animatingImageView, "animationProgress", 0.0f, 1.0f), ObjectAnimator.ofInt(photoBackgroundDrawable, "alpha", 0), ObjectAnimator.ofFloat(actionBar, "alpha", 0), ObjectAnimator.ofFloat(bottomLayout, "alpha", 0), ObjectAnimator.ofFloat(captionTextView, "alpha", 0)); } else { int h = AndroidUtilities.displaySize.y + AndroidUtilities.statusBarHeight; animatorSet.playTogether(ObjectAnimator.ofInt(photoBackgroundDrawable, "alpha", 0), ObjectAnimator.ofFloat(animatingImageView, "alpha", 0.0f), ObjectAnimator.ofFloat(animatingImageView, "translationY", translationY >= 0 ? h : -h), ObjectAnimator.ofFloat(actionBar, "alpha", 0), ObjectAnimator.ofFloat(bottomLayout, "alpha", 0), ObjectAnimator.ofFloat(captionTextView, "alpha", 0)); } photoAnimationEndRunnable = new Runnable() { @Override public void run() { if (Build.VERSION.SDK_INT >= 18) { photoContainerView.setLayerType(View.LAYER_TYPE_NONE, null); } photoContainerView.setVisibility(View.INVISIBLE); photoContainerBackground.setVisibility(View.INVISIBLE); photoAnimationInProgress = 0; onPhotoClosed(object); } }; animatorSet.setDuration(200); animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { if (photoAnimationEndRunnable != null) { photoAnimationEndRunnable.run(); photoAnimationEndRunnable = null; } } }); } }); photoTransitionAnimationStartTime = System.currentTimeMillis(); if (Build.VERSION.SDK_INT >= 18) { photoContainerView.setLayerType(View.LAYER_TYPE_HARDWARE, null); } animatorSet.start(); } else { AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(ObjectAnimator.ofFloat(photoContainerView, "scaleX", 0.9f), ObjectAnimator.ofFloat(photoContainerView, "scaleY", 0.9f), ObjectAnimator.ofInt(photoBackgroundDrawable, "alpha", 0), ObjectAnimator.ofFloat(actionBar, "alpha", 0), ObjectAnimator.ofFloat(bottomLayout, "alpha", 0), ObjectAnimator.ofFloat(captionTextView, "alpha", 0)); photoAnimationInProgress = 2; photoAnimationEndRunnable = new Runnable() { @Override public void run() { if (photoContainerView == null) { return; } if (Build.VERSION.SDK_INT >= 18) { photoContainerView.setLayerType(View.LAYER_TYPE_NONE, null); } photoContainerView.setVisibility(View.INVISIBLE); photoContainerBackground.setVisibility(View.INVISIBLE); photoAnimationInProgress = 0; onPhotoClosed(object); photoContainerView.setScaleX(1.0f); photoContainerView.setScaleY(1.0f); } }; animatorSet.setDuration(200); animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (photoAnimationEndRunnable != null) { photoAnimationEndRunnable.run(); photoAnimationEndRunnable = null; } } }); photoTransitionAnimationStartTime = System.currentTimeMillis(); if (Build.VERSION.SDK_INT >= 18) { photoContainerView.setLayerType(View.LAYER_TYPE_HARDWARE, null); } animatorSet.start(); } if (currentAnimation != null) { currentAnimation.setSecondParentView(null); currentAnimation = null; centerImage.setImageBitmap((Drawable) null); } } private void onPhotoClosed(PlaceProviderObject object) { isPhotoVisible = false; disableShowCheck = true; currentMedia = null; currentThumb = null; if (currentAnimation != null) { currentAnimation.setSecondParentView(null); currentAnimation = null; } for (int a = 0; a < 3; a++) { if (radialProgressViews[a] != null) { radialProgressViews[a].setBackgroundState(-1, false); } } centerImage.setImageBitmap((Bitmap) null); leftImage.setImageBitmap((Bitmap) null); rightImage.setImageBitmap((Bitmap) null); photoContainerView.post(new Runnable() { @Override public void run() { animatingImageView.setImageBitmap(null); } }); disableShowCheck = false; if (object != null) { object.imageReceiver.setVisible(true, true); } } public void onPause() { if (currentAnimation != null) { closePhoto(false); } } private void updateMinMax(float scale) { int maxW = (int) (centerImage.getImageWidth() * scale - getContainerViewWidth()) / 2; int maxH = (int) (centerImage.getImageHeight() * scale - getContainerViewHeight()) / 2; if (maxW > 0) { minX = -maxW; maxX = maxW; } else { minX = maxX = 0; } if (maxH > 0) { minY = -maxH; maxY = maxH; } else { minY = maxY = 0; } } private int getContainerViewWidth() { return photoContainerView.getWidth(); } private int getContainerViewHeight() { return photoContainerView.getHeight(); } private boolean processTouchEvent(MotionEvent ev) { if (photoAnimationInProgress != 0 || animationStartTime != 0) { return false; } if (ev.getPointerCount() == 1 && gestureDetector.onTouchEvent(ev) && doubleTap) { doubleTap = false; moving = false; zooming = false; checkMinMax(false); return true; } if (ev.getActionMasked() == MotionEvent.ACTION_DOWN || ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { discardTap = false; if (!scroller.isFinished()) { scroller.abortAnimation(); } if (!draggingDown && !changingPage) { if (canZoom && ev.getPointerCount() == 2) { pinchStartDistance = (float) Math.hypot(ev.getX(1) - ev.getX(0), ev.getY(1) - ev.getY(0)); pinchStartScale = scale; pinchCenterX = (ev.getX(0) + ev.getX(1)) / 2.0f; pinchCenterY = (ev.getY(0) + ev.getY(1)) / 2.0f; pinchStartX = translationX; pinchStartY = translationY; zooming = true; moving = false; if (velocityTracker != null) { velocityTracker.clear(); } } else if (ev.getPointerCount() == 1) { moveStartX = ev.getX(); dragY = moveStartY = ev.getY(); draggingDown = false; canDragDown = true; if (velocityTracker != null) { velocityTracker.clear(); } } } } else if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) { if (canZoom && ev.getPointerCount() == 2 && !draggingDown && zooming && !changingPage) { discardTap = true; scale = (float) Math.hypot(ev.getX(1) - ev.getX(0), ev.getY(1) - ev.getY(0)) / pinchStartDistance * pinchStartScale; translationX = (pinchCenterX - getContainerViewWidth() / 2) - ((pinchCenterX - getContainerViewWidth() / 2) - pinchStartX) * (scale / pinchStartScale); translationY = (pinchCenterY - getContainerViewHeight() / 2) - ((pinchCenterY - getContainerViewHeight() / 2) - pinchStartY) * (scale / pinchStartScale); updateMinMax(scale); photoContainerView.invalidate(); } else if (ev.getPointerCount() == 1) { if (velocityTracker != null) { velocityTracker.addMovement(ev); } float dx = Math.abs(ev.getX() - moveStartX); float dy = Math.abs(ev.getY() - dragY); if (dx > AndroidUtilities.dp(3) || dy > AndroidUtilities.dp(3)) { discardTap = true; } if (canDragDown && !draggingDown && scale == 1 && dy >= AndroidUtilities.dp(30) && dy / 2 > dx) { draggingDown = true; moving = false; dragY = ev.getY(); if (isActionBarVisible) { toggleActionBar(false, true); } return true; } else if (draggingDown) { translationY = ev.getY() - dragY; photoContainerView.invalidate(); } else if (!invalidCoords && animationStartTime == 0) { float moveDx = moveStartX - ev.getX(); float moveDy = moveStartY - ev.getY(); if (moving || scale == 1 && Math.abs(moveDy) + AndroidUtilities.dp(12) < Math.abs(moveDx) || scale != 1) { if (!moving) { moveDx = 0; moveDy = 0; moving = true; canDragDown = false; } moveStartX = ev.getX(); moveStartY = ev.getY(); updateMinMax(scale); if (translationX < minX && (!rightImage.hasImage()) || translationX > maxX && !leftImage.hasImage()) { moveDx /= 3.0f; } if (maxY == 0 && minY == 0) { if (translationY - moveDy < minY) { translationY = minY; moveDy = 0; } else if (translationY - moveDy > maxY) { translationY = maxY; moveDy = 0; } } else { if (translationY < minY || translationY > maxY) { moveDy /= 3.0f; } } translationX -= moveDx; if (scale != 1) { translationY -= moveDy; } photoContainerView.invalidate(); } } else { invalidCoords = false; moveStartX = ev.getX(); moveStartY = ev.getY(); } } } else if (ev.getActionMasked() == MotionEvent.ACTION_CANCEL || ev.getActionMasked() == MotionEvent.ACTION_UP || ev.getActionMasked() == MotionEvent.ACTION_POINTER_UP) { if (zooming) { invalidCoords = true; if (scale < 1.0f) { updateMinMax(1.0f); animateTo(1.0f, 0, 0, true); } else if (scale > 3.0f) { float atx = (pinchCenterX - getContainerViewWidth() / 2) - ((pinchCenterX - getContainerViewWidth() / 2) - pinchStartX) * (3.0f / pinchStartScale); float aty = (pinchCenterY - getContainerViewHeight() / 2) - ((pinchCenterY - getContainerViewHeight() / 2) - pinchStartY) * (3.0f / pinchStartScale); updateMinMax(3.0f); if (atx < minX) { atx = minX; } else if (atx > maxX) { atx = maxX; } if (aty < minY) { aty = minY; } else if (aty > maxY) { aty = maxY; } animateTo(3.0f, atx, aty, true); } else { checkMinMax(true); } zooming = false; } else if (draggingDown) { if (Math.abs(dragY - ev.getY()) > getContainerViewHeight() / 6.0f) { closePhoto(true); } else { animateTo(1, 0, 0, false); } draggingDown = false; } else if (moving) { float moveToX = translationX; float moveToY = translationY; updateMinMax(scale); moving = false; canDragDown = true; float velocity = 0; if (velocityTracker != null && scale == 1) { velocityTracker.computeCurrentVelocity(1000); velocity = velocityTracker.getXVelocity(); } if ((translationX < minX - getContainerViewWidth() / 3 || velocity < -AndroidUtilities.dp(650)) && rightImage.hasImage()) { goToNext(); return true; } if ((translationX > maxX + getContainerViewWidth() / 3 || velocity > AndroidUtilities.dp(650)) && leftImage.hasImage()) { goToPrev(); return true; } if (translationX < minX) { moveToX = minX; } else if (translationX > maxX) { moveToX = maxX; } if (translationY < minY) { moveToY = minY; } else if (translationY > maxY) { moveToY = maxY; } animateTo(scale, moveToX, moveToY, false); } } return false; } private void checkMinMax(boolean zoom) { float moveToX = translationX; float moveToY = translationY; updateMinMax(scale); if (translationX < minX) { moveToX = minX; } else if (translationX > maxX) { moveToX = maxX; } if (translationY < minY) { moveToY = minY; } else if (translationY > maxY) { moveToY = maxY; } animateTo(scale, moveToX, moveToY, zoom); } private void goToNext() { float extra = 0; if (scale != 1) { extra = (getContainerViewWidth() - centerImage.getImageWidth()) / 2 * scale; } switchImageAfterAnimation = 1; animateTo(scale, minX - getContainerViewWidth() - extra - AndroidUtilities.dp(30) / 2, translationY, false); } private void goToPrev() { float extra = 0; if (scale != 1) { extra = (getContainerViewWidth() - centerImage.getImageWidth()) / 2 * scale; } switchImageAfterAnimation = 2; animateTo(scale, maxX + getContainerViewWidth() + extra + AndroidUtilities.dp(30) / 2, translationY, false); } private void animateTo(float newScale, float newTx, float newTy, boolean isZoom) { animateTo(newScale, newTx, newTy, isZoom, 250); } private void animateTo(float newScale, float newTx, float newTy, boolean isZoom, int duration) { if (scale == newScale && translationX == newTx && translationY == newTy) { return; } zoomAnimation = isZoom; animateToScale = newScale; animateToX = newTx; animateToY = newTy; animationStartTime = System.currentTimeMillis(); imageMoveAnimation = new AnimatorSet(); imageMoveAnimation.playTogether(ObjectAnimator.ofFloat(this, "animationValue", 0, 1)); imageMoveAnimation.setInterpolator(interpolator); imageMoveAnimation.setDuration(duration); imageMoveAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { imageMoveAnimation = null; photoContainerView.invalidate(); } }); imageMoveAnimation.start(); } public void setAnimationValue(float value) { animationValue = value; photoContainerView.invalidate(); } public float getAnimationValue() { return animationValue; } private void drawContent(Canvas canvas) { if (photoAnimationInProgress == 1 || !isPhotoVisible && photoAnimationInProgress != 2) { return; } float currentTranslationY; float currentTranslationX; float currentScale; float aty = -1; if (imageMoveAnimation != null) { if (!scroller.isFinished()) { scroller.abortAnimation(); } float ts = scale + (animateToScale - scale) * animationValue; float tx = translationX + (animateToX - translationX) * animationValue; float ty = translationY + (animateToY - translationY) * animationValue; if (animateToScale == 1 && scale == 1 && translationX == 0) { aty = ty; } currentScale = ts; currentTranslationY = ty; currentTranslationX = tx; photoContainerView.invalidate(); } else { if (animationStartTime != 0) { translationX = animateToX; translationY = animateToY; scale = animateToScale; animationStartTime = 0; updateMinMax(scale); zoomAnimation = false; } if (!scroller.isFinished()) { if (scroller.computeScrollOffset()) { if (scroller.getStartX() < maxX && scroller.getStartX() > minX) { translationX = scroller.getCurrX(); } if (scroller.getStartY() < maxY && scroller.getStartY() > minY) { translationY = scroller.getCurrY(); } photoContainerView.invalidate(); } } if (switchImageAfterAnimation != 0) { if (switchImageAfterAnimation == 1) { setImageIndex(currentIndex + 1, false); } else if (switchImageAfterAnimation == 2) { setImageIndex(currentIndex - 1, false); } switchImageAfterAnimation = 0; } currentScale = scale; currentTranslationY = translationY; currentTranslationX = translationX; if (!moving) { aty = translationY; } } if (scale == 1 && aty != -1 && !zoomAnimation) { float maxValue = getContainerViewHeight() / 4.0f; photoBackgroundDrawable .setAlpha((int) Math.max(127, 255 * (1.0f - (Math.min(Math.abs(aty), maxValue) / maxValue)))); } else { photoBackgroundDrawable.setAlpha(255); } ImageReceiver sideImage = null; if (scale >= 1.0f && !zoomAnimation && !zooming) { if (currentTranslationX > maxX + AndroidUtilities.dp(5)) { sideImage = leftImage; } else if (currentTranslationX < minX - AndroidUtilities.dp(5)) { sideImage = rightImage; } } changingPage = sideImage != null; if (sideImage == rightImage) { float tranlateX = currentTranslationX; float scaleDiff = 0; float alpha = 1; if (!zoomAnimation && tranlateX < minX) { alpha = Math.min(1.0f, (minX - tranlateX) / canvas.getWidth()); scaleDiff = (1.0f - alpha) * 0.3f; tranlateX = -canvas.getWidth() - AndroidUtilities.dp(30) / 2; } if (sideImage.hasBitmapImage()) { canvas.save(); canvas.translate(getContainerViewWidth() / 2, getContainerViewHeight() / 2); canvas.translate(canvas.getWidth() + AndroidUtilities.dp(30) / 2 + tranlateX, 0); canvas.scale(1.0f - scaleDiff, 1.0f - scaleDiff); int bitmapWidth = sideImage.getBitmapWidth(); int bitmapHeight = sideImage.getBitmapHeight(); float scaleX = (float) getContainerViewWidth() / (float) bitmapWidth; float scaleY = (float) getContainerViewHeight() / (float) bitmapHeight; float scale = scaleX > scaleY ? scaleY : scaleX; int width = (int) (bitmapWidth * scale); int height = (int) (bitmapHeight * scale); sideImage.setAlpha(alpha); sideImage.setImageCoords(-width / 2, -height / 2, width, height); sideImage.draw(canvas); canvas.restore(); } canvas.save(); canvas.translate(tranlateX, currentTranslationY / currentScale); canvas.translate((canvas.getWidth() * (scale + 1) + AndroidUtilities.dp(30)) / 2, -currentTranslationY / currentScale); radialProgressViews[1].setScale(1.0f - scaleDiff); radialProgressViews[1].setAlpha(alpha); radialProgressViews[1].onDraw(canvas); canvas.restore(); } float translateX = currentTranslationX; float scaleDiff = 0; float alpha = 1; if (!zoomAnimation && translateX > maxX) { alpha = Math.min(1.0f, (translateX - maxX) / canvas.getWidth()); scaleDiff = alpha * 0.3f; alpha = 1.0f - alpha; translateX = maxX; } boolean drawTextureView = aspectRatioFrameLayout != null && aspectRatioFrameLayout.getVisibility() == View.VISIBLE; if (centerImage.hasBitmapImage()) { canvas.save(); canvas.translate(getContainerViewWidth() / 2, getContainerViewHeight() / 2); canvas.translate(translateX, currentTranslationY); canvas.scale(currentScale - scaleDiff, currentScale - scaleDiff); int bitmapWidth = centerImage.getBitmapWidth(); int bitmapHeight = centerImage.getBitmapHeight(); if (drawTextureView && textureUploaded) { float scale1 = bitmapWidth / (float) bitmapHeight; float scale2 = videoTextureView.getMeasuredWidth() / (float) videoTextureView.getMeasuredHeight(); if (Math.abs(scale1 - scale2) > 0.01f) { bitmapWidth = videoTextureView.getMeasuredWidth(); bitmapHeight = videoTextureView.getMeasuredHeight(); } } float scaleX = (float) getContainerViewWidth() / (float) bitmapWidth; float scaleY = (float) getContainerViewHeight() / (float) bitmapHeight; float scale = scaleX > scaleY ? scaleY : scaleX; int width = (int) (bitmapWidth * scale); int height = (int) (bitmapHeight * scale); if (!drawTextureView || !textureUploaded || !videoCrossfadeStarted || videoCrossfadeAlpha != 1.0f) { centerImage.setAlpha(alpha); centerImage.setImageCoords(-width / 2, -height / 2, width, height); centerImage.draw(canvas); } if (drawTextureView) { if (!videoCrossfadeStarted && textureUploaded) { videoCrossfadeStarted = true; videoCrossfadeAlpha = 0.0f; videoCrossfadeAlphaLastTime = System.currentTimeMillis(); } canvas.translate(-width / 2, -height / 2); videoTextureView.setAlpha(alpha * videoCrossfadeAlpha); aspectRatioFrameLayout.draw(canvas); if (videoCrossfadeStarted && videoCrossfadeAlpha < 1.0f) { long newUpdateTime = System.currentTimeMillis(); long dt = newUpdateTime - videoCrossfadeAlphaLastTime; videoCrossfadeAlphaLastTime = newUpdateTime; videoCrossfadeAlpha += dt / 300.0f; photoContainerView.invalidate(); if (videoCrossfadeAlpha > 1.0f) { videoCrossfadeAlpha = 1.0f; } } } canvas.restore(); } if (!drawTextureView && bottomLayout.getVisibility() != View.VISIBLE) { canvas.save(); canvas.translate(translateX, currentTranslationY / currentScale); radialProgressViews[0].setScale(1.0f - scaleDiff); radialProgressViews[0].setAlpha(alpha); radialProgressViews[0].onDraw(canvas); canvas.restore(); } if (sideImage == leftImage) { if (sideImage.hasBitmapImage()) { canvas.save(); canvas.translate(getContainerViewWidth() / 2, getContainerViewHeight() / 2); canvas.translate( -(canvas.getWidth() * (scale + 1) + AndroidUtilities.dp(30)) / 2 + currentTranslationX, 0); int bitmapWidth = sideImage.getBitmapWidth(); int bitmapHeight = sideImage.getBitmapHeight(); float scaleX = (float) getContainerViewWidth() / (float) bitmapWidth; float scaleY = (float) getContainerViewHeight() / (float) bitmapHeight; float scale = scaleX > scaleY ? scaleY : scaleX; int width = (int) (bitmapWidth * scale); int height = (int) (bitmapHeight * scale); sideImage.setAlpha(1.0f); sideImage.setImageCoords(-width / 2, -height / 2, width, height); sideImage.draw(canvas); canvas.restore(); } canvas.save(); canvas.translate(currentTranslationX, currentTranslationY / currentScale); canvas.translate(-(canvas.getWidth() * (scale + 1) + AndroidUtilities.dp(30)) / 2, -currentTranslationY / currentScale); radialProgressViews[2].setScale(1.0f); radialProgressViews[2].setAlpha(1.0f); radialProgressViews[2].onDraw(canvas); canvas.restore(); } } private void onActionClick(boolean download) { TLObject media = getMedia(currentIndex); if (!(media instanceof TLRPC.Document) || currentFileNames[0] == null) { return; } TLRPC.Document document = (TLRPC.Document) media; File file = null; if (currentMedia != null) { file = getMediaFile(currentIndex); if (file != null && !file.exists()) { file = null; } } if (file == null) { if (download) { if (!FileLoader.getInstance().isLoadingFile(currentFileNames[0])) { FileLoader.getInstance().loadFile(document, true, true); } else { FileLoader.getInstance().cancelLoadFile(document); } } } else { preparePlayer(file, true); } } @Override public boolean onDown(MotionEvent e) { return false; } @Override public void onShowPress(MotionEvent e) { } @Override public boolean onSingleTapUp(MotionEvent e) { return false; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return false; } @Override public void onLongPress(MotionEvent e) { } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { if (scale != 1) { scroller.abortAnimation(); scroller.fling(Math.round(translationX), Math.round(translationY), Math.round(velocityX), Math.round(velocityY), (int) minX, (int) maxX, (int) minY, (int) maxY); photoContainerView.postInvalidate(); } return false; } @Override public boolean onSingleTapConfirmed(MotionEvent e) { if (discardTap) { return false; } boolean drawTextureView = aspectRatioFrameLayout != null && aspectRatioFrameLayout.getVisibility() == View.VISIBLE; if (radialProgressViews[0] != null && photoContainerView != null && !drawTextureView) { int state = radialProgressViews[0].backgroundState; if (state > 0 && state <= 3) { float x = e.getX(); float y = e.getY(); if (x >= (getContainerViewWidth() - AndroidUtilities.dp(100)) / 2.0f && x <= (getContainerViewWidth() + AndroidUtilities.dp(100)) / 2.0f && y >= (getContainerViewHeight() - AndroidUtilities.dp(100)) / 2.0f && y <= (getContainerViewHeight() + AndroidUtilities.dp(100)) / 2.0f) { onActionClick(true); checkProgress(0, true); return true; } } } toggleActionBar(!isActionBarVisible, true); return true; } @Override public boolean onDoubleTap(MotionEvent e) { if (!canZoom || scale == 1.0f && (translationY != 0 || translationX != 0)) { return false; } if (animationStartTime != 0 || photoAnimationInProgress != 0) { return false; } if (scale == 1.0f) { float atx = (e.getX() - getContainerViewWidth() / 2) - ((e.getX() - getContainerViewWidth() / 2) - translationX) * (3.0f / scale); float aty = (e.getY() - getContainerViewHeight() / 2) - ((e.getY() - getContainerViewHeight() / 2) - translationY) * (3.0f / scale); updateMinMax(3.0f); if (atx < minX) { atx = minX; } else if (atx > maxX) { atx = maxX; } if (aty < minY) { aty = minY; } else if (aty > maxY) { aty = maxY; } animateTo(3.0f, atx, aty, true); } else { animateTo(1.0f, 0, 0, true); } doubleTap = true; return true; } @Override public boolean onDoubleTapEvent(MotionEvent e) { return false; } private ImageReceiver getImageReceiverFromListView(ViewGroup listView, TLRPC.PageBlock pageBlock, int[] coords) { int count = listView.getChildCount(); for (int a = 0; a < count; a++) { View view = listView.getChildAt(a); if (view instanceof BlockPhotoCell) { BlockPhotoCell cell = (BlockPhotoCell) view; if (cell.currentBlock == pageBlock) { view.getLocationInWindow(coords); return cell.imageView; } } else if (view instanceof BlockVideoCell) { BlockVideoCell cell = (BlockVideoCell) view; if (cell.currentBlock == pageBlock) { view.getLocationInWindow(coords); return cell.imageView; } } else if (view instanceof BlockCollageCell) { ImageReceiver imageReceiver = getImageReceiverFromListView(((BlockCollageCell) view).innerListView, pageBlock, coords); if (imageReceiver != null) { return imageReceiver; } } else if (view instanceof BlockSlideshowCell) { ImageReceiver imageReceiver = getImageReceiverFromListView( ((BlockSlideshowCell) view).innerListView, pageBlock, coords); if (imageReceiver != null) { return imageReceiver; } } } return null; } private PlaceProviderObject getPlaceForPhoto(TLRPC.PageBlock pageBlock) { ImageReceiver imageReceiver = getImageReceiverFromListView(listView, pageBlock, coords); if (imageReceiver == null) { return null; } PlaceProviderObject object = new PlaceProviderObject(); object.viewX = coords[0]; object.viewY = coords[1]; object.parentView = listView; object.imageReceiver = imageReceiver; object.thumb = imageReceiver.getBitmap(); object.radius = imageReceiver.getRoundRadius(); object.clipTopAddition = currentHeaderHeight; return object; } private boolean scaleToFill() { return false; } }