diff --git a/_CHANGES.md b/_CHANGES.md new file mode 100644 index 00000000..b1bb22c8 --- /dev/null +++ b/_CHANGES.md @@ -0,0 +1,2 @@ +* `Markwon.builder` won't require CorePlugin registration (it is done automatically) + to create a builder without CorePlugin - use `Markwon#builderNoCore` \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index c6ad59c1..9d95baaf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -33,8 +33,7 @@ dependencies { implementation project(':markwon-ext-tables') implementation project(':markwon-ext-tasklist') implementation project(':markwon-html') - implementation project(':markwon-image-gif') - implementation project(':markwon-image-svg') + implementation project(':markwon-image') implementation project(':markwon-syntax-highlight') deps.with { @@ -42,6 +41,8 @@ dependencies { implementation it['prism4j'] implementation it['debug'] implementation it['dagger'] + implementation it['android-svg'] + implementation it['android-gif'] } deps['annotationProcessor'].with { diff --git a/app/src/main/java/ru/noties/markwon/MarkdownRenderer.java b/app/src/main/java/ru/noties/markwon/MarkdownRenderer.java index cf2ab04c..26b247a2 100644 --- a/app/src/main/java/ru/noties/markwon/MarkdownRenderer.java +++ b/app/src/main/java/ru/noties/markwon/MarkdownRenderer.java @@ -14,15 +14,18 @@ import java.util.concurrent.Future; import javax.inject.Inject; import ru.noties.debug.Debug; -import ru.noties.markwon.core.CorePlugin; import ru.noties.markwon.ext.strikethrough.StrikethroughPlugin; import ru.noties.markwon.ext.tables.TablePlugin; import ru.noties.markwon.ext.tasklist.TaskListPlugin; import ru.noties.markwon.gif.GifAwarePlugin; import ru.noties.markwon.html.HtmlPlugin; +import ru.noties.markwon.image.DefaultImageMediaDecoder; import ru.noties.markwon.image.ImagesPlugin; -import ru.noties.markwon.image.gif.GifPlugin; -import ru.noties.markwon.image.svg.SvgPlugin; +import ru.noties.markwon.image.data.DataUriSchemeHandler; +import ru.noties.markwon.image.file.FileSchemeHandler; +import ru.noties.markwon.image.gif.GifMediaDecoder; +import ru.noties.markwon.image.network.OkHttpNetworkSchemeHandler; +import ru.noties.markwon.image.svg.SvgMediaDecoder; import ru.noties.markwon.syntax.Prism4jTheme; import ru.noties.markwon.syntax.Prism4jThemeDarkula; import ru.noties.markwon.syntax.Prism4jThemeDefault; @@ -94,10 +97,18 @@ public class MarkdownRenderer { : prism4JThemeDarkula; final Markwon markwon = Markwon.builder(context) - .usePlugin(CorePlugin.create()) - .usePlugin(ImagesPlugin.createWithAssets(context)) - .usePlugin(SvgPlugin.create(context.getResources())) - .usePlugin(GifPlugin.create(false)) + .usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() { + @Override + public void configureImages(@NonNull ImagesPlugin plugin) { + plugin + .addSchemeHandler(DataUriSchemeHandler.create()) + .addSchemeHandler(OkHttpNetworkSchemeHandler.create()) + .addSchemeHandler(FileSchemeHandler.createWithAssets(context.getAssets())) + .addMediaDecoder(GifMediaDecoder.create(false)) + .addMediaDecoder(SvgMediaDecoder.create()) + .defaultMediaDecoder(DefaultImageMediaDecoder.create()); + } + })) .usePlugin(SyntaxHighlightPlugin.create(prism4j, prism4jTheme)) .usePlugin(GifAwarePlugin.create(context)) .usePlugin(TablePlugin.create(context)) diff --git a/app/src/main/java/ru/noties/markwon/gif/GifAwarePlugin.java b/app/src/main/java/ru/noties/markwon/gif/GifAwarePlugin.java index 89e49384..95509525 100644 --- a/app/src/main/java/ru/noties/markwon/gif/GifAwarePlugin.java +++ b/app/src/main/java/ru/noties/markwon/gif/GifAwarePlugin.java @@ -14,8 +14,6 @@ import ru.noties.markwon.RenderProps; import ru.noties.markwon.SpanFactory; import ru.noties.markwon.image.AsyncDrawableSpan; import ru.noties.markwon.image.ImageProps; -import ru.noties.markwon.image.ImagesPlugin; -import ru.noties.markwon.priority.Priority; public class GifAwarePlugin extends AbstractMarkwonPlugin { @@ -59,12 +57,6 @@ public class GifAwarePlugin extends AbstractMarkwonPlugin { }); } - @NonNull - @Override - public Priority priority() { - return Priority.after(ImagesPlugin.class); - } - @Override public void afterSetText(@NonNull TextView textView) { processor.process(textView); diff --git a/markwon-core/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java b/markwon-core/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java index 3bf54029..ae05550a 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java +++ b/markwon-core/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java @@ -7,11 +7,8 @@ import android.widget.TextView; import org.commonmark.node.Node; import org.commonmark.parser.Parser; -import ru.noties.markwon.core.CorePlugin; import ru.noties.markwon.core.MarkwonTheme; import ru.noties.markwon.html.MarkwonHtmlRenderer; -import ru.noties.markwon.image.AsyncDrawableLoader; -import ru.noties.markwon.priority.Priority; /** * Class that extends {@link MarkwonPlugin} with all methods implemented (empty body) diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/CorePlugin.java b/markwon-core/src/main/java/ru/noties/markwon/core/CorePlugin.java index 39436839..4882441a 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/core/CorePlugin.java +++ b/markwon-core/src/main/java/ru/noties/markwon/core/CorePlugin.java @@ -40,7 +40,6 @@ import ru.noties.markwon.core.factory.ListItemSpanFactory; import ru.noties.markwon.core.factory.StrongEmphasisSpanFactory; import ru.noties.markwon.core.factory.ThematicBreakSpanFactory; import ru.noties.markwon.core.spans.OrderedListItemSpan; -import ru.noties.markwon.priority.Priority; /** * @see CoreProps diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java b/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java index 5db212fd..239dad4e 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java +++ b/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java @@ -40,7 +40,7 @@ public class AsyncDrawable extends Drawable { this.imageSizeResolver = imageSizeResolver; this.imageSize = imageSize; - final Drawable placeholder = loader.placeholder(); + final Drawable placeholder = loader.placeholder(this); if (placeholder != null) { setPlaceholderResult(placeholder); } diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoader.java b/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoader.java index 7e297bf3..f9bbb4b7 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoader.java +++ b/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoader.java @@ -3,13 +3,6 @@ package ru.noties.markwon.image; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.util.Log; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; public abstract class AsyncDrawableLoader { @@ -31,28 +24,9 @@ public abstract class AsyncDrawableLoader { */ public abstract void cancel(@NonNull AsyncDrawable drawable); - /** - * @see #load(AsyncDrawable) - * @deprecated 3.1.0-SNAPSHOT - */ - @Deprecated - public void load(@NonNull String destination, @NonNull AsyncDrawable drawable) { - load(drawable); - } - - /** - * Method is deprecated because cancellation won\'t work for markdown input - * with multiple images with the same source - * - * @deprecated 3.1.0-SNAPSHOT - */ - @Deprecated - public void cancel(@NonNull String destination) { - Log.e("MARKWON-IL", "Image loading cancellation must be triggered " + - "by AsyncDrawable, please use #cancel(AsyncDrawable) method instead. " + - "No op, nothing is cancelled for destination: " + destination); - } - @Nullable - public abstract Drawable placeholder(); + public abstract Drawable placeholder(@NonNull AsyncDrawable drawable); + + + } diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderImpl.java b/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderImpl.java deleted file mode 100644 index 711508c2..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderImpl.java +++ /dev/null @@ -1,290 +0,0 @@ -package ru.noties.markwon.image; - -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Handler; -import android.os.Looper; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.io.IOException; -import java.io.InputStream; -import java.lang.ref.WeakReference; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; - -class AsyncDrawableLoaderImpl extends AsyncDrawableLoader { - - private final ExecutorService executorService; - private final Map schemeHandlers; - private final Map mediaDecoders; - private final MediaDecoder defaultMediaDecoder; - private final DrawableProvider placeholderDrawableProvider; - private final DrawableProvider errorDrawableProvider; - - private final Handler mainThread; - - // @since 3.1.0-SNAPSHOT use a hash-map with a weak AsyncDrawable as key for multiple requests - // for the same destination - private final Map, Future> requests = new HashMap<>(2); - - AsyncDrawableLoaderImpl(@NonNull Builder builder) { - this.executorService = builder.executorService; - this.schemeHandlers = builder.schemeHandlers; - this.mediaDecoders = builder.mediaDecoders; - this.defaultMediaDecoder = builder.defaultMediaDecoder; - this.placeholderDrawableProvider = builder.placeholderDrawableProvider; - this.errorDrawableProvider = builder.errorDrawableProvider; - this.mainThread = new Handler(Looper.getMainLooper()); - } - - @Override - public void load(@NonNull final AsyncDrawable drawable) { - - // primitive synchronization via main-thread - if (!isMainThread()) { - mainThread.post(new Runnable() { - @Override - public void run() { - load(drawable); - } - }); - return; - } - - // okay, if by some chance requested drawable already has a future associated -> no-op - // as AsyncDrawable cannot change `destination` (immutable field) - // @since 3.1.0-SNAPSHOT - if (hasTaskAssociated(drawable)) { - return; - } - - final WeakReference reference = new WeakReference<>(drawable); - requests.put(reference, execute(drawable.getDestination(), reference)); - } - - @Override - public void cancel(@NonNull final AsyncDrawable drawable) { - - if (!isMainThread()) { - mainThread.post(new Runnable() { - @Override - public void run() { - cancel(drawable); - } - }); - return; - } - - final Iterator, Future>> iterator = - requests.entrySet().iterator(); - - AsyncDrawable key; - Map.Entry, Future> entry; - - while (iterator.hasNext()) { - - entry = iterator.next(); - key = entry.getKey().get(); - - // if key is null or it contains requested AsyncDrawable -> cancel - if (shouldCleanUp(key) || key == drawable) { - entry.getValue().cancel(true); - iterator.remove(); - } - } - } - - private boolean hasTaskAssociated(@NonNull AsyncDrawable drawable) { - - final Iterator, Future>> iterator = - requests.entrySet().iterator(); - - boolean result = false; - - AsyncDrawable key; - Map.Entry, Future> entry; - - while (iterator.hasNext()) { - - entry = iterator.next(); - key = entry.getKey().get(); - - // clean-up - if (shouldCleanUp(key)) { - entry.getValue().cancel(true); - iterator.remove(); - } else if (key == drawable) { - result = true; - // do not break, let iteration continue to possibly clean-up the rest references - } - } - - return result; - } - - private void cleanUp() { - - final Iterator, Future>> iterator = - requests.entrySet().iterator(); - - AsyncDrawable key; - Map.Entry, Future> entry; - - while (iterator.hasNext()) { - - entry = iterator.next(); - key = entry.getKey().get(); - - // clean-up of already referenced or detached drawables - if (shouldCleanUp(key)) { - entry.getValue().cancel(true); - iterator.remove(); - } - } - } - -// @Override -// public void load(@NonNull String destination, @NonNull AsyncDrawable drawable) { -// -// // todo: we cannot reliably identify request by the destination, as if -// // markdown input has multiple images with the same destination as source -// // we will be tracking only one of them (the one appears the last). We should -// // move to AsyncDrawable based identification. This method also _maybe_ -// // should include the ImageSize (comment @since 3.1.0-SNAPSHOT) -// -// requests.put(destination, execute(destination, drawable)); -// } -// -// @Override -// public void cancel(@NonNull String destination) { -// -// // todo: as we are moving away from a single request for a destination, -// // we should re-evaluate this cancellation logic, as if there are multiple images -// // in markdown input all of them will be cancelled (won't delivered), even if -// // only a single drawable is detached. Cancellation must also take -// // the AsyncDrawable argument (comment @since 3.1.0-SNAPSHOT) -// -// // -// final Future request = requests.remove(destination); -// if (request != null) { -// request.cancel(true); -// } -// } - - @Nullable - @Override - public Drawable placeholder() { - return placeholderDrawableProvider != null - ? placeholderDrawableProvider.provide() - : null; - } - - private Future execute(@NonNull final String destination, @NonNull final WeakReference reference) { - - // todo: error handing (simply applying errorDrawable is not a good solution - // as reason for an error is unclear (no scheme handler, no input data, error decoding, etc) - - // todo: more efficient ImageMediaDecoder... BitmapFactory.decodeStream is a bit not optimal - // for big images for sure. We _could_ introduce internal Drawable that will check for - // image bounds (but we will need to cache inputStream in order to inspect and optimize - // input image...) - - return executorService.submit(new Runnable() { - @Override - public void run() { - - final ImageItem item; - - final Uri uri = Uri.parse(destination); - - final SchemeHandler schemeHandler = schemeHandlers.get(uri.getScheme()); - - if (schemeHandler != null) { - item = schemeHandler.handle(destination, uri); - } else { - item = null; - } - - final InputStream inputStream = item != null - ? item.inputStream() - : null; - - Drawable result = null; - - if (inputStream != null) { - try { - - MediaDecoder mediaDecoder = mediaDecoders.get(item.contentType()); - if (mediaDecoder == null) { - mediaDecoder = defaultMediaDecoder; - } - - if (mediaDecoder != null) { - result = mediaDecoder.decode(inputStream); - } - - } finally { - try { - inputStream.close(); - } catch (IOException e) { - // ignored - } - } - } - - // if result is null, we assume it's an error - if (result == null) { - result = errorDrawableProvider != null - ? errorDrawableProvider.provide() - : null; - } - - final Drawable out = result; - - mainThread.post(new Runnable() { - @Override - public void run() { - - if (out != null) { - - // this doesn't work with markdown input with multiple images with the - // same source (comment @since 3.1.0-SNAPSHOT) -// final boolean canDeliver = requests.remove(destination) != null; -// if (canDeliver) { -// final AsyncDrawable asyncDrawable = reference.get(); -// if (asyncDrawable != null && asyncDrawable.isAttached()) { -// asyncDrawable.setResult(out); -// } -// } - - // todo: AsyncDrawable cannot change destination, so if it's - // attached and not garbage-collected, we can deliver the result. - // Note that there is no cache, so attach/detach of drawables - // will always request a new entry.. (comment @since 3.1.0-SNAPSHOT) - final AsyncDrawable asyncDrawable = reference.get(); - if (asyncDrawable != null && asyncDrawable.isAttached()) { - asyncDrawable.setResult(out); - } - } - - requests.remove(reference); - cleanUp(); - } - }); - } - }); - } - - private static boolean shouldCleanUp(@Nullable AsyncDrawable drawable) { - return drawable == null || !drawable.isAttached(); - } - - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - private static boolean isMainThread() { - return Looper.myLooper() == Looper.getMainLooper(); - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderNoOp.java b/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderNoOp.java index 330abe94..7c3475b7 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderNoOp.java +++ b/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderNoOp.java @@ -4,7 +4,7 @@ import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -class AsyncDrawableLoaderNoOp extends AsyncDrawableLoader { +public class AsyncDrawableLoaderNoOp extends AsyncDrawableLoader { @Override public void load(@NonNull AsyncDrawable drawable) { @@ -17,7 +17,7 @@ class AsyncDrawableLoaderNoOp extends AsyncDrawableLoader { @Nullable @Override - public Drawable placeholder() { + public Drawable placeholder(@NonNull AsyncDrawable drawable) { return null; } } diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/ImageItem.java b/markwon-core/src/main/java/ru/noties/markwon/image/ImageItem.java deleted file mode 100644 index a6e9f72e..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/image/ImageItem.java +++ /dev/null @@ -1,70 +0,0 @@ -package ru.noties.markwon.image; - -import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.io.InputStream; - -/** - * @since 2.0.0 - */ -public abstract class ImageItem { - - /** - * Create an {@link ImageItem} with result, so no further decoding is required. - * - * @since 4.0.0-SNAPSHOT - */ - @NonNull - public static ImageItem withResult(@Nullable Drawable drawable) { - return new WithResult(drawable); - } - - @NonNull - public static ImageItem withDecodingNeeded( - @Nullable String contentType, - @Nullable InputStream inputStream) { - return new WithDecodingNeeded(contentType, inputStream); - } - - private ImageItem() { - } - - public static class WithResult extends ImageItem { - - private final Drawable result; - - WithResult(@Nullable Drawable drawable) { - result = drawable; - } - - @Nullable - public Drawable result() { - return result; - } - } - - public static class WithDecodingNeeded extends ImageItem { - - private final String contentType; - private final InputStream inputStream; - - WithDecodingNeeded( - @Nullable String contentType, - @Nullable InputStream inputStream) { - this.contentType = contentType; - this.inputStream = inputStream; - } - - @Nullable - public String contentType() { - return contentType; - } - - @Nullable - public InputStream inputStream() { - return inputStream; - } - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/ImageMediaDecoder.java b/markwon-core/src/main/java/ru/noties/markwon/image/ImageMediaDecoder.java deleted file mode 100644 index c510c4d8..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/image/ImageMediaDecoder.java +++ /dev/null @@ -1,50 +0,0 @@ -package ru.noties.markwon.image; - -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.io.InputStream; - -/** - * This class can be used as the last {@link MediaDecoder} to _try_ to handle all rest cases. - * Here we just assume that supplied InputStream is of image type and try to decode it. - * - * @since 1.1.0 - */ -public class ImageMediaDecoder extends MediaDecoder { - - @NonNull - public static ImageMediaDecoder create(@NonNull Resources resources) { - return new ImageMediaDecoder(resources); - } - - private final Resources resources; - - @SuppressWarnings("WeakerAccess") - ImageMediaDecoder(Resources resources) { - this.resources = resources; - } - - @Nullable - @Override - public Drawable decode(@NonNull InputStream inputStream) { - - final Drawable out; - - // absolutely not optimal... thing - final Bitmap bitmap = BitmapFactory.decodeStream(inputStream); - if (bitmap != null) { - out = new BitmapDrawable(resources, bitmap); - DrawableUtils.applyIntrinsicBounds(out); - } else { - out = null; - } - - return out; - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/ImagesPlugin.java b/markwon-core/src/main/java/ru/noties/markwon/image/ImagesPlugin.java deleted file mode 100644 index 2f6e8117..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/image/ImagesPlugin.java +++ /dev/null @@ -1,130 +0,0 @@ -package ru.noties.markwon.image; - -import android.content.Context; -import android.support.annotation.NonNull; -import android.text.Spanned; -import android.widget.TextView; - -import org.commonmark.node.Image; -import org.commonmark.node.Link; -import org.commonmark.node.Node; - -import java.util.Arrays; - -import ru.noties.markwon.AbstractMarkwonPlugin; -import ru.noties.markwon.MarkwonConfiguration; -import ru.noties.markwon.MarkwonSpansFactory; -import ru.noties.markwon.MarkwonVisitor; -import ru.noties.markwon.RenderProps; -import ru.noties.markwon.SpanFactory; -import ru.noties.markwon.image.data.DataUriSchemeHandler; -import ru.noties.markwon.image.file.FileSchemeHandler; -import ru.noties.markwon.image.network.NetworkSchemeHandler; - -/** - * @since 3.0.0 - */ -public class ImagesPlugin extends AbstractMarkwonPlugin { - - @NonNull - public static ImagesPlugin create(@NonNull Context context) { - return new ImagesPlugin(context, false); - } - - /** - * Special scheme that is used {@code file:///android_asset/} - * - * @param context - * @return - */ - @NonNull - public static ImagesPlugin createWithAssets(@NonNull Context context) { - return new ImagesPlugin(context, true); - } - - private final Context context; - private final boolean useAssets; - - protected ImagesPlugin(Context context, boolean useAssets) { - this.context = context; - this.useAssets = useAssets; - } - - @Override - public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) { - - final FileSchemeHandler fileSchemeHandler = useAssets - ? FileSchemeHandler.createWithAssets(context.getAssets()) - : FileSchemeHandler.create(); - - builder - .addSchemeHandler(DataUriSchemeHandler.SCHEME, DataUriSchemeHandler.create()) - .addSchemeHandler(FileSchemeHandler.SCHEME, fileSchemeHandler) - .addSchemeHandler( - Arrays.asList( - NetworkSchemeHandler.SCHEME_HTTP, - NetworkSchemeHandler.SCHEME_HTTPS), - NetworkSchemeHandler.create()) - .defaultMediaDecoder(ImageMediaDecoder.create(context.getResources())); - } - - @Override - public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { - builder.setFactory(Image.class, new ImageSpanFactory()); - } - - @Override - public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) { - builder.on(Image.class, new MarkwonVisitor.NodeVisitor() { - @Override - public void visit(@NonNull MarkwonVisitor visitor, @NonNull Image image) { - - // if there is no image spanFactory, ignore - final SpanFactory spanFactory = visitor.configuration().spansFactory().get(Image.class); - if (spanFactory == null) { - visitor.visitChildren(image); - return; - } - - final int length = visitor.length(); - - visitor.visitChildren(image); - - // we must check if anything _was_ added, as we need at least one char to render - if (length == visitor.length()) { - visitor.builder().append('\uFFFC'); - } - - final MarkwonConfiguration configuration = visitor.configuration(); - - final Node parent = image.getParent(); - final boolean link = parent instanceof Link; - - final String destination = configuration - .urlProcessor() - .process(image.getDestination()); - - final RenderProps props = visitor.renderProps(); - - // apply image properties - // Please note that we explicitly set IMAGE_SIZE to null as we do not clear - // properties after we applied span (we could though) - ImageProps.DESTINATION.set(props, destination); - ImageProps.REPLACEMENT_TEXT_IS_LINK.set(props, link); - ImageProps.IMAGE_SIZE.set(props, null); - - visitor.setSpans(length, spanFactory.getSpans(configuration, props)); - } - }); - } - - @Override - public void beforeSetText(@NonNull TextView textView, @NonNull Spanned markdown) { - AsyncDrawableScheduler.unschedule(textView); - } - - @Override - public void afterSetText(@NonNull TextView textView) { - AsyncDrawableScheduler.schedule(textView); - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/MediaDecoder.java b/markwon-core/src/main/java/ru/noties/markwon/image/MediaDecoder.java deleted file mode 100644 index 68d0ff33..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/image/MediaDecoder.java +++ /dev/null @@ -1,16 +0,0 @@ -package ru.noties.markwon.image; - -import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.io.InputStream; - -/** - * @since 3.0.0 - */ -public abstract class MediaDecoder { - - @Nullable - public abstract Drawable decode(@NonNull InputStream inputStream); -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/SchemeHandler.java b/markwon-core/src/main/java/ru/noties/markwon/image/SchemeHandler.java deleted file mode 100644 index cac1c801..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/image/SchemeHandler.java +++ /dev/null @@ -1,14 +0,0 @@ -package ru.noties.markwon.image; - -import android.net.Uri; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -/** - * @since 3.0.0 - */ -public abstract class SchemeHandler { - - @Nullable - public abstract ImageItem handle(@NonNull String raw, @NonNull Uri uri); -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/data/DataUri.java b/markwon-core/src/main/java/ru/noties/markwon/image/data/DataUri.java deleted file mode 100644 index 6e812c92..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/image/data/DataUri.java +++ /dev/null @@ -1,60 +0,0 @@ -package ru.noties.markwon.image.data; - -import android.support.annotation.Nullable; - -public class DataUri { - - private final String contentType; - private final boolean base64; - private final String data; - - public DataUri(@Nullable String contentType, boolean base64, @Nullable String data) { - this.contentType = contentType; - this.base64 = base64; - this.data = data; - } - - @Nullable - public String contentType() { - return contentType; - } - - public boolean base64() { - return base64; - } - - @Nullable - public String data() { - return data; - } - - @Override - public String toString() { - return "DataUri{" + - "contentType='" + contentType + '\'' + - ", base64=" + base64 + - ", data='" + data + '\'' + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - DataUri dataUri = (DataUri) o; - - if (base64 != dataUri.base64) return false; - if (contentType != null ? !contentType.equals(dataUri.contentType) : dataUri.contentType != null) - return false; - return data != null ? data.equals(dataUri.data) : dataUri.data == null; - } - - @Override - public int hashCode() { - int result = contentType != null ? contentType.hashCode() : 0; - result = 31 * result + (base64 ? 1 : 0); - result = 31 * result + (data != null ? data.hashCode() : 0); - return result; - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/data/DataUriDecoder.java b/markwon-core/src/main/java/ru/noties/markwon/image/data/DataUriDecoder.java deleted file mode 100644 index 7e3d4f73..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/image/data/DataUriDecoder.java +++ /dev/null @@ -1,41 +0,0 @@ -package ru.noties.markwon.image.data; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.text.TextUtils; -import android.util.Base64; - -public abstract class DataUriDecoder { - - @Nullable - public abstract byte[] decode(@NonNull DataUri dataUri); - - @NonNull - public static DataUriDecoder create() { - return new Impl(); - } - - static class Impl extends DataUriDecoder { - - @Nullable - @Override - public byte[] decode(@NonNull DataUri dataUri) { - - final String data = dataUri.data(); - - if (!TextUtils.isEmpty(data)) { - try { - if (dataUri.base64()) { - return Base64.decode(data.getBytes("UTF-8"), Base64.DEFAULT); - } else { - return data.getBytes("UTF-8"); - } - } catch (Throwable t) { - return null; - } - } else { - return null; - } - } - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/data/DataUriParser.java b/markwon-core/src/main/java/ru/noties/markwon/image/data/DataUriParser.java deleted file mode 100644 index 0768ee4a..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/image/data/DataUriParser.java +++ /dev/null @@ -1,79 +0,0 @@ -package ru.noties.markwon.image.data; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -public abstract class DataUriParser { - - @Nullable - public abstract DataUri parse(@NonNull String input); - - - @NonNull - public static DataUriParser create() { - return new Impl(); - } - - static class Impl extends DataUriParser { - - @Nullable - @Override - public DataUri parse(@NonNull String input) { - - final int index = input.indexOf(','); - // we expect exactly one comma - if (index < 0) { - return null; - } - - final String contentType; - final boolean base64; - - if (index > 0) { - final String part = input.substring(0, index); - final String[] parts = part.split(";"); - final int length = parts.length; - if (length > 0) { - // if one: either content-type or base64 - if (length == 1) { - final String value = parts[0]; - if ("base64".equals(value)) { - contentType = null; - base64 = true; - } else { - contentType = value.indexOf('/') > -1 - ? value - : null; - base64 = false; - } - } else { - contentType = parts[0].indexOf('/') > -1 - ? parts[0] - : null; - base64 = "base64".equals(parts[length - 1]); - } - } else { - contentType = null; - base64 = false; - } - } else { - contentType = null; - base64 = false; - } - - final String data; - if (index < input.length()) { - final String value = input.substring(index + 1, input.length()).replaceAll("\n", ""); - if (value.length() == 0) { - data = null; - } else { - data = value; - } - } else { - data = null; - } - - return new DataUri(contentType, base64, data); - } - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/data/DataUriSchemeHandler.java b/markwon-core/src/main/java/ru/noties/markwon/image/data/DataUriSchemeHandler.java deleted file mode 100644 index f4c87ed4..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/image/data/DataUriSchemeHandler.java +++ /dev/null @@ -1,65 +0,0 @@ -package ru.noties.markwon.image.data; - -import android.net.Uri; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.io.ByteArrayInputStream; - -import ru.noties.markwon.image.ImageItem; -import ru.noties.markwon.image.SchemeHandler; - -/** - * @since 2.0.0 - */ -public class DataUriSchemeHandler extends SchemeHandler { - - public static final String SCHEME = "data"; - - @NonNull - public static DataUriSchemeHandler create() { - return new DataUriSchemeHandler(DataUriParser.create(), DataUriDecoder.create()); - } - - private static final String START = "data:"; - - private final DataUriParser uriParser; - private final DataUriDecoder uriDecoder; - - @SuppressWarnings("WeakerAccess") - DataUriSchemeHandler(@NonNull DataUriParser uriParser, @NonNull DataUriDecoder uriDecoder) { - this.uriParser = uriParser; - this.uriDecoder = uriDecoder; - } - - @Nullable - @Override - public ImageItem handle(@NonNull String raw, @NonNull Uri uri) { - - if (!raw.startsWith(START)) { - return null; - } - - String part = raw.substring(START.length()); - - // this part is added to support `data://` with which this functionality was released - if (part.startsWith("//")) { - part = part.substring(2); - } - - final DataUri dataUri = uriParser.parse(part); - if (dataUri == null) { - return null; - } - - final byte[] bytes = uriDecoder.decode(dataUri); - if (bytes == null) { - return null; - } - - return new ImageItem( - dataUri.contentType(), - new ByteArrayInputStream(bytes) - ); - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/file/FileSchemeHandler.java b/markwon-core/src/main/java/ru/noties/markwon/image/file/FileSchemeHandler.java deleted file mode 100644 index 712899aa..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/image/file/FileSchemeHandler.java +++ /dev/null @@ -1,105 +0,0 @@ -package ru.noties.markwon.image.file; - -import android.content.res.AssetManager; -import android.net.Uri; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.webkit.MimeTypeMap; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; - -import ru.noties.markwon.image.ImageItem; -import ru.noties.markwon.image.SchemeHandler; - -/** - * @since 3.0.0 - */ -public class FileSchemeHandler extends SchemeHandler { - - public static final String SCHEME = "file"; - - @NonNull - public static FileSchemeHandler createWithAssets(@NonNull AssetManager assetManager) { - return new FileSchemeHandler(assetManager); - } - - @NonNull - public static FileSchemeHandler create() { - return new FileSchemeHandler(null); - } - - private static final String FILE_ANDROID_ASSETS = "android_asset"; - - @Nullable - private final AssetManager assetManager; - - @SuppressWarnings("WeakerAccess") - FileSchemeHandler(@Nullable AssetManager assetManager) { - this.assetManager = assetManager; - } - - @Nullable - @Override - public ImageItem handle(@NonNull String raw, @NonNull Uri uri) { - - final List segments = uri.getPathSegments(); - if (segments == null - || segments.size() == 0) { - // pointing to file & having no path segments is no use - return null; - } - - final ImageItem out; - - InputStream inputStream = null; - - final boolean assets = FILE_ANDROID_ASSETS.equals(segments.get(0)); - final String fileName = uri.getLastPathSegment(); - - if (assets) { - - // no handling of assets here if we have no assetsManager - if (assetManager != null) { - - final StringBuilder path = new StringBuilder(); - for (int i = 1, size = segments.size(); i < size; i++) { - if (i != 1) { - path.append('/'); - } - path.append(segments.get(i)); - } - // load assets - - try { - inputStream = assetManager.open(path.toString()); - } catch (IOException e) { - e.printStackTrace(); - } - } - - } else { - try { - inputStream = new BufferedInputStream(new FileInputStream(new File(uri.getPath()))); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - } - - if (inputStream != null) { - final String contentType = MimeTypeMap - .getSingleton() - .getMimeTypeFromExtension(MimeTypeMap.getFileExtensionFromUrl(fileName)); - out = new ImageItem(contentType, inputStream); - } else { - out = null; - } - - return out; - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/network/NetworkSchemeHandler.java b/markwon-core/src/main/java/ru/noties/markwon/image/network/NetworkSchemeHandler.java deleted file mode 100644 index ebaaf803..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/image/network/NetworkSchemeHandler.java +++ /dev/null @@ -1,70 +0,0 @@ -package ru.noties.markwon.image.network; - -import android.net.Uri; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URL; - -import ru.noties.markwon.image.ImageItem; -import ru.noties.markwon.image.SchemeHandler; - -/** - * A simple network scheme handler that is not dependent on any external libraries. - * - * @see #create() - * @since 3.0.0 - */ -public class NetworkSchemeHandler extends SchemeHandler { - - public static final String SCHEME_HTTP = "http"; - public static final String SCHEME_HTTPS = "https"; - - @NonNull - public static NetworkSchemeHandler create() { - return new NetworkSchemeHandler(); - } - - @Nullable - @Override - public ImageItem handle(@NonNull String raw, @NonNull Uri uri) { - - try { - - final URL url = new URL(raw); - final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.connect(); - - final int responseCode = connection.getResponseCode(); - if (responseCode >= 200 && responseCode < 300) { - final String contentType = contentType(connection.getHeaderField("Content-Type")); - final InputStream inputStream = new BufferedInputStream(connection.getInputStream()); - return new ImageItem(contentType, inputStream); - } - - } catch (IOException e) { - e.printStackTrace(); - } - - return null; - } - - @Nullable - static String contentType(@Nullable String contentType) { - - if (contentType == null) { - return null; - } - - final int index = contentType.indexOf(';'); - if (index > -1) { - return contentType.substring(0, index); - } - - return contentType; - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/priority/Priority.java b/markwon-core/src/main/java/ru/noties/markwon/priority/Priority.java deleted file mode 100644 index e10faf2a..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/priority/Priority.java +++ /dev/null @@ -1,97 +0,0 @@ -package ru.noties.markwon.priority; - -import android.support.annotation.NonNull; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import ru.noties.markwon.MarkwonPlugin; - -// a small dependency graph also -// what if plugins cannot be constructed into a graph? for example they depend on something -// but not overlap? then it would be hard to sort them (but this doesn't make sense, if -// they do not care about other components, just put them in whatever order, no?) - -/** - * @see MarkwonPlugin#priority() - * @since 3.0.0 - */ -@Deprecated -public abstract class Priority { - - @NonNull - public static Priority none() { - return builder().build(); - } - - @NonNull - public static Priority after(@NonNull Class plugin) { - return builder().after(plugin).build(); - } - - @NonNull - public static Priority after( - @NonNull Class plugin1, - @NonNull Class plugin2) { - return builder().after(plugin1).after(plugin2).build(); - } - - @NonNull - public static Builder builder() { - return new Impl.BuilderImpl(); - } - - public interface Builder { - - @NonNull - Builder after(@NonNull Class plugin); - - @NonNull - Priority build(); - } - - @NonNull - public abstract List> after(); - - - static class Impl extends Priority { - - private final List> after; - - Impl(@NonNull List> after) { - this.after = after; - } - - @NonNull - @Override - public List> after() { - return after; - } - - @Override - public String toString() { - return "Priority{" + - "after=" + after + - '}'; - } - - static class BuilderImpl implements Builder { - - private final List> after = new ArrayList<>(0); - - @NonNull - @Override - public Builder after(@NonNull Class plugin) { - after.add(plugin); - return this; - } - - @NonNull - @Override - public Priority build() { - return new Impl(Collections.unmodifiableList(after)); - } - } - } -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/priority/PriorityProcessor.java b/markwon-core/src/main/java/ru/noties/markwon/priority/PriorityProcessor.java deleted file mode 100644 index 1ba1353d..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/priority/PriorityProcessor.java +++ /dev/null @@ -1,18 +0,0 @@ -package ru.noties.markwon.priority; - -import android.support.annotation.NonNull; - -import java.util.List; - -import ru.noties.markwon.MarkwonPlugin; - -public abstract class PriorityProcessor { - - @NonNull - public static PriorityProcessor create() { - return new PriorityProcessorImpl(); - } - - @NonNull - public abstract List process(@NonNull List plugins); -} diff --git a/markwon-core/src/main/java/ru/noties/markwon/priority/PriorityProcessorImpl.java b/markwon-core/src/main/java/ru/noties/markwon/priority/PriorityProcessorImpl.java deleted file mode 100644 index e676a9c9..00000000 --- a/markwon-core/src/main/java/ru/noties/markwon/priority/PriorityProcessorImpl.java +++ /dev/null @@ -1,132 +0,0 @@ -package ru.noties.markwon.priority; - -import android.support.annotation.NonNull; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import ru.noties.markwon.MarkwonPlugin; - -import static java.lang.Math.max; - -class PriorityProcessorImpl extends PriorityProcessor { - - @NonNull - @Override - public List process(@NonNull List in) { - - // create new collection based on supplied argument - final List plugins = new ArrayList<>(in); - - final int size = plugins.size(); - - final Map, Set>> map = - new HashMap<>(size); - - for (MarkwonPlugin plugin : plugins) { - if (map.put(plugin.getClass(), new HashSet<>(plugin.priority().after())) != null) { - throw new IllegalStateException(String.format("Markwon duplicate plugin " + - "found `%s`: %s", plugin.getClass().getName(), plugin)); - } - } - - final Map cache = new HashMap<>(size); - for (MarkwonPlugin plugin : plugins) { - cache.put(plugin, eval(plugin, map)); - } - - Collections.sort(plugins, new PriorityComparator(cache)); - - return plugins; - } - - private static int eval( - @NonNull MarkwonPlugin plugin, - @NonNull Map, Set>> map) { - - final Set> set = map.get(plugin.getClass()); - - // no dependencies - if (set.isEmpty()) { - return 0; - } - - final Class who = plugin.getClass(); - - int max = 0; - - for (Class dependency : set) { - max = max(max, eval(who, dependency, map)); - } - - return 1 + max; - } - - // we need to count the number of steps to a root node (which has no parents) - private static int eval( - @NonNull Class who, - @NonNull Class plugin, - @NonNull Map, Set>> map) { - - // exact match - Set> set = map.get(plugin); - - if (set == null) { - - // let's try to find inexact type (overridden/subclassed) - for (Map.Entry, Set>> entry : map.entrySet()) { - if (plugin.isAssignableFrom(entry.getKey())) { - set = entry.getValue(); - break; - } - } - - if (set == null) { - // unsatisfied dependency - throw new IllegalStateException(String.format("Markwon unsatisfied dependency found. " + - "Plugin `%s` comes after `%s` but it is not added.", - who.getName(), plugin.getName())); - } - } - - if (set.isEmpty()) { - return 0; - } - - int value = 1; - - for (Class dependency : set) { - - // a case when a plugin defines `Priority.after(getClass)` or being - // referenced by own dependency (even indirect) - if (who.equals(dependency)) { - throw new IllegalStateException(String.format("Markwon plugin `%s` defined self " + - "as a dependency or being referenced by own dependency (cycle)", who.getName())); - } - - value += eval(who, dependency, map); - } - - return value; - } - - private static class PriorityComparator implements Comparator { - - private final Map map; - - PriorityComparator(@NonNull Map map) { - this.map = map; - } - - @Override - public int compare(MarkwonPlugin o1, MarkwonPlugin o2) { - return map.get(o1).compareTo(map.get(o2)); - } - } -} diff --git a/markwon-core/src/test/java/ru/noties/markwon/AbstractMarkwonPluginTest.java b/markwon-core/src/test/java/ru/noties/markwon/AbstractMarkwonPluginTest.java index 1af2b513..2002a06d 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/AbstractMarkwonPluginTest.java +++ b/markwon-core/src/test/java/ru/noties/markwon/AbstractMarkwonPluginTest.java @@ -8,7 +8,6 @@ import org.robolectric.annotation.Config; import java.util.List; import ru.noties.markwon.core.CorePlugin; -import ru.noties.markwon.priority.Priority; import static org.junit.Assert.assertEquals; diff --git a/markwon-core/src/test/java/ru/noties/markwon/MarkwonBuilderImplTest.java b/markwon-core/src/test/java/ru/noties/markwon/MarkwonBuilderImplTest.java index af3d00b1..43d115a4 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/MarkwonBuilderImplTest.java +++ b/markwon-core/src/test/java/ru/noties/markwon/MarkwonBuilderImplTest.java @@ -23,8 +23,6 @@ import ru.noties.markwon.core.CorePlugin; import ru.noties.markwon.core.MarkwonTheme; import ru.noties.markwon.html.MarkwonHtmlRenderer; import ru.noties.markwon.image.AsyncDrawableLoader; -import ru.noties.markwon.priority.Priority; -import ru.noties.markwon.priority.PriorityProcessor; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.hasItem; diff --git a/markwon-core/src/test/java/ru/noties/markwon/image/data/DataUriSchemeHandlerTest.java b/markwon-core/src/test/java/ru/noties/markwon/image/data/DataUriSchemeHandlerTest.java index 16dc73b5..82c571f7 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/image/data/DataUriSchemeHandlerTest.java +++ b/markwon-core/src/test/java/ru/noties/markwon/image/data/DataUriSchemeHandlerTest.java @@ -14,8 +14,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Scanner; -import ru.noties.markwon.image.ImageItem; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; diff --git a/markwon-core/src/test/java/ru/noties/markwon/priority/PriorityProcessorTest.java b/markwon-core/src/test/java/ru/noties/markwon/priority/PriorityProcessorTest.java index 2a6063ed..164f98a5 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/priority/PriorityProcessorTest.java +++ b/markwon-core/src/test/java/ru/noties/markwon/priority/PriorityProcessorTest.java @@ -18,7 +18,6 @@ import java.util.List; import ru.noties.markwon.AbstractMarkwonPlugin; import ru.noties.markwon.MarkwonPlugin; import ru.noties.markwon.core.CorePlugin; -import ru.noties.markwon.image.ImagesPlugin; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; diff --git a/markwon-ext-latex/src/main/java/ru/noties/markwon/ext/latex/JLatexMathPlugin.java b/markwon-ext-latex/src/main/java/ru/noties/markwon/ext/latex/JLatexMathPlugin.java index c4761d0c..9a09679d 100644 --- a/markwon-ext-latex/src/main/java/ru/noties/markwon/ext/latex/JLatexMathPlugin.java +++ b/markwon-ext-latex/src/main/java/ru/noties/markwon/ext/latex/JLatexMathPlugin.java @@ -19,13 +19,7 @@ import ru.noties.markwon.AbstractMarkwonPlugin; import ru.noties.markwon.MarkwonVisitor; import ru.noties.markwon.RenderProps; import ru.noties.markwon.image.AsyncDrawableLoader; -import ru.noties.markwon.image.ImageItem; -import ru.noties.markwon.image.ImageProps; import ru.noties.markwon.image.ImageSize; -import ru.noties.markwon.image.ImagesPlugin; -import ru.noties.markwon.image.MediaDecoder; -import ru.noties.markwon.image.SchemeHandler; -import ru.noties.markwon.priority.Priority; /** * @since 3.0.0 diff --git a/markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifMediaDecoder.java b/markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifMediaDecoder.java index 6f5eda18..72dd572c 100644 --- a/markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifMediaDecoder.java +++ b/markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifMediaDecoder.java @@ -10,7 +10,6 @@ import java.io.InputStream; import pl.droidsonroids.gif.GifDrawable; import ru.noties.markwon.image.DrawableUtils; -import ru.noties.markwon.image.MediaDecoder; /** * @since 1.1.0 diff --git a/markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifPlugin.java b/markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifPlugin.java index d0db857e..6cceffdf 100644 --- a/markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifPlugin.java +++ b/markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifPlugin.java @@ -4,7 +4,6 @@ import android.support.annotation.NonNull; import ru.noties.markwon.AbstractMarkwonPlugin; import ru.noties.markwon.image.AsyncDrawableLoader; -import ru.noties.markwon.image.ImagesPlugin; import ru.noties.markwon.priority.Priority; public class GifPlugin extends AbstractMarkwonPlugin { diff --git a/markwon-image-okhttp/src/main/java/ru/noties/markwon/image/okhttp/OkHttpImagesPlugin.java b/markwon-image-okhttp/src/main/java/ru/noties/markwon/image/okhttp/OkHttpImagesPlugin.java index fe43c289..36cc79fb 100644 --- a/markwon-image-okhttp/src/main/java/ru/noties/markwon/image/okhttp/OkHttpImagesPlugin.java +++ b/markwon-image-okhttp/src/main/java/ru/noties/markwon/image/okhttp/OkHttpImagesPlugin.java @@ -7,8 +7,6 @@ import java.util.Arrays; import okhttp3.OkHttpClient; import ru.noties.markwon.AbstractMarkwonPlugin; import ru.noties.markwon.image.AsyncDrawableLoader; -import ru.noties.markwon.image.ImagesPlugin; -import ru.noties.markwon.image.network.NetworkSchemeHandler; import ru.noties.markwon.priority.Priority; /** diff --git a/markwon-image-okhttp/src/main/java/ru/noties/markwon/image/okhttp/OkHttpSchemeHandler.java b/markwon-image-okhttp/src/main/java/ru/noties/markwon/image/okhttp/OkHttpSchemeHandler.java index eaf73bd6..32527bdf 100644 --- a/markwon-image-okhttp/src/main/java/ru/noties/markwon/image/okhttp/OkHttpSchemeHandler.java +++ b/markwon-image-okhttp/src/main/java/ru/noties/markwon/image/okhttp/OkHttpSchemeHandler.java @@ -11,8 +11,6 @@ import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import okhttp3.ResponseBody; -import ru.noties.markwon.image.ImageItem; -import ru.noties.markwon.image.SchemeHandler; class OkHttpSchemeHandler extends SchemeHandler { diff --git a/markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgMediaDecoder.java b/markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgMediaDecoder.java index 5ee71c61..8c8b9dcc 100644 --- a/markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgMediaDecoder.java +++ b/markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgMediaDecoder.java @@ -14,7 +14,6 @@ import com.caverock.androidsvg.SVGParseException; import java.io.InputStream; import ru.noties.markwon.image.DrawableUtils; -import ru.noties.markwon.image.MediaDecoder; /** * @since 1.1.0 diff --git a/markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgPlugin.java b/markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgPlugin.java index be357480..c4486fc6 100644 --- a/markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgPlugin.java +++ b/markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgPlugin.java @@ -5,7 +5,6 @@ import android.support.annotation.NonNull; import ru.noties.markwon.AbstractMarkwonPlugin; import ru.noties.markwon.image.AsyncDrawableLoader; -import ru.noties.markwon.image.ImagesPlugin; import ru.noties.markwon.priority.Priority; public class SvgPlugin extends AbstractMarkwonPlugin { diff --git a/markwon-image/gradle.properties b/markwon-image/gradle.properties index a845eebf..d7dd30bd 100644 --- a/markwon-image/gradle.properties +++ b/markwon-image/gradle.properties @@ -1,4 +1,4 @@ POM_NAME=Image POM_ARTIFACT_ID=image -POM_DESCRIPTION=Markwon image loading module (with GIF and SVG support) +POM_DESCRIPTION=Markwon image loading module (with optional GIF and SVG support) POM_PACKAGING=aar \ No newline at end of file diff --git a/markwon-image/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderBuilder.java b/markwon-image/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderBuilder.java index 47c91146..6bbe234b 100644 --- a/markwon-image/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderBuilder.java +++ b/markwon-image/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderBuilder.java @@ -1,6 +1,5 @@ package ru.noties.markwon.image; -import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -10,88 +9,69 @@ import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -public class AsyncDrawableLoaderBuilder { +class AsyncDrawableLoaderBuilder { ExecutorService executorService; final Map schemeHandlers = new HashMap<>(3); final Map mediaDecoders = new HashMap<>(3); MediaDecoder defaultMediaDecoder; - DrawableProvider placeholderDrawableProvider; - DrawableProvider errorDrawableProvider; + ImagesPlugin.PlaceholderProvider placeholderProvider; + ImagesPlugin.ErrorHandler errorHandler; - @NonNull - public AsyncDrawableLoaderBuilder executorService(@NonNull ExecutorService executorService) { + boolean isBuilt; + + void executorService(@NonNull ExecutorService executorService) { this.executorService = executorService; - return this; } - @NonNull - public AsyncDrawableLoaderBuilder addSchemeHandler(@NonNull String scheme, @NonNull SchemeHandler schemeHandler) { - schemeHandlers.put(scheme, schemeHandler); - return this; - } - - @NonNull - public AsyncDrawableLoaderBuilder addSchemeHandler(@NonNull Collection schemes, @NonNull SchemeHandler schemeHandler) { - for (String scheme : schemes) { + void addSchemeHandler(@NonNull SchemeHandler schemeHandler) { + for (String scheme : schemeHandler.supportedSchemes()) { schemeHandlers.put(scheme, schemeHandler); } - return this; } - @NonNull - public AsyncDrawableLoaderBuilder addMediaDecoder(@NonNull String contentType, @NonNull MediaDecoder mediaDecoder) { - mediaDecoders.put(contentType, mediaDecoder); - return this; - } - - @NonNull - public AsyncDrawableLoaderBuilder addMediaDecoder(@NonNull Collection contentTypes, @NonNull MediaDecoder mediaDecoder) { - for (String contentType : contentTypes) { - mediaDecoders.put(contentType, mediaDecoder); + void addMediaDecoder(@NonNull MediaDecoder mediaDecoder) { + final Collection supportedTypes = mediaDecoder.supportedTypes(); + if (supportedTypes.isEmpty()) { + // todo: we should think about this little _side-effect_... does it worth it? + defaultMediaDecoder = mediaDecoder; + } else { + for (String type : supportedTypes) { + mediaDecoders.put(type, mediaDecoder); + } } - return this; } - @NonNull - public AsyncDrawableLoaderBuilder removeSchemeHandler(@NonNull String scheme) { - schemeHandlers.remove(scheme); - return this; - } - - @NonNull - public AsyncDrawableLoaderBuilder removeMediaDecoder(@NonNull String contentType) { - mediaDecoders.remove(contentType); - return this; - } - - @NonNull - public AsyncDrawableLoaderBuilder defaultMediaDecoder(@Nullable MediaDecoder mediaDecoder) { + void defaultMediaDecoder(@Nullable MediaDecoder mediaDecoder) { this.defaultMediaDecoder = mediaDecoder; - return this; + } + + void removeSchemeHandler(@NonNull String scheme) { + schemeHandlers.remove(scheme); + } + + void removeMediaDecoder(@NonNull String contentType) { + mediaDecoders.remove(contentType); } /** * @since 3.0.0 */ - @NonNull - public AsyncDrawableLoaderBuilder placeholderDrawableProvider(@NonNull DrawableProvider placeholderDrawableProvider) { - this.placeholderDrawableProvider = placeholderDrawableProvider; - return this; + void placeholderProvider(@NonNull ImagesPlugin.PlaceholderProvider placeholderDrawableProvider) { + this.placeholderProvider = placeholderDrawableProvider; } /** * @since 3.0.0 */ - @NonNull - public AsyncDrawableLoaderBuilder errorDrawableProvider(@NonNull DrawableProvider errorDrawableProvider) { - this.errorDrawableProvider = errorDrawableProvider; - return this; + void errorHandler(@NonNull ImagesPlugin.ErrorHandler errorHandler) { + this.errorHandler = errorHandler; } - @NonNull - public AsyncDrawableLoader build() { + AsyncDrawableLoader build() { + + isBuilt = true; // if we have no schemeHandlers -> we cannot show anything // OR if we have no media decoders diff --git a/markwon-image/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderImpl.java b/markwon-image/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderImpl.java index 711508c2..a0226c48 100644 --- a/markwon-image/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderImpl.java +++ b/markwon-image/src/main/java/ru/noties/markwon/image/AsyncDrawableLoaderImpl.java @@ -6,9 +6,8 @@ import android.os.Handler; import android.os.Looper; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.util.Log; -import java.io.IOException; -import java.io.InputStream; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Iterator; @@ -22,8 +21,8 @@ class AsyncDrawableLoaderImpl extends AsyncDrawableLoader { private final Map schemeHandlers; private final Map mediaDecoders; private final MediaDecoder defaultMediaDecoder; - private final DrawableProvider placeholderDrawableProvider; - private final DrawableProvider errorDrawableProvider; + private final ImagesPlugin.PlaceholderProvider placeholderProvider; + private final ImagesPlugin.ErrorHandler errorHandler; private final Handler mainThread; @@ -31,13 +30,13 @@ class AsyncDrawableLoaderImpl extends AsyncDrawableLoader { // for the same destination private final Map, Future> requests = new HashMap<>(2); - AsyncDrawableLoaderImpl(@NonNull Builder builder) { + AsyncDrawableLoaderImpl(@NonNull AsyncDrawableLoaderBuilder builder) { this.executorService = builder.executorService; this.schemeHandlers = builder.schemeHandlers; this.mediaDecoders = builder.mediaDecoders; this.defaultMediaDecoder = builder.defaultMediaDecoder; - this.placeholderDrawableProvider = builder.placeholderDrawableProvider; - this.errorDrawableProvider = builder.errorDrawableProvider; + this.placeholderProvider = builder.placeholderProvider; + this.errorHandler = builder.errorHandler; this.mainThread = new Handler(Looper.getMainLooper()); } @@ -147,48 +146,18 @@ class AsyncDrawableLoaderImpl extends AsyncDrawableLoader { } } -// @Override -// public void load(@NonNull String destination, @NonNull AsyncDrawable drawable) { -// -// // todo: we cannot reliably identify request by the destination, as if -// // markdown input has multiple images with the same destination as source -// // we will be tracking only one of them (the one appears the last). We should -// // move to AsyncDrawable based identification. This method also _maybe_ -// // should include the ImageSize (comment @since 3.1.0-SNAPSHOT) -// -// requests.put(destination, execute(destination, drawable)); -// } -// -// @Override -// public void cancel(@NonNull String destination) { -// -// // todo: as we are moving away from a single request for a destination, -// // we should re-evaluate this cancellation logic, as if there are multiple images -// // in markdown input all of them will be cancelled (won't delivered), even if -// // only a single drawable is detached. Cancellation must also take -// // the AsyncDrawable argument (comment @since 3.1.0-SNAPSHOT) -// -// // -// final Future request = requests.remove(destination); -// if (request != null) { -// request.cancel(true); -// } -// } - @Nullable @Override - public Drawable placeholder() { - return placeholderDrawableProvider != null - ? placeholderDrawableProvider.provide() + public Drawable placeholder(@NonNull AsyncDrawable drawable) { + return placeholderProvider != null + ? placeholderProvider.providePlaceholder(drawable) : null; } + @NonNull private Future execute(@NonNull final String destination, @NonNull final WeakReference reference) { - // todo: error handing (simply applying errorDrawable is not a good solution - // as reason for an error is unclear (no scheme handler, no input data, error decoding, etc) - - // todo: more efficient ImageMediaDecoder... BitmapFactory.decodeStream is a bit not optimal + // todo: more efficient DefaultImageMediaDecoder... BitmapFactory.decodeStream is a bit not optimal // for big images for sure. We _could_ introduce internal Drawable that will check for // image bounds (but we will need to cache inputStream in order to inspect and optimize // input image...) @@ -197,71 +166,60 @@ class AsyncDrawableLoaderImpl extends AsyncDrawableLoader { @Override public void run() { - final ImageItem item; - final Uri uri = Uri.parse(destination); - final SchemeHandler schemeHandler = schemeHandlers.get(uri.getScheme()); + Drawable drawable = null; - if (schemeHandler != null) { - item = schemeHandler.handle(destination, uri); - } else { - item = null; - } + try { + // obtain scheme handler + final SchemeHandler schemeHandler = schemeHandlers.get(uri.getScheme()); + if (schemeHandler != null) { - final InputStream inputStream = item != null - ? item.inputStream() - : null; + // handle scheme + final ImageItem imageItem = schemeHandler.handle(destination, uri); - Drawable result = null; + // if resulting imageItem needs further decoding -> proceed + if (imageItem.hasDecodingNeeded()) { - if (inputStream != null) { - try { + final ImageItem.WithDecodingNeeded withDecodingNeeded = imageItem.getAsWithDecodingNeeded(); - MediaDecoder mediaDecoder = mediaDecoders.get(item.contentType()); - if (mediaDecoder == null) { - mediaDecoder = defaultMediaDecoder; + MediaDecoder mediaDecoder = mediaDecoders.get(withDecodingNeeded.contentType()); + + if (mediaDecoder == null) { + mediaDecoder = defaultMediaDecoder; + } + + if (mediaDecoder != null) { + drawable = mediaDecoder.decode(withDecodingNeeded.contentType(), withDecodingNeeded.inputStream()); + } else { + // throw that no media decoder is found + throw new IllegalStateException("No media-decoder is found: " + destination); + } + } else { + drawable = imageItem.getAsWithResult().result(); } + } else { + // throw no scheme handler is available + throw new IllegalStateException("No scheme-handler is found: " + destination); + } - if (mediaDecoder != null) { - result = mediaDecoder.decode(inputStream); - } - - } finally { - try { - inputStream.close(); - } catch (IOException e) { - // ignored - } + } catch (Throwable t) { + if (errorHandler != null) { + drawable = errorHandler.handleError(destination, t); + } else { + // else simply log the error + Log.e("MARKWON-IMAGE", "Error loading image: " + destination, t); } } - // if result is null, we assume it's an error - if (result == null) { - result = errorDrawableProvider != null - ? errorDrawableProvider.provide() - : null; - } - - final Drawable out = result; + final Drawable out = drawable; mainThread.post(new Runnable() { @Override public void run() { if (out != null) { - - // this doesn't work with markdown input with multiple images with the - // same source (comment @since 3.1.0-SNAPSHOT) -// final boolean canDeliver = requests.remove(destination) != null; -// if (canDeliver) { -// final AsyncDrawable asyncDrawable = reference.get(); -// if (asyncDrawable != null && asyncDrawable.isAttached()) { -// asyncDrawable.setResult(out); -// } -// } - - // todo: AsyncDrawable cannot change destination, so if it's + // AsyncDrawable cannot change destination, so if it's // attached and not garbage-collected, we can deliver the result. // Note that there is no cache, so attach/detach of drawables // will always request a new entry.. (comment @since 3.1.0-SNAPSHOT) diff --git a/markwon-image/src/main/java/ru/noties/markwon/image/DefaultImageMediaDecoder.java b/markwon-image/src/main/java/ru/noties/markwon/image/DefaultImageMediaDecoder.java new file mode 100644 index 00000000..8322f745 --- /dev/null +++ b/markwon-image/src/main/java/ru/noties/markwon/image/DefaultImageMediaDecoder.java @@ -0,0 +1,62 @@ +package ru.noties.markwon.image; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.io.InputStream; +import java.util.Collection; +import java.util.Collections; + +/** + * This class can be used as the last {@link MediaDecoder} to _try_ to handle all rest cases. + * Here we just assume that supplied InputStream is of image type and try to decode it. + * + * @since 1.1.0 + */ +public class DefaultImageMediaDecoder extends MediaDecoder { + + @NonNull + public static DefaultImageMediaDecoder create() { + return new DefaultImageMediaDecoder(Resources.getSystem()); + } + + @NonNull + public static DefaultImageMediaDecoder create(@NonNull Resources resources) { + return new DefaultImageMediaDecoder(resources); + } + + private final Resources resources; + + @SuppressWarnings("WeakerAccess") + DefaultImageMediaDecoder(Resources resources) { + this.resources = resources; + } + + @NonNull + @Override + public Drawable decode(@Nullable String contentType, @NonNull InputStream inputStream) { + + final Bitmap bitmap; + try { + // absolutely not optimal... thing + bitmap = BitmapFactory.decodeStream(inputStream); + } catch (Throwable t) { + throw new IllegalStateException("Exception decoding input-stream", t); + } + + final Drawable drawable = new BitmapDrawable(resources, bitmap); + DrawableUtils.applyIntrinsicBounds(drawable); + return drawable; + } + + @NonNull + @Override + public Collection supportedTypes() { + return Collections.emptySet(); + } +} diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/ImageSpanFactory.java b/markwon-image/src/main/java/ru/noties/markwon/image/ImageSpanFactory.java similarity index 100% rename from markwon-core/src/main/java/ru/noties/markwon/image/ImageSpanFactory.java rename to markwon-image/src/main/java/ru/noties/markwon/image/ImageSpanFactory.java diff --git a/markwon-image/src/main/java/ru/noties/markwon/image/ImagesPlugin.java b/markwon-image/src/main/java/ru/noties/markwon/image/ImagesPlugin.java index 3d441f7b..e8cd8324 100644 --- a/markwon-image/src/main/java/ru/noties/markwon/image/ImagesPlugin.java +++ b/markwon-image/src/main/java/ru/noties/markwon/image/ImagesPlugin.java @@ -1,6 +1,5 @@ package ru.noties.markwon.image; -import android.content.Context; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -11,6 +10,8 @@ import org.commonmark.node.Image; import org.commonmark.node.Link; import org.commonmark.node.Node; +import java.util.concurrent.ExecutorService; + import ru.noties.markwon.AbstractMarkwonPlugin; import ru.noties.markwon.MarkwonConfiguration; import ru.noties.markwon.MarkwonSpansFactory; @@ -23,55 +24,123 @@ public class ImagesPlugin extends AbstractMarkwonPlugin { /** * @since 4.0.0-SNAPSHOT */ - public interface DrawableProvider { - @Nullable - Drawable provide(); - } - - - @NonNull - public static ImagesPlugin create(@NonNull Context context) { - return new ImagesPlugin(context, false); + public interface ImagesConfigure { + void configureImages(@NonNull ImagesPlugin plugin); } /** - * Special scheme that is used {@code file:///android_asset/} - * - * @param context - * @return + * @since 4.0.0-SNAPSHOT + */ + public interface PlaceholderProvider { + @Nullable + Drawable providePlaceholder(@NonNull AsyncDrawable drawable); + } + + /** + * @since 4.0.0-SNAPSHOT + */ + public interface ErrorHandler { + + /** + * Can optionally return a Drawable that will be displayed in case of an error + */ + @Nullable + Drawable handleError(@NonNull String url, @NonNull Throwable throwable); + } + + /** + * Factory method to create an empty {@link ImagesPlugin} instance with no {@link SchemeHandler}s + * nor {@link MediaDecoder}s registered. Can be used to further configure via instance methods or + * via {@link ru.noties.markwon.MarkwonPlugin#configure(Registry)} */ @NonNull - public static ImagesPlugin createWithAssets(@NonNull Context context) { - return new ImagesPlugin(context, true); + public static ImagesPlugin createEmpty() { + return new ImagesPlugin(); } - private final Context context; - private final boolean useAssets; - - protected ImagesPlugin(Context context, boolean useAssets) { - this.context = context; - this.useAssets = useAssets; + @NonNull + public static ImagesPlugin create(@NonNull ImagesConfigure configure) { + final ImagesPlugin plugin = new ImagesPlugin(); + configure.configureImages(plugin); + return plugin; } - // we must expose scheme handling... so it's available during construction and via `require` + private final AsyncDrawableLoaderBuilder builder = new AsyncDrawableLoaderBuilder(); -// @Override -// public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) { -// -// final FileSchemeHandler fileSchemeHandler = useAssets -// ? FileSchemeHandler.createWithAssets(context.getAssets()) -// : FileSchemeHandler.create(); -// -// builder -// .addSchemeHandler(DataUriSchemeHandler.SCHEME, DataUriSchemeHandler.create()) -// .addSchemeHandler(FileSchemeHandler.SCHEME, fileSchemeHandler) -// .addSchemeHandler( -// Arrays.asList( -// NetworkSchemeHandler.SCHEME_HTTP, -// NetworkSchemeHandler.SCHEME_HTTPS), -// NetworkSchemeHandler.create()) -// .defaultMediaDecoder(ImageMediaDecoder.create(context.getResources())); -// } + /** + * Optional (by default new cached thread executor will be used) + * + * @since 4.0.0-SNAPSHOT + */ + @NonNull + public ImagesPlugin executorService(@NonNull ExecutorService executorService) { + checkBuilderState(); + builder.executorService(executorService); + return this; + } + + /** + * @see SchemeHandler + * @see ru.noties.markwon.image.data.DataUriSchemeHandler + * @see ru.noties.markwon.image.file.FileSchemeHandler + * @see ru.noties.markwon.image.network.NetworkSchemeHandler + * @see ru.noties.markwon.image.network.OkHttpNetworkSchemeHandler + * @since 4.0.0-SNAPSHOT + */ + @NonNull + public ImagesPlugin addSchemeHandler(@NonNull SchemeHandler schemeHandler) { + checkBuilderState(); + builder.addSchemeHandler(schemeHandler); + return this; + } + + @NonNull + public ImagesPlugin addMediaDecoder(@NonNull MediaDecoder mediaDecoder) { + checkBuilderState(); + builder.addMediaDecoder(mediaDecoder); + return this; + } + + @NonNull + public ImagesPlugin defaultMediaDecoder(@Nullable MediaDecoder mediaDecoder) { + checkBuilderState(); + builder.defaultMediaDecoder(mediaDecoder); + return this; + } + + @NonNull + public ImagesPlugin removeSchemeHandler(@NonNull String scheme) { + checkBuilderState(); + builder.removeSchemeHandler(scheme); + return this; + } + + @NonNull + public ImagesPlugin removeMediaDecoder(@NonNull String contentType) { + checkBuilderState(); + builder.removeMediaDecoder(contentType); + return this; + } + + @NonNull + public ImagesPlugin placeholderProvider(@NonNull PlaceholderProvider placeholderProvider) { + checkBuilderState(); + builder.placeholderProvider(placeholderProvider); + return this; + } + + @NonNull + public ImagesPlugin errorHandler(@NonNull ErrorHandler errorHandler) { + checkBuilderState(); + builder.errorHandler(errorHandler); + return this; + } + + @Override + public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) { + checkBuilderState(); + builder.asyncDrawableLoader(this.builder.build()); + } @Override public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { @@ -132,4 +201,11 @@ public class ImagesPlugin extends AbstractMarkwonPlugin { public void afterSetText(@NonNull TextView textView) { AsyncDrawableScheduler.schedule(textView); } + + private void checkBuilderState() { + if (builder.isBuilt) { + throw new IllegalStateException("ImagesPlugin has already been configured " + + "and cannot be modified any further"); + } + } } diff --git a/markwon-image/src/main/java/ru/noties/markwon/image/MediaDecoder.java b/markwon-image/src/main/java/ru/noties/markwon/image/MediaDecoder.java index ecea7a98..4470c50e 100644 --- a/markwon-image/src/main/java/ru/noties/markwon/image/MediaDecoder.java +++ b/markwon-image/src/main/java/ru/noties/markwon/image/MediaDecoder.java @@ -5,6 +5,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import java.io.InputStream; +import java.util.Collection; /** * @since 3.0.0 @@ -16,14 +17,17 @@ public abstract class MediaDecoder { *
    *
  • Returns `non-null` drawable
  • *
  • Added `contentType` method parameter
  • - *
  • Added `throws Exception` to method signature
  • *
- * - * @throws Exception since 4.0.0-SNAPSHOT */ @NonNull public abstract Drawable decode( @Nullable String contentType, @NonNull InputStream inputStream - ) throws Exception; + ); + + /** + * @since 4.0.0-SNAPSHOT + */ + @NonNull + public abstract Collection supportedTypes(); } diff --git a/markwon-image/src/main/java/ru/noties/markwon/image/SchemeHandler.java b/markwon-image/src/main/java/ru/noties/markwon/image/SchemeHandler.java index 2c5889ed..32402136 100644 --- a/markwon-image/src/main/java/ru/noties/markwon/image/SchemeHandler.java +++ b/markwon-image/src/main/java/ru/noties/markwon/image/SchemeHandler.java @@ -3,6 +3,8 @@ package ru.noties.markwon.image; import android.net.Uri; import android.support.annotation.NonNull; +import java.util.Collection; + /** * @since 3.0.0 */ @@ -12,13 +14,17 @@ public abstract class SchemeHandler { * Changes since 4.0.0-SNAPSHOT: *
    *
  • Returns `non-null` image-item
  • - *
  • added `throws Exception` to method signature
  • *
* - * @throws Exception since 4.0.0-SNAPSHOT * @see ImageItem#withResult(android.graphics.drawable.Drawable) * @see ImageItem#withDecodingNeeded(String, java.io.InputStream) */ @NonNull - public abstract ImageItem handle(@NonNull String raw, @NonNull Uri uri) throws Exception; + public abstract ImageItem handle(@NonNull String raw, @NonNull Uri uri); + + /** + * @since 4.0.0-SNAPSHOT + */ + @NonNull + public abstract Collection supportedSchemes(); } diff --git a/markwon-image/src/main/java/ru/noties/markwon/image/data/DataUriSchemeHandler.java b/markwon-image/src/main/java/ru/noties/markwon/image/data/DataUriSchemeHandler.java index cd4bd570..b5c8dcc4 100644 --- a/markwon-image/src/main/java/ru/noties/markwon/image/data/DataUriSchemeHandler.java +++ b/markwon-image/src/main/java/ru/noties/markwon/image/data/DataUriSchemeHandler.java @@ -4,6 +4,8 @@ import android.net.Uri; import android.support.annotation.NonNull; import java.io.ByteArrayInputStream; +import java.util.Collection; +import java.util.Collections; import ru.noties.markwon.image.ImageItem; import ru.noties.markwon.image.SchemeHandler; @@ -61,4 +63,10 @@ public class DataUriSchemeHandler extends SchemeHandler { dataUri.contentType(), new ByteArrayInputStream(bytes)); } + + @NonNull + @Override + public Collection supportedSchemes() { + return Collections.singleton(SCHEME); + } } diff --git a/markwon-image/src/main/java/ru/noties/markwon/image/file/FileSchemeHandler.java b/markwon-image/src/main/java/ru/noties/markwon/image/file/FileSchemeHandler.java index cd5030c1..681fb29f 100644 --- a/markwon-image/src/main/java/ru/noties/markwon/image/file/FileSchemeHandler.java +++ b/markwon-image/src/main/java/ru/noties/markwon/image/file/FileSchemeHandler.java @@ -13,6 +13,8 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.util.Collection; +import java.util.Collections; import java.util.List; import ru.noties.markwon.image.ImageItem; @@ -111,4 +113,10 @@ public class FileSchemeHandler extends SchemeHandler { return ImageItem.withDecodingNeeded(contentType, inputStream); } + + @NonNull + @Override + public Collection supportedSchemes() { + return Collections.singleton(SCHEME); + } } diff --git a/markwon-image/src/main/java/ru/noties/markwon/image/gif/GifMediaDecoder.java b/markwon-image/src/main/java/ru/noties/markwon/image/gif/GifMediaDecoder.java index 4d0cbe32..e5195bc5 100644 --- a/markwon-image/src/main/java/ru/noties/markwon/image/gif/GifMediaDecoder.java +++ b/markwon-image/src/main/java/ru/noties/markwon/image/gif/GifMediaDecoder.java @@ -7,6 +7,8 @@ import android.support.annotation.Nullable; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Collection; +import java.util.Collections; import pl.droidsonroids.gif.GifDrawable; import ru.noties.markwon.image.DrawableUtils; @@ -58,6 +60,12 @@ public class GifMediaDecoder extends MediaDecoder { return drawable; } + @NonNull + @Override + public Collection supportedTypes() { + return Collections.singleton(CONTENT_TYPE); + } + @NonNull protected GifDrawable newGifDrawable(@NonNull byte[] bytes) throws IOException { return new GifDrawable(bytes); diff --git a/markwon-image/src/main/java/ru/noties/markwon/image/network/NetworkSchemeHandler.java b/markwon-image/src/main/java/ru/noties/markwon/image/network/NetworkSchemeHandler.java index c48d54d3..a9dbfaee 100644 --- a/markwon-image/src/main/java/ru/noties/markwon/image/network/NetworkSchemeHandler.java +++ b/markwon-image/src/main/java/ru/noties/markwon/image/network/NetworkSchemeHandler.java @@ -9,6 +9,8 @@ import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; +import java.util.Arrays; +import java.util.Collection; import ru.noties.markwon.image.ImageItem; import ru.noties.markwon.image.SchemeHandler; @@ -56,6 +58,12 @@ public class NetworkSchemeHandler extends SchemeHandler { return imageItem; } + @NonNull + @Override + public Collection supportedSchemes() { + return Arrays.asList(SCHEME_HTTP, SCHEME_HTTPS); + } + @Nullable static String contentType(@Nullable String contentType) { diff --git a/markwon-image/src/main/java/ru/noties/markwon/image/network/OkHttpNetworkSchemeHandler.java b/markwon-image/src/main/java/ru/noties/markwon/image/network/OkHttpNetworkSchemeHandler.java index 0433fd13..7faec7e5 100644 --- a/markwon-image/src/main/java/ru/noties/markwon/image/network/OkHttpNetworkSchemeHandler.java +++ b/markwon-image/src/main/java/ru/noties/markwon/image/network/OkHttpNetworkSchemeHandler.java @@ -3,8 +3,9 @@ package ru.noties.markwon.image.network; import android.net.Uri; import android.support.annotation.NonNull; -import java.io.IOException; import java.io.InputStream; +import java.util.Arrays; +import java.util.Collection; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -16,7 +17,7 @@ import ru.noties.markwon.image.SchemeHandler; /** * @since 4.0.0-SNAPSHOT */ -class OkHttpNetworkSchemeHandler extends SchemeHandler { +public class OkHttpNetworkSchemeHandler extends SchemeHandler { /** * @see #create(OkHttpClient) @@ -51,8 +52,8 @@ class OkHttpNetworkSchemeHandler extends SchemeHandler { final Response response; try { response = client.newCall(request).execute(); - } catch (IOException e) { - throw new IllegalStateException("Exception obtaining network resource: " + raw, e); + } catch (Throwable t) { + throw new IllegalStateException("Exception obtaining network resource: " + raw, t); } if (response == null) { @@ -68,8 +69,18 @@ class OkHttpNetworkSchemeHandler extends SchemeHandler { throw new IllegalStateException("Response does not contain body: " + raw); } - final String contentType = response.header(HEADER_CONTENT_TYPE); + // important to process content-type as it can have encoding specified (which we should remove) + final String contentType = + NetworkSchemeHandler.contentType(response.header(HEADER_CONTENT_TYPE)); return ImageItem.withDecodingNeeded(contentType, inputStream); } + + @NonNull + @Override + public Collection supportedSchemes() { + return Arrays.asList( + NetworkSchemeHandler.SCHEME_HTTP, + NetworkSchemeHandler.SCHEME_HTTPS); + } } diff --git a/markwon-image/src/main/java/ru/noties/markwon/image/svg/SvgMediaDecoder.java b/markwon-image/src/main/java/ru/noties/markwon/image/svg/SvgMediaDecoder.java index cba9fb39..e9484a25 100644 --- a/markwon-image/src/main/java/ru/noties/markwon/image/svg/SvgMediaDecoder.java +++ b/markwon-image/src/main/java/ru/noties/markwon/image/svg/SvgMediaDecoder.java @@ -12,6 +12,8 @@ import com.caverock.androidsvg.SVG; import com.caverock.androidsvg.SVGParseException; import java.io.InputStream; +import java.util.Collection; +import java.util.Collections; import ru.noties.markwon.image.DrawableUtils; import ru.noties.markwon.image.MediaDecoder; @@ -71,4 +73,10 @@ public class SvgMediaDecoder extends MediaDecoder { DrawableUtils.applyIntrinsicBounds(drawable); return drawable; } + + @NonNull + @Override + public Collection supportedTypes() { + return Collections.singleton(CONTENT_TYPE); + } } diff --git a/sample/build.gradle b/sample/build.gradle index dbe4bdaa..649e3a8e 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -39,9 +39,7 @@ dependencies { implementation project(':markwon-ext-tables') implementation project(':markwon-ext-tasklist') implementation project(':markwon-html') - implementation project(':markwon-image-gif') - implementation project(':markwon-image-okhttp') - implementation project(':markwon-image-svg') + implementation project(':markwon-image') implementation project(':markwon-syntax-highlight') implementation project(':markwon-recycler') implementation project(':markwon-recycler-table') diff --git a/sample/src/main/java/ru/noties/markwon/sample/basicplugins/BasicPluginsActivity.java b/sample/src/main/java/ru/noties/markwon/sample/basicplugins/BasicPluginsActivity.java index 0728be6c..45f35cf8 100644 --- a/sample/src/main/java/ru/noties/markwon/sample/basicplugins/BasicPluginsActivity.java +++ b/sample/src/main/java/ru/noties/markwon/sample/basicplugins/BasicPluginsActivity.java @@ -22,10 +22,6 @@ import ru.noties.markwon.MarkwonVisitor; import ru.noties.markwon.movement.MovementMethodPlugin; import ru.noties.markwon.core.MarkwonTheme; import ru.noties.markwon.image.AsyncDrawableLoader; -import ru.noties.markwon.image.ImageItem; -import ru.noties.markwon.image.ImagesPlugin; -import ru.noties.markwon.image.SchemeHandler; -import ru.noties.markwon.image.network.NetworkSchemeHandler; public class BasicPluginsActivity extends Activity { diff --git a/sample/src/main/java/ru/noties/markwon/sample/customextension/CustomExtensionActivity.java b/sample/src/main/java/ru/noties/markwon/sample/customextension/CustomExtensionActivity.java index 4a9c2fd9..d2e80e18 100644 --- a/sample/src/main/java/ru/noties/markwon/sample/customextension/CustomExtensionActivity.java +++ b/sample/src/main/java/ru/noties/markwon/sample/customextension/CustomExtensionActivity.java @@ -6,7 +6,6 @@ import android.support.annotation.Nullable; import android.widget.TextView; import ru.noties.markwon.Markwon; -import ru.noties.markwon.image.ImagesPlugin; import ru.noties.markwon.sample.R; public class CustomExtensionActivity extends Activity { diff --git a/sample/src/main/java/ru/noties/markwon/sample/customextension/IconPlugin.java b/sample/src/main/java/ru/noties/markwon/sample/customextension/IconPlugin.java index 3de96315..cbd2348b 100644 --- a/sample/src/main/java/ru/noties/markwon/sample/customextension/IconPlugin.java +++ b/sample/src/main/java/ru/noties/markwon/sample/customextension/IconPlugin.java @@ -7,8 +7,6 @@ import org.commonmark.parser.Parser; import ru.noties.markwon.AbstractMarkwonPlugin; import ru.noties.markwon.MarkwonVisitor; -import ru.noties.markwon.image.ImagesPlugin; -import ru.noties.markwon.priority.Priority; public class IconPlugin extends AbstractMarkwonPlugin { diff --git a/sample/src/main/java/ru/noties/markwon/sample/latex/LatexActivity.java b/sample/src/main/java/ru/noties/markwon/sample/latex/LatexActivity.java index d4143a76..40da04f7 100644 --- a/sample/src/main/java/ru/noties/markwon/sample/latex/LatexActivity.java +++ b/sample/src/main/java/ru/noties/markwon/sample/latex/LatexActivity.java @@ -7,7 +7,6 @@ import android.widget.TextView; import ru.noties.markwon.Markwon; import ru.noties.markwon.ext.latex.JLatexMathPlugin; -import ru.noties.markwon.image.ImagesPlugin; import ru.noties.markwon.sample.R; public class LatexActivity extends Activity { diff --git a/sample/src/main/java/ru/noties/markwon/sample/recycler/RecyclerActivity.java b/sample/src/main/java/ru/noties/markwon/sample/recycler/RecyclerActivity.java index adeb6c4b..2bed0452 100644 --- a/sample/src/main/java/ru/noties/markwon/sample/recycler/RecyclerActivity.java +++ b/sample/src/main/java/ru/noties/markwon/sample/recycler/RecyclerActivity.java @@ -26,7 +26,6 @@ import ru.noties.markwon.MarkwonConfiguration; import ru.noties.markwon.MarkwonVisitor; import ru.noties.markwon.core.CorePlugin; import ru.noties.markwon.html.HtmlPlugin; -import ru.noties.markwon.image.ImagesPlugin; import ru.noties.markwon.image.svg.SvgPlugin; import ru.noties.markwon.recycler.MarkwonAdapter; import ru.noties.markwon.recycler.SimpleEntry; diff --git a/settings.gradle b/settings.gradle index 258291b4..f0568523 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,9 +7,6 @@ include ':app', ':sample', ':markwon-ext-tasklist', ':markwon-html', ':markwon-image', - ':markwon-image-gif', - ':markwon-image-okhttp', - ':markwon-image-svg', ':markwon-recycler', ':markwon-recycler-table', ':markwon-syntax-highlight',