Only display one highres version at a time.

tempwork
Floens 10 years ago
parent 639d5cd8d3
commit 29f564ad95
  1. 85
      Clover/app/src/main/java/org/floens/chan/core/presenter/ImageViewerPresenter.java
  2. 2
      Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java
  3. 7
      Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java
  4. 95
      Clover/app/src/main/java/org/floens/chan/ui/view/MultiImageView.java
  5. 32
      Clover/app/src/main/java/org/floens/chan/ui/view/OptionalSwipeViewPager.java
  6. 2
      Clover/app/src/main/res/layout/controller_image_viewer.xml

@ -1,16 +1,20 @@
package org.floens.chan.core.presenter; package org.floens.chan.core.presenter;
import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager;
import android.util.Log;
import org.floens.chan.core.model.PostImage; import org.floens.chan.core.model.PostImage;
import org.floens.chan.core.settings.ChanSettings; import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.view.MultiImageView; import org.floens.chan.ui.view.MultiImageView;
import org.floens.chan.utils.Logger;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class ImageViewerPresenter implements MultiImageView.Callback, ViewPager.OnPageChangeListener { public class ImageViewerPresenter implements MultiImageView.Callback, ViewPager.OnPageChangeListener {
private static final String TAG = "ImageViewerPresenter";
private final Callback callback; private final Callback callback;
private final boolean imageAutoLoad = ChanSettings.imageAutoLoad.get(); private final boolean imageAutoLoad = ChanSettings.imageAutoLoad.get();
@ -18,21 +22,25 @@ public class ImageViewerPresenter implements MultiImageView.Callback, ViewPager.
private boolean entering = true; private boolean entering = true;
private boolean exiting = false; private boolean exiting = false;
private List<PostImage> images; private List<PostImage> images;
private int selectedIndex; private int selectedPosition;
private boolean initalLowResLoaded = false;
// Disables swiping until the view pager is visible
private boolean viewPagerVisible = false;
private boolean changeViewsOnInTransitionEnd = false; private boolean changeViewsOnInTransitionEnd = false;
public ImageViewerPresenter(Callback callback) { public ImageViewerPresenter(Callback callback) {
this.callback = callback; this.callback = callback;
} }
public void showImages(List<PostImage> images, int index) { public void showImages(List<PostImage> images, int position) {
this.images = images; this.images = images;
selectedIndex = index; selectedPosition = position;
Logger.test("showImages position " + position);
// Do this before the view is measured, to avoid it to always loading the first two pages // Do this before the view is measured, to avoid it to always loading the first two pages
callback.setPagerItems(images, selectedIndex); callback.setPagerItems(images, selectedPosition);
callback.setImageMode(images.get(selectedIndex), MultiImageView.Mode.LOWRES); callback.setImageMode(images.get(selectedPosition), MultiImageView.Mode.LOWRES);
} }
public void onViewMeasured() { public void onViewMeasured() {
@ -60,14 +68,15 @@ public class ImageViewerPresenter implements MultiImageView.Callback, ViewPager.
@Override @Override
public void onPageSelected(int position) { public void onPageSelected(int position) {
selectedIndex = position; Logger.test("onPageSelected " + selectedPosition + ", " + position);
if (initalLowResLoaded) {
for (PostImage other : getOther(selectedIndex)) { if (!viewPagerVisible) {
callback.setImageMode(other, MultiImageView.Mode.LOWRES); return;
}
callback.setImageMode(images.get(selectedIndex), MultiImageView.Mode.LOWRES);
} }
// onModeLoaded will handle the else case
selectedPosition = position;
onPageSwipedTo(position);
} }
@Override @Override
@ -83,8 +92,8 @@ public class ImageViewerPresenter implements MultiImageView.Callback, ViewPager.
if (mode == MultiImageView.Mode.LOWRES) { if (mode == MultiImageView.Mode.LOWRES) {
// lowres is requested at the beginning of the transition, // lowres is requested at the beginning of the transition,
// the lowres is loaded before the in transition or after // the lowres is loaded before the in transition or after
if (!initalLowResLoaded) { if (!viewPagerVisible) {
initalLowResLoaded = true; viewPagerVisible = true;
if (!entering) { if (!entering) {
// Entering transition was already ended, switch now // Entering transition was already ended, switch now
callback.setPreviewVisibility(false); callback.setPreviewVisibility(false);
@ -94,25 +103,50 @@ public class ImageViewerPresenter implements MultiImageView.Callback, ViewPager.
changeViewsOnInTransitionEnd = true; changeViewsOnInTransitionEnd = true;
} }
// Transition ended or not, request loading the other side views to lowres // Transition ended or not, request loading the other side views to lowres
for (PostImage other : getOther(selectedIndex)) { for (PostImage other : getOther(selectedPosition, false)) {
callback.setImageMode(other, MultiImageView.Mode.LOWRES); callback.setImageMode(other, MultiImageView.Mode.LOWRES);
} }
// selectedIndex can be different than the initial one because of page changes before onModeLoaded was called, onLowResInCenter();
// request a load of the current selectedIndex one here } else {
callback.setImageMode(images.get(selectedIndex), MultiImageView.Mode.LOWRES); if (multiImageView.getPostImage() == images.get(selectedPosition)) {
Log.i(TAG, "Loading high res from a onModeLoaded");
onLowResInCenter();
}
}
}
}
private void onPageSwipedTo(int position) {
for (PostImage other : getOther(position, false)) {
callback.setImageMode(other, MultiImageView.Mode.LOWRES);
} }
// Initial load or not, transitioning or not, load the high res when the user setting says so after the low res // Already in LOWRES mode
if (imageAutoLoad) { if (callback.getImageMode(images.get(selectedPosition)) == MultiImageView.Mode.LOWRES) {
multiImageView.setMode(MultiImageView.Mode.BIGIMAGE); Log.i(TAG, "Loading high res from a swipe");
onLowResInCenter();
} }
// Else let onModeChange handle it
}
// Called from either a page swipe caused a lowres image to the center or an
// onModeLoaded when a unloaded image was swiped to the center earlier
private void onLowResInCenter() {
PostImage postImage = images.get(selectedPosition);
if (postImage.type == PostImage.Type.STATIC) {
callback.setImageMode(postImage, MultiImageView.Mode.BIGIMAGE);
} else {
// todo
} }
} }
@Override @Override
public void onTap(MultiImageView multiImageView) { public void onTap(MultiImageView multiImageView) {
// Don't mistake a swipe from a user when the pager is disabled as a tap
if (viewPagerVisible) {
onExit(); onExit();
} }
}
@Override @Override
public void setProgress(MultiImageView multiImageView, boolean progress) { public void setProgress(MultiImageView multiImageView, boolean progress) {
@ -134,11 +168,14 @@ public class ImageViewerPresenter implements MultiImageView.Callback, ViewPager.
} }
private List<PostImage> getOther(int position) { private List<PostImage> getOther(int position, boolean all) {
List<PostImage> other = new ArrayList<>(2); List<PostImage> other = new ArrayList<>(3);
if (position - 1 >= 0) { if (position - 1 >= 0) {
other.add(images.get(position - 1)); other.add(images.get(position - 1));
} }
if (all) {
other.add(images.get(position));
}
if (position + 1 < images.size()) { if (position + 1 < images.size()) {
other.add(images.get(position + 1)); other.add(images.get(position + 1));
} }

@ -143,7 +143,7 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
if (item.hasImage) { if (item.hasImage) {
images.add(new PostImage(item.thumbnailUrl, item.imageUrl, item.filename, item.ext, item.imageWidth, item.imageHeight)); images.add(new PostImage(item.thumbnailUrl, item.imageUrl, item.filename, item.ext, item.imageWidth, item.imageHeight));
if (item.no == post.no) { if (item.no == post.no) {
index = i; index = images.size() - 1;
} }
} }
} }

@ -11,7 +11,6 @@ import android.graphics.Color;
import android.graphics.Point; import android.graphics.Point;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Build; import android.os.Build;
import android.support.v4.view.ViewPager;
import android.view.View; import android.view.View;
import android.view.Window; import android.view.Window;
import android.view.animation.DecelerateInterpolator; import android.view.animation.DecelerateInterpolator;
@ -24,6 +23,7 @@ import org.floens.chan.core.presenter.ImageViewerPresenter;
import org.floens.chan.ui.adapter.ImageViewerAdapter; import org.floens.chan.ui.adapter.ImageViewerAdapter;
import org.floens.chan.ui.toolbar.Toolbar; import org.floens.chan.ui.toolbar.Toolbar;
import org.floens.chan.ui.view.ClippingImageView; import org.floens.chan.ui.view.ClippingImageView;
import org.floens.chan.ui.view.OptionalSwipeViewPager;
import org.floens.chan.ui.view.MultiImageView; import org.floens.chan.ui.view.MultiImageView;
import org.floens.chan.utils.AndroidUtils; import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.AnimationUtils; import org.floens.chan.utils.AnimationUtils;
@ -47,7 +47,7 @@ public class ImageViewerController extends Controller implements View.OnClickLis
private final Toolbar toolbar; private final Toolbar toolbar;
private ClippingImageView previewImage; private ClippingImageView previewImage;
private ViewPager pager; private OptionalSwipeViewPager pager;
public ImageViewerController(Context context, Toolbar toolbar) { public ImageViewerController(Context context, Toolbar toolbar) {
super(context); super(context);
@ -65,7 +65,7 @@ public class ImageViewerController extends Controller implements View.OnClickLis
view = inflateRes(R.layout.controller_image_viewer); view = inflateRes(R.layout.controller_image_viewer);
view.setOnClickListener(this); view.setOnClickListener(this);
previewImage = (ClippingImageView) view.findViewById(R.id.preview_image); previewImage = (ClippingImageView) view.findViewById(R.id.preview_image);
pager = (ViewPager) view.findViewById(R.id.pager); pager = (OptionalSwipeViewPager) view.findViewById(R.id.pager);
pager.setOnPageChangeListener(presenter); pager.setOnPageChangeListener(presenter);
AndroidUtils.waitForMeasure(view, new AndroidUtils.OnMeasuredCallback() { AndroidUtils.waitForMeasure(view, new AndroidUtils.OnMeasuredCallback() {
@ -102,6 +102,7 @@ public class ImageViewerController extends Controller implements View.OnClickLis
public void setPagerVisiblity(boolean visible) { public void setPagerVisiblity(boolean visible) {
pager.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); pager.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
pager.setSwipingEnabled(visible);
} }
public void setPagerItems(List<PostImage> images, int initialIndex) { public void setPagerItems(List<PostImage> images, int initialIndex) {

@ -26,6 +26,7 @@ import android.net.Uri;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.Gravity; import android.view.Gravity;
import android.view.View; import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.Toast; import android.widget.Toast;
import android.widget.VideoView; import android.widget.VideoView;
@ -33,6 +34,7 @@ import android.widget.VideoView;
import com.android.volley.VolleyError; import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader.ImageContainer; import com.android.volley.toolbox.ImageLoader.ImageContainer;
import com.koushikdutta.async.future.Future; import com.koushikdutta.async.future.Future;
import com.koushikdutta.ion.Response;
import org.floens.chan.ChanApplication; import org.floens.chan.ChanApplication;
import org.floens.chan.R; import org.floens.chan.R;
@ -48,7 +50,7 @@ import java.io.IOException;
import pl.droidsonroids.gif.GifDrawable; import pl.droidsonroids.gif.GifDrawable;
import pl.droidsonroids.gif.GifImageView; import pl.droidsonroids.gif.GifImageView;
public class MultiImageView extends LoadView implements View.OnClickListener { public class MultiImageView extends FrameLayout implements View.OnClickListener {
public enum Mode { public enum Mode {
UNLOADED, LOWRES, BIGIMAGE UNLOADED, LOWRES, BIGIMAGE
} }
@ -61,7 +63,11 @@ public class MultiImageView extends LoadView implements View.OnClickListener {
private Mode mode = Mode.UNLOADED; private Mode mode = Mode.UNLOADED;
private boolean thumbnailNeeded = true; private boolean thumbnailNeeded = true;
private Future<?> request; private boolean hasContent = false;
private ImageContainer thumbnailRequest;
private Future<Response<File>> bigImageRequest;
private Future<Response<File>> gifRequest;
private Future<Response<File>> videoRequest;
private VideoView videoView; private VideoView videoView;
@ -95,9 +101,10 @@ public class MultiImageView extends LoadView implements View.OnClickListener {
public void setMode(Mode mode) { public void setMode(Mode mode) {
if (this.mode != mode) { if (this.mode != mode) {
final Mode previousTargetMode = this.mode;
this.mode = mode; this.mode = mode;
Logger.d(TAG, "Changing mode from " + previousTargetMode + "to " + mode + " for " + postImage.thumbnailUrl);
if (mode == Mode.LOWRES) { if (mode == Mode.LOWRES) {
Logger.d(TAG, "Changing mode to LOWRES for " + postImage.thumbnailUrl);
AndroidUtils.waitForMeasure(this, new AndroidUtils.OnMeasuredCallback() { AndroidUtils.waitForMeasure(this, new AndroidUtils.OnMeasuredCallback() {
@Override @Override
public boolean onMeasured(View view) { public boolean onMeasured(View view) {
@ -106,10 +113,14 @@ public class MultiImageView extends LoadView implements View.OnClickListener {
} }
}); });
} else if (mode == Mode.BIGIMAGE) { } 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) { if (postImage.type == PostImage.Type.STATIC) {
AndroidUtils.waitForMeasure(this, new AndroidUtils.OnMeasuredCallback() {
@Override
public boolean onMeasured(View view) {
setBigImage(postImage.imageUrl); setBigImage(postImage.imageUrl);
return false;
}
});
} else { } else {
Logger.e(TAG, "postImage type not STATIC, not changing to BIGIMAGE mode!"); Logger.e(TAG, "postImage type not STATIC, not changing to BIGIMAGE mode!");
} }
@ -132,25 +143,32 @@ public class MultiImageView extends LoadView implements View.OnClickListener {
} }
// Also use volley for the thumbnails // Also use volley for the thumbnails
ChanApplication.getVolleyImageLoader().get(thumbnailUrl, new com.android.volley.toolbox.ImageLoader.ImageListener() { thumbnailRequest = ChanApplication.getVolleyImageLoader().get(thumbnailUrl, new com.android.volley.toolbox.ImageLoader.ImageListener() {
@Override @Override
public void onErrorResponse(VolleyError error) { public void onErrorResponse(VolleyError error) {
thumbnailRequest = null;
onError(); onError();
} }
@Override @Override
public void onResponse(ImageContainer response, boolean isImmediate) { public void onResponse(ImageContainer response, boolean isImmediate) {
if (response.getBitmap() != null && thumbnailNeeded) { thumbnailRequest = null;
if (response.getBitmap() != null && (!hasContent || mode == Mode.LOWRES)) {
ImageView thumbnail = new ImageView(getContext()); ImageView thumbnail = new ImageView(getContext());
thumbnail.setImageBitmap(response.getBitmap()); thumbnail.setImageBitmap(response.getBitmap());
thumbnail.setLayoutParams(AndroidUtils.MATCH_PARAMS); onModeLoaded(Mode.LOWRES, thumbnail);
setView(thumbnail, false);
callback.onModeLoaded(MultiImageView.this, Mode.LOWRES);
} }
} }
}, getWidth(), getHeight()); }, getWidth(), getHeight());
} }
private void cancelThumbnail() {
if (thumbnailRequest != null) {
thumbnailRequest.cancelRequest();
thumbnailRequest = null;
}
}
public void setBigImage(String imageUrl) { public void setBigImage(String imageUrl) {
if (getWidth() == 0 || getHeight() == 0) { if (getWidth() == 0 || getHeight() == 0) {
Logger.e(TAG, "getWidth() or getHeight() returned 0, not loading big image"); Logger.e(TAG, "getWidth() or getHeight() returned 0, not loading big image");
@ -158,7 +176,7 @@ public class MultiImageView extends LoadView implements View.OnClickListener {
} }
callback.setProgress(this, true); callback.setProgress(this, true);
request = ChanApplication.getFileCache().downloadFile(getContext(), imageUrl, new FileCache.DownloadedCallback() { bigImageRequest = ChanApplication.getFileCache().downloadFile(getContext(), imageUrl, new FileCache.DownloadedCallback() {
@Override @Override
public void onProgress(long downloaded, long total, boolean done) { public void onProgress(long downloaded, long total, boolean done) {
if (done) { if (done) {
@ -171,11 +189,13 @@ public class MultiImageView extends LoadView implements View.OnClickListener {
@Override @Override
public void onSuccess(File file) { public void onSuccess(File file) {
bigImageRequest = null;
setBigImageFile(file); setBigImageFile(file);
} }
@Override @Override
public void onFail(boolean notFound) { public void onFail(boolean notFound) {
bigImageRequest = null;
if (notFound) { if (notFound) {
onNotFoundError(); onNotFoundError();
} else { } else {
@ -195,10 +215,10 @@ public class MultiImageView extends LoadView implements View.OnClickListener {
image.setInitCallback(new CustomScaleImageView.InitedCallback() { image.setInitCallback(new CustomScaleImageView.InitedCallback() {
@Override @Override
public void onInit() { public void onInit() {
removeAllViews(); if (!hasContent || mode == Mode.BIGIMAGE) {
addView(image);
callback.setProgress(MultiImageView.this, false); callback.setProgress(MultiImageView.this, false);
callback.onModeLoaded(MultiImageView.this, Mode.BIGIMAGE); onModeLoaded(Mode.BIGIMAGE, image);
}
} }
@Override @Override
@ -208,6 +228,13 @@ public class MultiImageView extends LoadView implements View.OnClickListener {
}); });
} }
private void cancelBigImage() {
if (bigImageRequest != null) {
bigImageRequest.cancel();
bigImageRequest = null;
}
}
public void setGif(String gifUrl) { public void setGif(String gifUrl) {
if (getWidth() == 0 || getHeight() == 0) { 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");
@ -215,7 +242,7 @@ public class MultiImageView extends LoadView implements View.OnClickListener {
} }
callback.setProgress(this, true); callback.setProgress(this, true);
request = ChanApplication.getFileCache().downloadFile(getContext(), gifUrl, new FileCache.DownloadedCallback() { gifRequest = ChanApplication.getFileCache().downloadFile(getContext(), gifUrl, new FileCache.DownloadedCallback() {
@Override @Override
public void onProgress(long downloaded, long total, boolean done) { public void onProgress(long downloaded, long total, boolean done) {
if (done) { if (done) {
@ -229,11 +256,13 @@ public class MultiImageView extends LoadView implements View.OnClickListener {
@Override @Override
public void onSuccess(File file) { public void onSuccess(File file) {
gifRequest = null;
setGifFile(file); setGifFile(file);
} }
@Override @Override
public void onFail(boolean notFound) { public void onFail(boolean notFound) {
gifRequest = null;
if (notFound) { if (notFound) {
onNotFoundError(); onNotFoundError();
} else { } else {
@ -261,12 +290,12 @@ public class MultiImageView extends LoadView implements View.OnClickListener {
GifImageView view = new GifImageView(getContext()); GifImageView view = new GifImageView(getContext());
view.setImageDrawable(drawable); view.setImageDrawable(drawable);
view.setLayoutParams(AndroidUtils.MATCH_PARAMS); view.setLayoutParams(AndroidUtils.MATCH_PARAMS);
setView(view, false); setView(view);
} }
public void setVideo(String videoUrl) { public void setVideo(String videoUrl) {
callback.setProgress(this, true); callback.setProgress(this, true);
request = ChanApplication.getFileCache().downloadFile(getContext(), videoUrl, new FileCache.DownloadedCallback() { videoRequest = ChanApplication.getFileCache().downloadFile(getContext(), videoUrl, new FileCache.DownloadedCallback() {
@Override @Override
public void onProgress(long downloaded, long total, boolean done) { public void onProgress(long downloaded, long total, boolean done) {
if (done) { if (done) {
@ -280,11 +309,13 @@ public class MultiImageView extends LoadView implements View.OnClickListener {
@Override @Override
public void onSuccess(File file) { public void onSuccess(File file) {
videoRequest = null;
setVideoFile(file); setVideoFile(file);
} }
@Override @Override
public void onFail(boolean notFound) { public void onFail(boolean notFound) {
videoRequest = null;
if (notFound) { if (notFound) {
onNotFoundError(); onNotFoundError();
} else { } else {
@ -334,7 +365,7 @@ public class MultiImageView extends LoadView implements View.OnClickListener {
videoView.setVideoPath(file.getAbsolutePath()); videoView.setVideoPath(file.getAbsolutePath());
setView(videoView, false); setView(videoView);
videoView.start(); videoView.start();
} }
@ -360,8 +391,14 @@ public class MultiImageView extends LoadView implements View.OnClickListener {
} }
public void cancelLoad() { public void cancelLoad() {
if (request != null) { if (bigImageRequest != null) {
request.cancel(true); bigImageRequest.cancel();
}
if (gifRequest != null) {
gifRequest.cancel();
}
if (videoRequest != null) {
videoRequest.cancel();
} }
} }
@ -370,6 +407,24 @@ public class MultiImageView extends LoadView implements View.OnClickListener {
callback.onTap(this); callback.onTap(this);
} }
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
cancelLoad();
}
private void setView(View view) {
removeAllViews();
addView(view, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
private void onModeLoaded(Mode mode, View view) {
removeAllViews();
addView(view, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
hasContent = true;
callback.onModeLoaded(this, mode);
}
public static interface Callback { public static interface Callback {
public void onTap(MultiImageView multiImageView); public void onTap(MultiImageView multiImageView);

@ -0,0 +1,32 @@
package org.floens.chan.ui.view;
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
public class OptionalSwipeViewPager extends ViewPager {
private boolean swipingEnabled;
public OptionalSwipeViewPager(Context context) {
super(context);
}
public OptionalSwipeViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
return swipingEnabled && super.onTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return swipingEnabled && super.onInterceptTouchEvent(ev);
}
public void setSwipingEnabled(boolean swipingEnabled) {
this.swipingEnabled = swipingEnabled;
}
}

@ -8,7 +8,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
<android.support.v4.view.ViewPager <org.floens.chan.ui.view.OptionalSwipeViewPager
android:id="@+id/pager" android:id="@+id/pager"
android:visibility="invisible" android:visibility="invisible"
android:layout_width="match_parent" android:layout_width="match_parent"

Loading…
Cancel
Save