From a1182e209a3575bdf1b86962544b9e2949abeff4 Mon Sep 17 00:00:00 2001 From: Tyler Wong Date: Tue, 12 Nov 2019 22:00:33 -0800 Subject: [PATCH] Add CoilImagesPlugin --- build.gradle | 3 +- docs/docs/v4/image-coil/README.md | 24 +++ markwon-image-coil/README.md | 3 + markwon-image-coil/build.gradle | 21 ++ markwon-image-coil/gradle.properties | 4 + .../src/main/AndroidManifest.xml | 1 + .../markwon/image/coil/CoilImagesPlugin.java | 186 ++++++++++++++++++ .../sample/recycler/RecyclerActivity.java | 1 + settings.gradle | 1 + 9 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 docs/docs/v4/image-coil/README.md create mode 100644 markwon-image-coil/README.md create mode 100644 markwon-image-coil/build.gradle create mode 100644 markwon-image-coil/gradle.properties create mode 100644 markwon-image-coil/src/main/AndroidManifest.xml create mode 100644 markwon-image-coil/src/main/java/io/noties/markwon/image/coil/CoilImagesPlugin.java diff --git a/build.gradle b/build.gradle index 9f065c7a..180abe37 100644 --- a/build.gradle +++ b/build.gradle @@ -72,7 +72,8 @@ ext { 'adapt' : 'io.noties:adapt:2.0.0', 'dagger' : "com.google.dagger:dagger:$daggerVersion", 'picasso' : 'com.squareup.picasso:picasso:2.71828', - 'glide' : 'com.github.bumptech.glide:glide:4.9.0' + 'glide' : 'com.github.bumptech.glide:glide:4.9.0', + 'coil' : 'io.coil-kt:coil:0.8.0' ] deps['annotationProcessor'] = [ diff --git a/docs/docs/v4/image-coil/README.md b/docs/docs/v4/image-coil/README.md new file mode 100644 index 00000000..435ea408 --- /dev/null +++ b/docs/docs/v4/image-coil/README.md @@ -0,0 +1,24 @@ +# Image Coil + + + +Image loading based on `Coil` library + +```kt +val markwon = Markwon.builder(context) + // automatically create Coil instance + .usePlugin(CoilImagesPlugin.create(context)) + // if you need more control + .usePlugin(CoilImagesPlugin.create(object : CoilImagesPlugin.CoilStore() { + override load(drawable: AsyncDrawable, target: Target): RequestDisposable { + return Coil.load(context, drawable.destination) { + target(target) + } + } + + override cancel(requestDisposable: RequestDisposable) { + requestDisposable.dispose() + } + })) + .build() +``` \ No newline at end of file diff --git a/markwon-image-coil/README.md b/markwon-image-coil/README.md new file mode 100644 index 00000000..74e61881 --- /dev/null +++ b/markwon-image-coil/README.md @@ -0,0 +1,3 @@ +# Images (Coil) + +https://noties.io/Markwon/docs/v4/image-coil/ \ No newline at end of file diff --git a/markwon-image-coil/build.gradle b/markwon-image-coil/build.gradle new file mode 100644 index 00000000..aa760dfb --- /dev/null +++ b/markwon-image-coil/build.gradle @@ -0,0 +1,21 @@ +apply plugin: 'com.android.library' + +android { + + compileSdkVersion config['compile-sdk'] + buildToolsVersion config['build-tools'] + + defaultConfig { + minSdkVersion config['min-sdk'] + targetSdkVersion config['target-sdk'] + versionCode 1 + versionName version + } +} + +dependencies { + api project(':markwon-core') + api deps['coil'] +} + +registerArtifact(this) diff --git a/markwon-image-coil/gradle.properties b/markwon-image-coil/gradle.properties new file mode 100644 index 00000000..489bbf6e --- /dev/null +++ b/markwon-image-coil/gradle.properties @@ -0,0 +1,4 @@ +POM_NAME=Image Coil +POM_ARTIFACT_ID=image-coil +POM_DESCRIPTION=Markwon image loading module (based on Coil library) +POM_PACKAGING=aar \ No newline at end of file diff --git a/markwon-image-coil/src/main/AndroidManifest.xml b/markwon-image-coil/src/main/AndroidManifest.xml new file mode 100644 index 00000000..b2f53ca9 --- /dev/null +++ b/markwon-image-coil/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/markwon-image-coil/src/main/java/io/noties/markwon/image/coil/CoilImagesPlugin.java b/markwon-image-coil/src/main/java/io/noties/markwon/image/coil/CoilImagesPlugin.java new file mode 100644 index 00000000..cae52f52 --- /dev/null +++ b/markwon-image-coil/src/main/java/io/noties/markwon/image/coil/CoilImagesPlugin.java @@ -0,0 +1,186 @@ +package io.noties.markwon.image.coil; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.text.Spanned; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.commonmark.node.Image; + +import java.util.HashMap; +import java.util.Map; + +import coil.Coil; +import coil.ImageLoader; +import coil.api.ImageLoaders; +import coil.request.LoadRequest; +import coil.request.LoadRequestBuilder; +import coil.request.RequestDisposable; +import coil.target.Target; +import io.noties.markwon.AbstractMarkwonPlugin; +import io.noties.markwon.MarkwonConfiguration; +import io.noties.markwon.MarkwonSpansFactory; +import io.noties.markwon.image.AsyncDrawable; +import io.noties.markwon.image.AsyncDrawableLoader; +import io.noties.markwon.image.AsyncDrawableScheduler; +import io.noties.markwon.image.DrawableUtils; +import io.noties.markwon.image.ImageSpanFactory; + +/** + * @since 4.0.0 + */ +public class CoilImagesPlugin extends AbstractMarkwonPlugin { + + public interface CoilStore { + + @NonNull + RequestDisposable load(@NonNull AsyncDrawable drawable, @NonNull Target target); + + void cancel(@NonNull RequestDisposable disposable); + } + + @NonNull + public static CoilImagesPlugin create(@NonNull final Context context) { + return create(new CoilStore() { + @NonNull + @Override + public RequestDisposable load(@NonNull AsyncDrawable drawable, @NonNull Target target) { + ImageLoader imageLoader = Coil.loader(); + LoadRequest request = ImageLoaders.newLoadBuilder(imageLoader, context) + .data(drawable.getDestination()) + .target(target) + .build(); + return imageLoader.load(request); + } + + @Override + public void cancel(@NonNull RequestDisposable disposable) { + disposable.dispose(); + } + }); + } + + @NonNull + public static CoilImagesPlugin create(@NonNull final LoadRequestBuilder loadRequestBuilder) { + return create(new CoilStore() { + @NonNull + @Override + public RequestDisposable load(@NonNull AsyncDrawable drawable, @NonNull Target target) { + ImageLoader imageLoader = Coil.loader(); + LoadRequest request = loadRequestBuilder + .data(drawable.getDestination()) + .target(target) + .build(); + return imageLoader.load(request); + } + + @Override + public void cancel(@NonNull RequestDisposable disposable) { + disposable.dispose(); + } + }); + } + + @NonNull + public static CoilImagesPlugin create(@NonNull CoilStore coilStore) { + return new CoilImagesPlugin(coilStore); + } + + private final CoilAsyncDrawableLoader coilAsyncDrawableLoader; + + @SuppressWarnings("WeakerAccess") + CoilImagesPlugin(@NonNull CoilStore coilStore) { + this.coilAsyncDrawableLoader = new CoilAsyncDrawableLoader(coilStore); + } + + @Override + public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { + builder.setFactory(Image.class, new ImageSpanFactory()); + } + + @Override + public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { + builder.asyncDrawableLoader(coilAsyncDrawableLoader); + } + + @Override + public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) { + AsyncDrawableScheduler.unschedule(textView); + } + + @Override + public void afterSetText(@NonNull TextView textView) { + AsyncDrawableScheduler.schedule(textView); + } + + private static class CoilAsyncDrawableLoader extends AsyncDrawableLoader { + + private final CoilStore coilStore; + private final Map cache = new HashMap<>(2); + + CoilAsyncDrawableLoader(@NonNull CoilStore coilStore) { + this.coilStore = coilStore; + } + + @Override + public void load(@NonNull AsyncDrawable drawable) { + final Target target = new AsyncDrawableTarget(drawable); + RequestDisposable disposable = coilStore.load(drawable, target); + cache.put(drawable, disposable); + } + + @Override + public void cancel(@NonNull AsyncDrawable drawable) { + final RequestDisposable disposable = cache.remove(drawable); + if (disposable != null) { + coilStore.cancel(disposable); + } + } + + @Nullable + @Override + public Drawable placeholder(@NonNull AsyncDrawable drawable) { + return null; + } + + private class AsyncDrawableTarget implements Target { + + private final AsyncDrawable drawable; + + AsyncDrawableTarget(@NonNull AsyncDrawable drawable) { + this.drawable = drawable; + } + + @Override + public void onSuccess(@NonNull Drawable loadedDrawable) { + if (cache.remove(drawable) != null) { + if (drawable.isAttached()) { + DrawableUtils.applyIntrinsicBoundsIfEmpty(loadedDrawable); + drawable.setResult(loadedDrawable); + } + } + } + + @Override + public void onStart(@Nullable Drawable placeholder) { + if (placeholder != null && drawable.isAttached()) { + DrawableUtils.applyIntrinsicBoundsIfEmpty(placeholder); + drawable.setResult(placeholder); + } + } + + @Override + public void onError(@Nullable Drawable errorDrawable) { + if (cache.remove(drawable) != null) { + if (errorDrawable != null && drawable.isAttached()) { + DrawableUtils.applyIntrinsicBoundsIfEmpty(errorDrawable); + drawable.setResult(errorDrawable); + } + } + } + } + } +} diff --git a/sample/src/main/java/io/noties/markwon/sample/recycler/RecyclerActivity.java b/sample/src/main/java/io/noties/markwon/sample/recycler/RecyclerActivity.java index bbd6f890..815e5b03 100644 --- a/sample/src/main/java/io/noties/markwon/sample/recycler/RecyclerActivity.java +++ b/sample/src/main/java/io/noties/markwon/sample/recycler/RecyclerActivity.java @@ -82,6 +82,7 @@ public class RecyclerActivity extends Activity { // })) .usePlugin(PicassoImagesPlugin.create(context)) // .usePlugin(GlideImagesPlugin.create(context)) +// .usePlugin(CoilImagesPlugin.create(context)) // important to use TableEntryPlugin instead of TablePlugin .usePlugin(TableEntryPlugin.create(context)) .usePlugin(HtmlPlugin.create()) diff --git a/settings.gradle b/settings.gradle index 45a92d52..87a9f3b3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,6 +7,7 @@ include ':app', ':sample', ':markwon-ext-tasklist', ':markwon-html', ':markwon-image', + ':markwon-image-coil', ':markwon-image-glide', ':markwon-image-picasso', ':markwon-linkify',