diff --git a/Clover/app/src/main/java/org/floens/chan/core/presenter/ImageViewerPresenter.java b/Clover/app/src/main/java/org/floens/chan/core/presenter/ImageViewerPresenter.java index 649d7153..1cf686b8 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/presenter/ImageViewerPresenter.java +++ b/Clover/app/src/main/java/org/floens/chan/core/presenter/ImageViewerPresenter.java @@ -1,40 +1,163 @@ package org.floens.chan.core.presenter; +import android.support.v4.view.ViewPager; + import org.floens.chan.core.model.PostImage; +import org.floens.chan.core.settings.ChanSettings; +import org.floens.chan.ui.view.MultiImageView; +import java.io.File; +import java.util.ArrayList; import java.util.List; -public class ImageViewerPresenter { +public class ImageViewerPresenter implements MultiImageView.Callback, ViewPager.OnPageChangeListener { private final Callback callback; + private final boolean imageAutoLoad = ChanSettings.imageAutoLoad.get(); + + private boolean entering = true; private boolean exiting = false; private List images; private int selectedIndex; + private boolean initalLowResLoaded = false; + private boolean changeViewsOnInTransitionEnd = false; public ImageViewerPresenter(Callback callback) { this.callback = callback; } public void showImages(List images, int index) { - callback.startPreviewInTransition(); - this.images = images; selectedIndex = index; + + // Do this before the view is measured, to avoid it to always loading the first two pages + callback.setPagerItems(images, selectedIndex); + callback.setImageMode(images.get(selectedIndex), MultiImageView.Mode.LOWRES); + } + + public void onViewMeasured() { + // Pager is measured, but still invisible + callback.startPreviewInTransition(); + } + + public void onInTransitionEnd() { + entering = false; + // Depends on what onModeLoaded did + if (changeViewsOnInTransitionEnd) { + callback.setPreviewVisibility(false); + callback.setPagerVisiblity(true); + } } public void onExit() { - if (exiting) return; + if (entering || exiting) return; exiting = true; + + callback.setPagerVisiblity(false); + callback.setPreviewVisibility(true); callback.startPreviewOutTransition(); } - public void onInTransitionEnd() { - PostImage image = images.get(selectedIndex); + @Override + public void onPageSelected(int position) { + selectedIndex = position; + if (initalLowResLoaded) { + for (PostImage other : getOther(selectedIndex)) { + callback.setImageMode(other, MultiImageView.Mode.LOWRES); + } + callback.setImageMode(images.get(selectedIndex), MultiImageView.Mode.LOWRES); + } + // onModeLoaded will handle the else case + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + } + + @Override + public void onPageScrollStateChanged(int state) { + } + + @Override + public void onModeLoaded(MultiImageView multiImageView, MultiImageView.Mode mode) { + if (mode == MultiImageView.Mode.LOWRES) { + // lowres is requested at the beginning of the transition, + // the lowres is loaded before the in transition or after + if (!initalLowResLoaded) { + initalLowResLoaded = true; + if (!entering) { + // Entering transition was already ended, switch now + callback.setPreviewVisibility(false); + callback.setPagerVisiblity(true); + } else { + // Wait for enter animation to finish before changing views + changeViewsOnInTransitionEnd = true; + } + // Transition ended or not, request loading the other side views to lowres + for (PostImage other : getOther(selectedIndex)) { + callback.setImageMode(other, MultiImageView.Mode.LOWRES); + } + // selectedIndex can be different than the initial one because of page changes before onModeLoaded was called, + // request a load of the current selectedIndex one here + callback.setImageMode(images.get(selectedIndex), MultiImageView.Mode.LOWRES); + } + + // Initial load or not, transitioning or not, load the high res when the user setting says so after the low res + if (imageAutoLoad) { + multiImageView.setMode(MultiImageView.Mode.BIGIMAGE); + } + } + } + + @Override + public void onTap(MultiImageView multiImageView) { + onExit(); + } + + @Override + public void setProgress(MultiImageView multiImageView, boolean progress) { + + } + + @Override + public void setLinearProgress(MultiImageView multiImageView, long current, long total, boolean done) { + + } + + @Override + public void onVideoLoaded(MultiImageView multiImageView) { + + } + + @Override + public void onVideoError(MultiImageView multiImageView, File video) { + + } + + private List getOther(int position) { + List other = new ArrayList<>(2); + if (position - 1 >= 0) { + other.add(images.get(position - 1)); + } + if (position + 1 < images.size()) { + other.add(images.get(position + 1)); + } + return other; } public interface Callback { public void startPreviewInTransition(); public void startPreviewOutTransition(); + + public void setPreviewVisibility(boolean visible); + + public void setPagerVisiblity(boolean visible); + + public void setPagerItems(List images, int initialIndex); + + public void setImageMode(PostImage postImage, MultiImageView.Mode mode); + + public MultiImageView.Mode getImageMode(PostImage postImage); } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/adapter/ImageViewerAdapter.java b/Clover/app/src/main/java/org/floens/chan/ui/adapter/ImageViewerAdapter.java index 9f6ab587..ffec69db 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/adapter/ImageViewerAdapter.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/adapter/ImageViewerAdapter.java @@ -2,18 +2,26 @@ package org.floens.chan.ui.adapter; import android.content.Context; import android.view.View; +import android.view.ViewGroup; import org.floens.chan.core.model.PostImage; import org.floens.chan.ui.view.MultiImageView; import org.floens.chan.ui.view.ViewPagerAdapter; +import org.floens.chan.utils.Logger; +import java.util.ArrayList; import java.util.List; public class ImageViewerAdapter extends ViewPagerAdapter { + private static final String TAG = "ImageViewerAdapter"; + private final Context context; private final List images; private final MultiImageView.Callback multiImageViewCallback; + private List loadedViews = new ArrayList<>(3); + private List pendingModeChanges = new ArrayList<>(); + public ImageViewerAdapter(Context context, List images, MultiImageView.Callback multiImageViewCallback) { this.context = context; this.images = images; @@ -22,14 +30,84 @@ public class ImageViewerAdapter extends ViewPagerAdapter { @Override public View getView(int position) { + PostImage postImage = images.get(position); MultiImageView view = new MultiImageView(context); - view.bindPostImage(images.get(position), multiImageViewCallback); + view.bindPostImage(postImage, multiImageViewCallback); + + loadedViews.add(view); + + Logger.test("getView: " + postImage.imageUrl + " " + postImage.type.toString()); return view; } + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + super.destroyItem(container, position, object); + + PostImage postImage = ((MultiImageView)object).getPostImage(); + Logger.test("destroyView: " + postImage.imageUrl + " " + postImage.type.toString()); + + //noinspection SuspiciousMethodCalls + if (!loadedViews.remove((View) object)) { + Logger.test("Nope"); + } + } + @Override public int getCount() { return images.size(); } + + @Override + public void finishUpdate(ViewGroup container) { + for (ModeChange change : pendingModeChanges) { + MultiImageView view = find(change.postImage); + if (view == null) { + Logger.w(TAG, "finishUpdate setMode view still not found"); + } else { + view.setMode(change.mode); + } + } + pendingModeChanges.clear(); + } + + public void setMode(final PostImage postImage, MultiImageView.Mode mode) { + MultiImageView view = find(postImage); + if (view == null) { + Logger.w(TAG, "setMode view not found, scheduling it"); + pendingModeChanges.add(new ModeChange(mode, postImage)); + } else { + view.setMode(mode); + } + } + + public MultiImageView.Mode getMode(PostImage postImage) { + MultiImageView view = find(postImage); + if (view == null) { + Logger.w(TAG, "getMode view not found"); + return null; + } else { + return view.getMode(); + } + } + + public MultiImageView find(PostImage postImage) { + for (MultiImageView view : loadedViews) { + if (view.getPostImage() == postImage) { + return view; + } + } + return null; + } + + private static class ModeChange { + public MultiImageView.Mode mode; + public PostImage postImage; + + private ModeChange(MultiImageView.Mode mode, PostImage postImage) { + this.mode = mode; + this.postImage = postImage; + } + } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java index 961f0c72..34dc72a4 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java @@ -19,11 +19,17 @@ import android.widget.ImageView; import org.floens.chan.R; import org.floens.chan.controller.Controller; +import org.floens.chan.core.model.PostImage; import org.floens.chan.core.presenter.ImageViewerPresenter; +import org.floens.chan.ui.adapter.ImageViewerAdapter; import org.floens.chan.ui.toolbar.Toolbar; import org.floens.chan.ui.view.ClippingImageView; +import org.floens.chan.ui.view.MultiImageView; +import org.floens.chan.utils.AndroidUtils; import org.floens.chan.utils.AnimationUtils; +import java.util.List; + import static org.floens.chan.utils.AndroidUtils.dp; import static org.floens.chan.utils.AnimationUtils.calculateBoundsAnimation; @@ -36,7 +42,7 @@ public class ImageViewerController extends Controller implements View.OnClickLis private AnimatorSet startPreviewAnimation; private AnimatorSet endAnimation; - private Callback callback; + private PreviewCallback previewCallback; private ImageViewerPresenter presenter; private final Toolbar toolbar; @@ -60,6 +66,15 @@ public class ImageViewerController extends Controller implements View.OnClickLis view.setOnClickListener(this); previewImage = (ClippingImageView) view.findViewById(R.id.preview_image); pager = (ViewPager) view.findViewById(R.id.pager); + pager.setOnPageChangeListener(presenter); + + AndroidUtils.waitForMeasure(view, new AndroidUtils.OnMeasuredCallback() { + @Override + public boolean onMeasured(View view) { + presenter.onViewMeasured(); + return true; + } + }); } @Override @@ -73,16 +88,38 @@ public class ImageViewerController extends Controller implements View.OnClickLis return true; } - public void setCallback(Callback callback) { - this.callback = callback; + public void setPreviewCallback(PreviewCallback previewCallback) { + this.previewCallback = previewCallback; } public ImageViewerPresenter getPresenter() { return presenter; } + public void setPreviewVisibility(boolean visible) { + previewImage.setVisibility(visible ? View.VISIBLE : View.GONE); + } + + public void setPagerVisiblity(boolean visible) { + pager.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); + } + + public void setPagerItems(List images, int initialIndex) { + ImageViewerAdapter adapter = new ImageViewerAdapter(context, images, presenter); + pager.setAdapter(adapter); + pager.setCurrentItem(initialIndex); + } + + public void setImageMode(PostImage postImage, MultiImageView.Mode mode) { + ((ImageViewerAdapter) pager.getAdapter()).setMode(postImage, mode); + } + + public MultiImageView.Mode getImageMode(PostImage postImage) { + return ((ImageViewerAdapter) pager.getAdapter()).getMode(postImage); + } + public void startPreviewInTransition() { - ImageView previewImageView = callback.getPreviewImageStartView(this); + ImageView previewImageView = previewCallback.getPreviewImageStartView(this); previewImage.setImageDrawable(previewImageView.getDrawable()); Rect startBounds = getImageViewBounds(previewImageView); @@ -148,7 +185,7 @@ public class ImageViewerController extends Controller implements View.OnClickLis startPreviewAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { - callback.onPreviewCreate(ImageViewerController.this); + previewCallback.onPreviewCreate(ImageViewerController.this); } @Override @@ -247,7 +284,7 @@ public class ImageViewerController extends Controller implements View.OnClickLis private void previewOutAnimationEnded() { setBackgroundAlpha(0f); - callback.onPreviewDestroy(this); + previewCallback.onPreviewDestroy(this); navigationController.stopPresenting(false); } @@ -293,14 +330,14 @@ public class ImageViewerController extends Controller implements View.OnClickLis } private ImageView getStartImageView() { - return callback.getPreviewImageStartView(this); + return previewCallback.getPreviewImageStartView(this); } private Window getWindow() { return ((Activity) context).getWindow(); } - public interface Callback { + public interface PreviewCallback { public ImageView getPreviewImageStartView(ImageViewerController imageViewerController); public void onPreviewCreate(ImageViewerController imageViewerController); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerNavigationController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerNavigationController.java index 4aa3f706..77c455ea 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerNavigationController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerNavigationController.java @@ -1,14 +1,12 @@ package org.floens.chan.ui.controller; import android.content.Context; -import android.view.View; import android.widget.FrameLayout; import org.floens.chan.R; import org.floens.chan.controller.NavigationController; import org.floens.chan.core.model.PostImage; import org.floens.chan.ui.toolbar.Toolbar; -import org.floens.chan.utils.AndroidUtils; import java.util.List; @@ -33,14 +31,8 @@ public class ImageViewerNavigationController extends NavigationController { pushController(imageViewerController, false); } - public void showImages(final List images, final int index, final ImageViewerController.Callback callback) { - AndroidUtils.waitForMeasure(imageViewerController.view, new AndroidUtils.OnMeasuredCallback() { - @Override - public boolean onMeasured(View view) { - imageViewerController.setCallback(callback); - imageViewerController.getPresenter().showImages(images, index); - return true; - } - }); + public void showImages(final List images, final int index, final ImageViewerController.PreviewCallback previewCallback) { + imageViewerController.setPreviewCallback(previewCallback); + imageViewerController.getPresenter().showImages(images, index); } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java index 93608144..07cf7b0d 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java @@ -10,7 +10,7 @@ import org.floens.chan.ui.layout.ThreadLayout; import java.util.List; -public abstract class ThreadController extends Controller implements ThreadLayout.ThreadLayoutCallback, ImageViewerController.Callback { +public abstract class ThreadController extends Controller implements ThreadLayout.ThreadLayoutCallback, ImageViewerController.PreviewCallback { protected ThreadLayout threadLayout; private ImageView presentingImageView; @@ -24,11 +24,14 @@ public abstract class ThreadController extends Controller implements ThreadLayou @Override public void showImages(List images, int index, final ImageView thumbnail) { - presentingImageView = thumbnail; - - final ImageViewerNavigationController imageViewerNavigationController = new ImageViewerNavigationController(context); - presentController(imageViewerNavigationController, false); - imageViewerNavigationController.showImages(images, index, this); + // Just ignore the showImages request when the image is not loaded + if (thumbnail.getDrawable() != null) { + presentingImageView = thumbnail; + + final ImageViewerNavigationController imageViewerNavigationController = new ImageViewerNavigationController(context); + presentController(imageViewerNavigationController, false); + imageViewerNavigationController.showImages(images, index, this); + } } @Override diff --git a/Clover/app/src/main/java/org/floens/chan/ui/fragment/ImageViewFragment.java b/Clover/app/src/main/java/org/floens/chan/ui/fragment/ImageViewFragment.java index 19e6a515..cc4f5ce5 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/fragment/ImageViewFragment.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/fragment/ImageViewFragment.java @@ -280,6 +280,11 @@ public class ImageViewFragment extends Fragment implements Callback { } } + @Override + public void onModeLoaded(MultiImageView multiImageView, MultiImageView.Mode mode) { + + } + private void showVideoWarning() { LinearLayout notice = new LinearLayout(context); notice.setOrientation(LinearLayout.VERTICAL); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/view/LoadView.java b/Clover/app/src/main/java/org/floens/chan/ui/view/LoadView.java index ac49fb5c..bddaa9c5 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/view/LoadView.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/view/LoadView.java @@ -81,43 +81,54 @@ public class LoadView extends FrameLayout { newView = progressBar; } - // Readded while still running a add/remove animation for the new view - // This also removes the new view from this view - AnimatorSet out = animatorsOut.remove(newView); - if (out != null) { - out.cancel(); - } - - AnimatorSet in = animatorsIn.remove(newView); - if (in != null) { - in.cancel(); - } + if (animate) { + // Readded while still running a add/remove animation for the new view + // This also removes the new view from this view + AnimatorSet out = animatorsOut.remove(newView); + if (out != null) { + out.cancel(); + } - // Add fade out animations for all remaining view - for (int i = 0; i < getChildCount(); i++) { - View child = getChildAt(i); - if (child != null) { - AnimatorSet inSet = animatorsIn.remove(child); - if (inSet != null) { - inSet.cancel(); - } + AnimatorSet in = animatorsIn.remove(newView); + if (in != null) { + in.cancel(); + } - if (!animatorsOut.containsKey(child)) { - animateViewOut(child); + // Add fade out animations for all remaining view + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (child != null) { + AnimatorSet inSet = animatorsIn.remove(child); + if (inSet != null) { + inSet.cancel(); + } + + if (!animatorsOut.containsKey(child)) { + animateViewOut(child); + } } } - } - addView(newView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + addView(newView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - if (animate) { // Fade view in if (newView.getAlpha() == 1f) { newView.setAlpha(0f); } animateViewIn(newView); } else { - newView.setAlpha(1f); + for (AnimatorSet set : animatorsIn.values()) { + set.cancel(); + } + animatorsIn.clear(); + + for (AnimatorSet set : animatorsOut.values()) { + set.cancel(); + } + animatorsOut.clear(); + + removeAllViews(); + addView(newView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/view/MultiImageView.java b/Clover/app/src/main/java/org/floens/chan/ui/view/MultiImageView.java index 37fed5f9..3ca58706 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/view/MultiImageView.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/view/MultiImageView.java @@ -49,14 +49,20 @@ import pl.droidsonroids.gif.GifDrawable; import pl.droidsonroids.gif.GifImageView; public class MultiImageView extends LoadView implements View.OnClickListener { + public enum Mode { + UNLOADED, LOWRES, BIGIMAGE + } + private static final String TAG = "MultiImageView"; private PostImage postImage; private Callback callback; - private boolean thumbnailNeeded = true; + private Mode mode = Mode.UNLOADED; + private boolean thumbnailNeeded = true; private Future request; + private VideoView videoView; public MultiImageView(Context context) { @@ -83,11 +89,36 @@ public class MultiImageView extends LoadView implements View.OnClickListener { this.callback = callback; } - public void loadLowRes() { + public PostImage getPostImage() { + return postImage; } - public void loadHighRes() { + public void setMode(Mode mode) { + if (this.mode != mode) { + this.mode = mode; + if (mode == Mode.LOWRES) { + Logger.d(TAG, "Changing mode to LOWRES for " + postImage.thumbnailUrl); + AndroidUtils.waitForMeasure(this, new AndroidUtils.OnMeasuredCallback() { + @Override + public boolean onMeasured(View view) { + setThumbnail(postImage.thumbnailUrl); + return false; + } + }); + } else if (mode == Mode.BIGIMAGE) { + Logger.d(TAG, "Changing mode to BIGIMAGE for " + postImage.thumbnailUrl); + // Always done after at least LOWRES, so the view is measured + if (postImage.type == PostImage.Type.STATIC) { + setBigImage(postImage.imageUrl); + } else { + Logger.e(TAG, "postImage type not STATIC, not changing to BIGIMAGE mode!"); + } + } + } + } + public Mode getMode() { + return mode; } public void setCallback(Callback callback) { @@ -114,6 +145,7 @@ public class MultiImageView extends LoadView implements View.OnClickListener { thumbnail.setImageBitmap(response.getBitmap()); thumbnail.setLayoutParams(AndroidUtils.MATCH_PARAMS); setView(thumbnail, false); + callback.onModeLoaded(MultiImageView.this, Mode.LOWRES); } } }, getWidth(), getHeight()); @@ -121,7 +153,7 @@ public class MultiImageView extends LoadView implements View.OnClickListener { public void setBigImage(String imageUrl) { if (getWidth() == 0 || getHeight() == 0) { - Logger.e(TAG, "getWidth() or getHeight() returned 0, not loading"); + Logger.e(TAG, "getWidth() or getHeight() returned 0, not loading big image"); return; } @@ -166,6 +198,7 @@ public class MultiImageView extends LoadView implements View.OnClickListener { removeAllViews(); addView(image); callback.setProgress(MultiImageView.this, false); + callback.onModeLoaded(MultiImageView.this, Mode.BIGIMAGE); } @Override @@ -347,6 +380,8 @@ public class MultiImageView extends LoadView implements View.OnClickListener { public void onVideoLoaded(MultiImageView multiImageView); public void onVideoError(MultiImageView multiImageView, File video); + + public void onModeLoaded(MultiImageView multiImageView, Mode mode); } public static class NoMusicServiceCommandContext extends ContextWrapper { diff --git a/Clover/app/src/main/java/org/floens/chan/ui/view/ViewPagerAdapter.java b/Clover/app/src/main/java/org/floens/chan/ui/view/ViewPagerAdapter.java index 1ac0dbf3..2575f617 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/view/ViewPagerAdapter.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/view/ViewPagerAdapter.java @@ -9,12 +9,16 @@ import org.floens.chan.utils.AndroidUtils; public abstract class ViewPagerAdapter extends PagerAdapter { @Override public Object instantiateItem(ViewGroup container, int position) { - return getView(position); + View view = getView(position); + + container.addView(view); + + return view; } @Override public void destroyItem(ViewGroup container, int position, Object object) { - AndroidUtils.removeFromParentView((View) object); + container.removeView((View)object); } public boolean isViewFromObject(View view, Object object) { diff --git a/Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java b/Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java index cc44471b..1ad91139 100644 --- a/Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java +++ b/Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java @@ -201,11 +201,12 @@ public class AndroidUtils { observer.removeOnPreDrawListener(this); } - boolean ret = false; + boolean ret; try { ret = callback.onMeasured(view); } catch (Exception e) { Log.i("AndroidUtils", "Exception in onMeasured", e); + throw e; } return ret; diff --git a/Clover/app/src/main/res/layout/controller_image_viewer.xml b/Clover/app/src/main/res/layout/controller_image_viewer.xml index 76304c8d..0593486d 100644 --- a/Clover/app/src/main/res/layout/controller_image_viewer.xml +++ b/Clover/app/src/main/res/layout/controller_image_viewer.xml @@ -8,8 +8,9 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> -