functional progressbars for glide-loaded images in headlines buffer
This commit is contained in:
parent
7e16eb5022
commit
89c57fca2d
@ -28,6 +28,10 @@ android {
|
||||
dependencies {
|
||||
compile project(':taskerlocaleapi')
|
||||
compile files('libs/dashclock-api-r1.1.jar')
|
||||
compile 'com.squareup.okhttp3:okhttp:3.8.0'
|
||||
compile('com.github.bumptech.glide:okhttp3-integration:1.5.0') {
|
||||
exclude group: 'glide-parent'
|
||||
}
|
||||
compile 'org.jsoup:jsoup:1.10.2'
|
||||
compile 'com.bogdwellers:pinchtozoom:0.1'
|
||||
compile 'com.github.bumptech.glide:glide:3.8.0'
|
||||
@ -44,8 +48,6 @@ dependencies {
|
||||
compile 'com.ToxicBakery.viewpager.transforms:view-pager-transforms:1.2.32@aar'
|
||||
compile 'me.relex:circleindicator:1.2.2@aar'
|
||||
compile 'com.viewpagerindicator:library:2.4.1'
|
||||
//compile 'com.nhaarman.listviewanimations:lib-core:3.1.0@aar'
|
||||
//compile 'com.nhaarman.listviewanimations:lib-manipulation:3.1.0@aar'
|
||||
compile 'com.nineoldandroids:library:2.4.0'
|
||||
compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
||||
compile files('libs/YouTubeAndroidPlayerApi.jar')
|
||||
|
@ -1,8 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="org.fox.ttrss"
|
||||
android:versionCode="436"
|
||||
android:versionName="1.202" >
|
||||
android:versionName="1.202">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
@ -18,6 +19,12 @@
|
||||
android:hardwareAccelerated="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name" >
|
||||
|
||||
<!-- <meta-data android:name="com.bumptech.glide.integration.okhttp3.OkHttpGlideModule"
|
||||
tools:node="remove" /> -->
|
||||
<meta-data android:name="org.fox.ttrss.util.OkHttpProgressGlideModule"
|
||||
android:value="GlideModule" />
|
||||
|
||||
<activity
|
||||
android:name=".LaunchActivity"
|
||||
android:label="@string/app_name"
|
||||
|
@ -64,6 +64,8 @@ import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
|
||||
import com.bumptech.glide.request.RequestListener;
|
||||
import com.bumptech.glide.request.target.GlideDrawableImageViewTarget;
|
||||
import com.bumptech.glide.request.target.ImageViewTarget;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.shamanland.fab.FloatingActionButton;
|
||||
@ -74,6 +76,7 @@ import org.fox.ttrss.types.ArticleList;
|
||||
import org.fox.ttrss.types.Feed;
|
||||
import org.fox.ttrss.util.HeaderViewRecyclerAdapter;
|
||||
import org.fox.ttrss.util.HeadlinesRequest;
|
||||
import org.fox.ttrss.util.ProgressTarget;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
@ -777,6 +780,7 @@ public class HeadlinesFragment extends Fragment {
|
||||
public TextureView flavorVideoView;
|
||||
//public int position;
|
||||
public boolean flavorImageEmbedded;
|
||||
public ProgressTarget<String, GlideDrawable> flavorProgressTarget;
|
||||
|
||||
public ArticleViewHolder(View v) {
|
||||
super(v);
|
||||
@ -804,14 +808,44 @@ public class HeadlinesFragment extends Fragment {
|
||||
topChangedMessage = v.findViewById(R.id.headlines_row_top_changed);
|
||||
flavorImageOverflow = v.findViewById(R.id.gallery_overflow);
|
||||
flavorVideoView = (TextureView) v.findViewById(R.id.flavor_video);
|
||||
|
||||
if (flavorImageView != null && flavorImageLoadingBar != null) {
|
||||
flavorProgressTarget = new FlavorProgressTarget<>(new GlideDrawableImageViewTarget(flavorImageView), flavorImageLoadingBar);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearAnimation() {
|
||||
view.clearAnimation();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class FlavorProgressTarget<Z> extends ProgressTarget<String, Z> {
|
||||
private final ProgressBar progress;
|
||||
public FlavorProgressTarget(Target<Z> target, ProgressBar progress) {
|
||||
super(target);
|
||||
this.progress = progress;
|
||||
}
|
||||
|
||||
@Override public float getGranualityPercentage() {
|
||||
return 0.1f; // this matches the format string for #text below
|
||||
}
|
||||
|
||||
@Override protected void onConnecting() {
|
||||
progress.setIndeterminate(true);
|
||||
progress.setVisibility(View.VISIBLE);
|
||||
}
|
||||
@Override protected void onDownloading(long bytesRead, long expectedLength) {
|
||||
progress.setIndeterminate(false);
|
||||
progress.setProgress((int)(100 * bytesRead / expectedLength));
|
||||
}
|
||||
@Override protected void onDownloaded() {
|
||||
progress.setIndeterminate(true);
|
||||
}
|
||||
@Override protected void onDelivered() {
|
||||
progress.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private class ArticleListAdapter extends RecyclerView.Adapter<ArticleViewHolder> {
|
||||
private ArrayList<Article> items;
|
||||
|
||||
@ -1072,6 +1106,7 @@ public class HeadlinesFragment extends Fragment {
|
||||
|
||||
/* reset to default in case of convertview */
|
||||
holder.flavorImageLoadingBar.setVisibility(View.GONE);
|
||||
holder.flavorImageLoadingBar.setIndeterminate(false);
|
||||
holder.flavorImageView.setVisibility(View.GONE);
|
||||
holder.flavorVideoKindView.setVisibility(View.GONE);
|
||||
holder.flavorImageOverflow.setVisibility(View.GONE);
|
||||
@ -1155,17 +1190,18 @@ public class HeadlinesFragment extends Fragment {
|
||||
//Log.d(TAG, "TAG:" + holder.flavorImageOverflow.getTag());
|
||||
|
||||
if (!article.flavorImageUri.equals(holder.flavorImageOverflow.getTag())) {
|
||||
holder.flavorImageLoadingBar.setVisibility(View.VISIBLE);
|
||||
holder.flavorImageLoadingBar.setIndeterminate(true);
|
||||
//holder.flavorImageLoadingBar.setVisibility(View.VISIBLE);
|
||||
//holder.flavorImageLoadingBar.setIndeterminate(true);
|
||||
|
||||
holder.flavorImageView.setMaxHeight((int)(m_screenHeight * 0.8f));
|
||||
holder.flavorProgressTarget.setModel(article.flavorImageUri);
|
||||
|
||||
Glide.with(HeadlinesFragment.this)
|
||||
.load(article.flavorImageUri)
|
||||
.dontAnimate()
|
||||
.dontTransform()
|
||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||
.skipMemoryCache(false)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.skipMemoryCache(true)
|
||||
.listener(new RequestListener<String, GlideDrawable>() {
|
||||
@Override
|
||||
public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
|
||||
@ -1201,7 +1237,7 @@ public class HeadlinesFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
})
|
||||
.into(holder.flavorImageView);
|
||||
.into(holder.flavorProgressTarget);
|
||||
} else {
|
||||
holder.flavorImageOverflow.setVisibility(View.VISIBLE);
|
||||
|
||||
|
@ -0,0 +1,160 @@
|
||||
package org.fox.ttrss.util;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.*;
|
||||
|
||||
import com.bumptech.glide.*;
|
||||
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader;
|
||||
import com.bumptech.glide.load.model.GlideUrl;
|
||||
import com.bumptech.glide.module.GlideModule;
|
||||
|
||||
import okhttp3.*;
|
||||
import okio.*;
|
||||
|
||||
public class OkHttpProgressGlideModule implements GlideModule {
|
||||
@Override public void applyOptions(Context context, GlideBuilder builder) {
|
||||
|
||||
}
|
||||
@Override public void registerComponents(Context context, Glide glide) {
|
||||
OkHttpClient client = new OkHttpClient.Builder()
|
||||
.addNetworkInterceptor(createInterceptor(new DispatchingProgressListener()))
|
||||
.build();
|
||||
glide.register(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(client));
|
||||
}
|
||||
|
||||
private static Interceptor createInterceptor(final ResponseProgressListener listener) {
|
||||
return new Interceptor() {
|
||||
@Override public Response intercept(Chain chain) throws IOException {
|
||||
Request request = chain.request();
|
||||
Response response = chain.proceed(request);
|
||||
return response.newBuilder()
|
||||
.body(new OkHttpProgressResponseBody(request.url(), response.body(), listener))
|
||||
.build();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public interface UIProgressListener {
|
||||
void onProgress(long bytesRead, long expectedLength);
|
||||
/**
|
||||
* Control how often the listener needs an update. 0% and 100% will always be dispatched.
|
||||
* @return in percentage (0.2 = call {@link #onProgress} around every 0.2 percent of progress)
|
||||
*/
|
||||
float getGranualityPercentage();
|
||||
}
|
||||
|
||||
public static void forget(String url) {
|
||||
DispatchingProgressListener.forget(url);
|
||||
}
|
||||
public static void expect(String url, UIProgressListener listener) {
|
||||
DispatchingProgressListener.expect(url, listener);
|
||||
}
|
||||
|
||||
private interface ResponseProgressListener {
|
||||
void update(HttpUrl url, long bytesRead, long contentLength);
|
||||
}
|
||||
|
||||
private static class DispatchingProgressListener implements ResponseProgressListener {
|
||||
private static final Map<String, UIProgressListener> LISTENERS = new HashMap<>();
|
||||
private static final Map<String, Long> PROGRESSES = new HashMap<>();
|
||||
|
||||
private final Handler handler;
|
||||
DispatchingProgressListener() {
|
||||
this.handler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
static void forget(String url) {
|
||||
LISTENERS.remove(url);
|
||||
PROGRESSES.remove(url);
|
||||
}
|
||||
static void expect(String url, UIProgressListener listener) {
|
||||
LISTENERS.put(url, listener);
|
||||
}
|
||||
|
||||
@Override public void update(HttpUrl url, final long bytesRead, final long contentLength) {
|
||||
//System.out.printf("%s: %d/%d = %.2f%%%n", url, bytesRead, contentLength, (100f * bytesRead) / contentLength);
|
||||
|
||||
String key = url.toString();
|
||||
final UIProgressListener listener = LISTENERS.get(key);
|
||||
|
||||
if (listener == null) {
|
||||
return;
|
||||
}
|
||||
if (contentLength <= bytesRead) {
|
||||
forget(key);
|
||||
}
|
||||
if (needsDispatch(key, bytesRead, contentLength, listener.getGranualityPercentage())) {
|
||||
handler.post(new Runnable() {
|
||||
@Override public void run() {
|
||||
listener.onProgress(bytesRead, contentLength);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private boolean needsDispatch(String key, long current, long total, float granularity) {
|
||||
if (granularity == 0 || current == 0 || total == current) {
|
||||
return true;
|
||||
}
|
||||
float percent = 100f * current / total;
|
||||
long currentProgress = (long)(percent / granularity);
|
||||
Long lastProgress = PROGRESSES.get(key);
|
||||
if (lastProgress == null || currentProgress != lastProgress) {
|
||||
PROGRESSES.put(key, currentProgress);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class OkHttpProgressResponseBody extends ResponseBody {
|
||||
private final HttpUrl url;
|
||||
private final ResponseBody responseBody;
|
||||
private final ResponseProgressListener progressListener;
|
||||
private BufferedSource bufferedSource;
|
||||
|
||||
OkHttpProgressResponseBody(HttpUrl url, ResponseBody responseBody,
|
||||
ResponseProgressListener progressListener) {
|
||||
|
||||
this.url = url;
|
||||
this.responseBody = responseBody;
|
||||
this.progressListener = progressListener;
|
||||
}
|
||||
|
||||
@Override public MediaType contentType() {
|
||||
return responseBody.contentType();
|
||||
}
|
||||
|
||||
@Override public long contentLength() {
|
||||
return responseBody.contentLength();
|
||||
}
|
||||
|
||||
@Override public BufferedSource source() {
|
||||
if (bufferedSource == null) {
|
||||
bufferedSource = Okio.buffer(source(responseBody.source()));
|
||||
}
|
||||
return bufferedSource;
|
||||
}
|
||||
|
||||
private Source source(Source source) {
|
||||
return new ForwardingSource(source) {
|
||||
long totalBytesRead = 0L;
|
||||
@Override public long read(Buffer sink, long byteCount) throws IOException {
|
||||
long bytesRead = super.read(sink, byteCount);
|
||||
long fullLength = responseBody.contentLength();
|
||||
if (bytesRead == -1) { // this source is exhausted
|
||||
totalBytesRead = fullLength;
|
||||
} else {
|
||||
totalBytesRead += bytesRead;
|
||||
}
|
||||
progressListener.update(url, totalBytesRead, fullLength);
|
||||
return bytesRead;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
package org.fox.ttrss.util;
|
||||
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.animation.GlideAnimation;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
|
||||
public abstract class ProgressTarget<T, Z> extends WrappingTarget<Z> implements OkHttpProgressGlideModule.UIProgressListener {
|
||||
private T model;
|
||||
private boolean ignoreProgress = true;
|
||||
public ProgressTarget(Target<Z> target) {
|
||||
this(null, target);
|
||||
}
|
||||
public ProgressTarget(T model, Target<Z> target) {
|
||||
super(target);
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public final T getModel() {
|
||||
return model;
|
||||
}
|
||||
public final void setModel(T model) {
|
||||
Glide.clear(this); // indirectly calls cleanup
|
||||
this.model = model;
|
||||
}
|
||||
/**
|
||||
* Convert a model into an Url string that is used to match up the OkHttp requests. For explicit
|
||||
* {@link com.bumptech.glide.load.model.GlideUrl GlideUrl} loads this needs to return
|
||||
* {@link com.bumptech.glide.load.model.GlideUrl#toStringUrl toStringUrl}. For custom models do the same as your
|
||||
* {@link com.bumptech.glide.load.model.stream.BaseGlideUrlLoader BaseGlideUrlLoader} does.
|
||||
* @param model return the representation of the given model, DO NOT use {@link #getModel()} inside this method.
|
||||
* @return a stable Url representation of the model, otherwise the progress reporting won't work
|
||||
*/
|
||||
protected String toUrlString(T model) {
|
||||
return String.valueOf(model);
|
||||
}
|
||||
|
||||
@Override public float getGranualityPercentage() {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
@Override public void onProgress(long bytesRead, long expectedLength) {
|
||||
if (ignoreProgress) {
|
||||
return;
|
||||
}
|
||||
if (expectedLength == Long.MAX_VALUE) {
|
||||
onConnecting();
|
||||
} else if (bytesRead == expectedLength) {
|
||||
onDownloaded();
|
||||
} else {
|
||||
onDownloading(bytesRead, expectedLength);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the Glide load has started.
|
||||
* At this time it is not known if the Glide will even go and use the network to fetch the image.
|
||||
*/
|
||||
protected abstract void onConnecting();
|
||||
/**
|
||||
* Called when there's any progress on the download; not called when loading from cache.
|
||||
* At this time we know how many bytes have been transferred through the wire.
|
||||
*/
|
||||
protected abstract void onDownloading(long bytesRead, long expectedLength);
|
||||
/**
|
||||
* Called when the bytes downloaded reach the length reported by the server; not called when loading from cache.
|
||||
* At this time it is fairly certain, that Glide either finished reading the stream.
|
||||
* This means that the image was either already decoded or saved the network stream to cache.
|
||||
* In the latter case there's more work to do: decode the image from cache and transform.
|
||||
* These cannot be listened to for progress so it's unsure how fast they'll be, best to show indeterminate progress.
|
||||
*/
|
||||
protected abstract void onDownloaded();
|
||||
/**
|
||||
* Called when the Glide load has finished either by successfully loading the image or failing to load or cancelled.
|
||||
* In any case the best is to hide/reset any progress displays.
|
||||
*/
|
||||
protected abstract void onDelivered();
|
||||
|
||||
private void start() {
|
||||
OkHttpProgressGlideModule.expect(toUrlString(model), this);
|
||||
ignoreProgress = false;
|
||||
onProgress(0, Long.MAX_VALUE);
|
||||
}
|
||||
private void cleanup() {
|
||||
ignoreProgress = true;
|
||||
T model = this.model; // save in case it gets modified
|
||||
onDelivered();
|
||||
OkHttpProgressGlideModule.forget(toUrlString(model));
|
||||
this.model = null;
|
||||
}
|
||||
|
||||
@Override public void onLoadStarted(Drawable placeholder) {
|
||||
super.onLoadStarted(placeholder);
|
||||
start();
|
||||
}
|
||||
@Override public void onResourceReady(Z resource, GlideAnimation<? super Z> animation) {
|
||||
cleanup();
|
||||
super.onResourceReady(resource, animation);
|
||||
}
|
||||
@Override public void onLoadFailed(Exception e, Drawable errorDrawable) {
|
||||
cleanup();
|
||||
super.onLoadFailed(e, errorDrawable);
|
||||
}
|
||||
@Override public void onLoadCleared(Drawable placeholder) {
|
||||
cleanup();
|
||||
super.onLoadCleared(placeholder);
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package org.fox.ttrss.util;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.bumptech.glide.request.Request;
|
||||
import com.bumptech.glide.request.animation.GlideAnimation;
|
||||
import com.bumptech.glide.request.target.*;
|
||||
|
||||
public class WrappingTarget<Z> implements Target<Z> {
|
||||
protected final @NonNull Target<? super Z> target;
|
||||
public WrappingTarget(@NonNull Target<? super Z> target) {
|
||||
this.target = target;
|
||||
}
|
||||
public @NonNull Target<? super Z> getWrappedTarget() {
|
||||
return target;
|
||||
}
|
||||
@Override public void getSize(SizeReadyCallback cb) {
|
||||
target.getSize(cb);
|
||||
}
|
||||
|
||||
@Override public void onLoadStarted(Drawable placeholder) {
|
||||
target.onLoadStarted(placeholder);
|
||||
}
|
||||
@Override public void onLoadFailed(Exception e, Drawable errorDrawable) {
|
||||
target.onLoadFailed(e, errorDrawable);
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override public void onResourceReady(Z resource, GlideAnimation<? super Z> glideAnimation) {
|
||||
target.onResourceReady(resource, (GlideAnimation)glideAnimation);
|
||||
}
|
||||
@Override public void onLoadCleared(Drawable placeholder) {
|
||||
target.onLoadCleared(placeholder);
|
||||
}
|
||||
|
||||
@Override public Request getRequest() {
|
||||
return target.getRequest();
|
||||
}
|
||||
@Override public void setRequest(Request request) {
|
||||
target.setRequest(request);
|
||||
}
|
||||
|
||||
@Override public void onStart() {
|
||||
target.onStart();
|
||||
}
|
||||
@Override public void onStop() {
|
||||
target.onStop();
|
||||
}
|
||||
@Override public void onDestroy() {
|
||||
target.onDestroy();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user