summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xorg.fox.ttrss/build.gradle6
-rwxr-xr-xorg.fox.ttrss/src/main/AndroidManifest.xml9
-rwxr-xr-xorg.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragment.java48
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/util/OkHttpProgressGlideModule.java160
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/util/ProgressTarget.java110
-rw-r--r--org.fox.ttrss/src/main/java/org/fox/ttrss/util/WrappingTarget.java52
6 files changed, 376 insertions, 9 deletions
diff --git a/org.fox.ttrss/build.gradle b/org.fox.ttrss/build.gradle
index 7f48ed19..cabd2045 100755
--- a/org.fox.ttrss/build.gradle
+++ b/org.fox.ttrss/build.gradle
@@ -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')
diff --git a/org.fox.ttrss/src/main/AndroidManifest.xml b/org.fox.ttrss/src/main/AndroidManifest.xml
index 8e73e336..fd76ca34 100755
--- a/org.fox.ttrss/src/main/AndroidManifest.xml
+++ b/org.fox.ttrss/src/main/AndroidManifest.xml
@@ -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"
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragment.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragment.java
index 85547f1e..1682d1ff 100755
--- a/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragment.java
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/HeadlinesFragment.java
@@ -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);
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/util/OkHttpProgressGlideModule.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/util/OkHttpProgressGlideModule.java
new file mode 100644
index 00000000..b333807e
--- /dev/null
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/util/OkHttpProgressGlideModule.java
@@ -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;
+ }
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/util/ProgressTarget.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/util/ProgressTarget.java
new file mode 100644
index 00000000..df02865c
--- /dev/null
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/util/ProgressTarget.java
@@ -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);
+ }
+} \ No newline at end of file
diff --git a/org.fox.ttrss/src/main/java/org/fox/ttrss/util/WrappingTarget.java b/org.fox.ttrss/src/main/java/org/fox/ttrss/util/WrappingTarget.java
new file mode 100644
index 00000000..5dcfa7ec
--- /dev/null
+++ b/org.fox.ttrss/src/main/java/org/fox/ttrss/util/WrappingTarget.java
@@ -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();
+ }
+} \ No newline at end of file