Moved image loading into separate module
This commit is contained in:
parent
661f72da0f
commit
5bf21bc940
2
_CHANGES.md
Normal file
2
_CHANGES.md
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
* `Markwon.builder` won't require CorePlugin registration (it is done automatically)
|
||||||
|
to create a builder without CorePlugin - use `Markwon#builderNoCore`
|
@ -33,8 +33,7 @@ dependencies {
|
|||||||
implementation project(':markwon-ext-tables')
|
implementation project(':markwon-ext-tables')
|
||||||
implementation project(':markwon-ext-tasklist')
|
implementation project(':markwon-ext-tasklist')
|
||||||
implementation project(':markwon-html')
|
implementation project(':markwon-html')
|
||||||
implementation project(':markwon-image-gif')
|
implementation project(':markwon-image')
|
||||||
implementation project(':markwon-image-svg')
|
|
||||||
implementation project(':markwon-syntax-highlight')
|
implementation project(':markwon-syntax-highlight')
|
||||||
|
|
||||||
deps.with {
|
deps.with {
|
||||||
@ -42,6 +41,8 @@ dependencies {
|
|||||||
implementation it['prism4j']
|
implementation it['prism4j']
|
||||||
implementation it['debug']
|
implementation it['debug']
|
||||||
implementation it['dagger']
|
implementation it['dagger']
|
||||||
|
implementation it['android-svg']
|
||||||
|
implementation it['android-gif']
|
||||||
}
|
}
|
||||||
|
|
||||||
deps['annotationProcessor'].with {
|
deps['annotationProcessor'].with {
|
||||||
|
@ -14,15 +14,18 @@ import java.util.concurrent.Future;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import ru.noties.debug.Debug;
|
import ru.noties.debug.Debug;
|
||||||
import ru.noties.markwon.core.CorePlugin;
|
|
||||||
import ru.noties.markwon.ext.strikethrough.StrikethroughPlugin;
|
import ru.noties.markwon.ext.strikethrough.StrikethroughPlugin;
|
||||||
import ru.noties.markwon.ext.tables.TablePlugin;
|
import ru.noties.markwon.ext.tables.TablePlugin;
|
||||||
import ru.noties.markwon.ext.tasklist.TaskListPlugin;
|
import ru.noties.markwon.ext.tasklist.TaskListPlugin;
|
||||||
import ru.noties.markwon.gif.GifAwarePlugin;
|
import ru.noties.markwon.gif.GifAwarePlugin;
|
||||||
import ru.noties.markwon.html.HtmlPlugin;
|
import ru.noties.markwon.html.HtmlPlugin;
|
||||||
|
import ru.noties.markwon.image.DefaultImageMediaDecoder;
|
||||||
import ru.noties.markwon.image.ImagesPlugin;
|
import ru.noties.markwon.image.ImagesPlugin;
|
||||||
import ru.noties.markwon.image.gif.GifPlugin;
|
import ru.noties.markwon.image.data.DataUriSchemeHandler;
|
||||||
import ru.noties.markwon.image.svg.SvgPlugin;
|
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.Prism4jTheme;
|
||||||
import ru.noties.markwon.syntax.Prism4jThemeDarkula;
|
import ru.noties.markwon.syntax.Prism4jThemeDarkula;
|
||||||
import ru.noties.markwon.syntax.Prism4jThemeDefault;
|
import ru.noties.markwon.syntax.Prism4jThemeDefault;
|
||||||
@ -94,10 +97,18 @@ public class MarkdownRenderer {
|
|||||||
: prism4JThemeDarkula;
|
: prism4JThemeDarkula;
|
||||||
|
|
||||||
final Markwon markwon = Markwon.builder(context)
|
final Markwon markwon = Markwon.builder(context)
|
||||||
.usePlugin(CorePlugin.create())
|
.usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
|
||||||
.usePlugin(ImagesPlugin.createWithAssets(context))
|
@Override
|
||||||
.usePlugin(SvgPlugin.create(context.getResources()))
|
public void configureImages(@NonNull ImagesPlugin plugin) {
|
||||||
.usePlugin(GifPlugin.create(false))
|
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(SyntaxHighlightPlugin.create(prism4j, prism4jTheme))
|
||||||
.usePlugin(GifAwarePlugin.create(context))
|
.usePlugin(GifAwarePlugin.create(context))
|
||||||
.usePlugin(TablePlugin.create(context))
|
.usePlugin(TablePlugin.create(context))
|
||||||
|
@ -14,8 +14,6 @@ import ru.noties.markwon.RenderProps;
|
|||||||
import ru.noties.markwon.SpanFactory;
|
import ru.noties.markwon.SpanFactory;
|
||||||
import ru.noties.markwon.image.AsyncDrawableSpan;
|
import ru.noties.markwon.image.AsyncDrawableSpan;
|
||||||
import ru.noties.markwon.image.ImageProps;
|
import ru.noties.markwon.image.ImageProps;
|
||||||
import ru.noties.markwon.image.ImagesPlugin;
|
|
||||||
import ru.noties.markwon.priority.Priority;
|
|
||||||
|
|
||||||
public class GifAwarePlugin extends AbstractMarkwonPlugin {
|
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
|
@Override
|
||||||
public void afterSetText(@NonNull TextView textView) {
|
public void afterSetText(@NonNull TextView textView) {
|
||||||
processor.process(textView);
|
processor.process(textView);
|
||||||
|
@ -7,11 +7,8 @@ import android.widget.TextView;
|
|||||||
import org.commonmark.node.Node;
|
import org.commonmark.node.Node;
|
||||||
import org.commonmark.parser.Parser;
|
import org.commonmark.parser.Parser;
|
||||||
|
|
||||||
import ru.noties.markwon.core.CorePlugin;
|
|
||||||
import ru.noties.markwon.core.MarkwonTheme;
|
import ru.noties.markwon.core.MarkwonTheme;
|
||||||
import ru.noties.markwon.html.MarkwonHtmlRenderer;
|
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)
|
* Class that extends {@link MarkwonPlugin} with all methods implemented (empty body)
|
||||||
|
@ -40,7 +40,6 @@ import ru.noties.markwon.core.factory.ListItemSpanFactory;
|
|||||||
import ru.noties.markwon.core.factory.StrongEmphasisSpanFactory;
|
import ru.noties.markwon.core.factory.StrongEmphasisSpanFactory;
|
||||||
import ru.noties.markwon.core.factory.ThematicBreakSpanFactory;
|
import ru.noties.markwon.core.factory.ThematicBreakSpanFactory;
|
||||||
import ru.noties.markwon.core.spans.OrderedListItemSpan;
|
import ru.noties.markwon.core.spans.OrderedListItemSpan;
|
||||||
import ru.noties.markwon.priority.Priority;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see CoreProps
|
* @see CoreProps
|
||||||
|
@ -40,7 +40,7 @@ public class AsyncDrawable extends Drawable {
|
|||||||
this.imageSizeResolver = imageSizeResolver;
|
this.imageSizeResolver = imageSizeResolver;
|
||||||
this.imageSize = imageSize;
|
this.imageSize = imageSize;
|
||||||
|
|
||||||
final Drawable placeholder = loader.placeholder();
|
final Drawable placeholder = loader.placeholder(this);
|
||||||
if (placeholder != null) {
|
if (placeholder != null) {
|
||||||
setPlaceholderResult(placeholder);
|
setPlaceholderResult(placeholder);
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,6 @@ package ru.noties.markwon.image;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
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 {
|
public abstract class AsyncDrawableLoader {
|
||||||
|
|
||||||
@ -31,28 +24,9 @@ public abstract class AsyncDrawableLoader {
|
|||||||
*/
|
*/
|
||||||
public abstract void cancel(@NonNull AsyncDrawable drawable);
|
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
|
@Nullable
|
||||||
public abstract Drawable placeholder();
|
public abstract Drawable placeholder(@NonNull AsyncDrawable drawable);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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<String, SchemeHandler> schemeHandlers;
|
|
||||||
private final Map<String, MediaDecoder> 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<WeakReference<AsyncDrawable>, 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<AsyncDrawable> 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<Map.Entry<WeakReference<AsyncDrawable>, Future<?>>> iterator =
|
|
||||||
requests.entrySet().iterator();
|
|
||||||
|
|
||||||
AsyncDrawable key;
|
|
||||||
Map.Entry<WeakReference<AsyncDrawable>, 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<Map.Entry<WeakReference<AsyncDrawable>, Future<?>>> iterator =
|
|
||||||
requests.entrySet().iterator();
|
|
||||||
|
|
||||||
boolean result = false;
|
|
||||||
|
|
||||||
AsyncDrawable key;
|
|
||||||
Map.Entry<WeakReference<AsyncDrawable>, 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<Map.Entry<WeakReference<AsyncDrawable>, Future<?>>> iterator =
|
|
||||||
requests.entrySet().iterator();
|
|
||||||
|
|
||||||
AsyncDrawable key;
|
|
||||||
Map.Entry<WeakReference<AsyncDrawable>, 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<AsyncDrawable> 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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,7 +4,7 @@ import android.graphics.drawable.Drawable;
|
|||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
class AsyncDrawableLoaderNoOp extends AsyncDrawableLoader {
|
public class AsyncDrawableLoaderNoOp extends AsyncDrawableLoader {
|
||||||
@Override
|
@Override
|
||||||
public void load(@NonNull AsyncDrawable drawable) {
|
public void load(@NonNull AsyncDrawable drawable) {
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ class AsyncDrawableLoaderNoOp extends AsyncDrawableLoader {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Drawable placeholder() {
|
public Drawable placeholder(@NonNull AsyncDrawable drawable) {
|
||||||
return null;
|
return 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<Image>() {
|
|
||||||
@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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<String> 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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<? extends MarkwonPlugin> plugin) {
|
|
||||||
return builder().after(plugin).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static Priority after(
|
|
||||||
@NonNull Class<? extends MarkwonPlugin> plugin1,
|
|
||||||
@NonNull Class<? extends MarkwonPlugin> 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<? extends MarkwonPlugin> plugin);
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
Priority build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public abstract List<Class<? extends MarkwonPlugin>> after();
|
|
||||||
|
|
||||||
|
|
||||||
static class Impl extends Priority {
|
|
||||||
|
|
||||||
private final List<Class<? extends MarkwonPlugin>> after;
|
|
||||||
|
|
||||||
Impl(@NonNull List<Class<? extends MarkwonPlugin>> after) {
|
|
||||||
this.after = after;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public List<Class<? extends MarkwonPlugin>> after() {
|
|
||||||
return after;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Priority{" +
|
|
||||||
"after=" + after +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
|
|
||||||
static class BuilderImpl implements Builder {
|
|
||||||
|
|
||||||
private final List<Class<? extends MarkwonPlugin>> after = new ArrayList<>(0);
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Builder after(@NonNull Class<? extends MarkwonPlugin> plugin) {
|
|
||||||
after.add(plugin);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Priority build() {
|
|
||||||
return new Impl(Collections.unmodifiableList(after));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<MarkwonPlugin> process(@NonNull List<MarkwonPlugin> plugins);
|
|
||||||
}
|
|
@ -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<MarkwonPlugin> process(@NonNull List<MarkwonPlugin> in) {
|
|
||||||
|
|
||||||
// create new collection based on supplied argument
|
|
||||||
final List<MarkwonPlugin> plugins = new ArrayList<>(in);
|
|
||||||
|
|
||||||
final int size = plugins.size();
|
|
||||||
|
|
||||||
final Map<Class<? extends MarkwonPlugin>, Set<Class<? extends MarkwonPlugin>>> 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<MarkwonPlugin, Integer> 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<Class<? extends MarkwonPlugin>, Set<Class<? extends MarkwonPlugin>>> map) {
|
|
||||||
|
|
||||||
final Set<Class<? extends MarkwonPlugin>> set = map.get(plugin.getClass());
|
|
||||||
|
|
||||||
// no dependencies
|
|
||||||
if (set.isEmpty()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Class<? extends MarkwonPlugin> who = plugin.getClass();
|
|
||||||
|
|
||||||
int max = 0;
|
|
||||||
|
|
||||||
for (Class<? extends MarkwonPlugin> 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<? extends MarkwonPlugin> who,
|
|
||||||
@NonNull Class<? extends MarkwonPlugin> plugin,
|
|
||||||
@NonNull Map<Class<? extends MarkwonPlugin>, Set<Class<? extends MarkwonPlugin>>> map) {
|
|
||||||
|
|
||||||
// exact match
|
|
||||||
Set<Class<? extends MarkwonPlugin>> set = map.get(plugin);
|
|
||||||
|
|
||||||
if (set == null) {
|
|
||||||
|
|
||||||
// let's try to find inexact type (overridden/subclassed)
|
|
||||||
for (Map.Entry<Class<? extends MarkwonPlugin>, Set<Class<? extends MarkwonPlugin>>> 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<? extends MarkwonPlugin> 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<MarkwonPlugin> {
|
|
||||||
|
|
||||||
private final Map<MarkwonPlugin, Integer> map;
|
|
||||||
|
|
||||||
PriorityComparator(@NonNull Map<MarkwonPlugin, Integer> map) {
|
|
||||||
this.map = map;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(MarkwonPlugin o1, MarkwonPlugin o2) {
|
|
||||||
return map.get(o1).compareTo(map.get(o2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,7 +8,6 @@ import org.robolectric.annotation.Config;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import ru.noties.markwon.core.CorePlugin;
|
import ru.noties.markwon.core.CorePlugin;
|
||||||
import ru.noties.markwon.priority.Priority;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
@ -23,8 +23,6 @@ import ru.noties.markwon.core.CorePlugin;
|
|||||||
import ru.noties.markwon.core.MarkwonTheme;
|
import ru.noties.markwon.core.MarkwonTheme;
|
||||||
import ru.noties.markwon.html.MarkwonHtmlRenderer;
|
import ru.noties.markwon.html.MarkwonHtmlRenderer;
|
||||||
import ru.noties.markwon.image.AsyncDrawableLoader;
|
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.containsString;
|
||||||
import static org.hamcrest.Matchers.hasItem;
|
import static org.hamcrest.Matchers.hasItem;
|
||||||
|
@ -14,8 +14,6 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
|
|
||||||
import ru.noties.markwon.image.ImageItem;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
@ -18,7 +18,6 @@ import java.util.List;
|
|||||||
import ru.noties.markwon.AbstractMarkwonPlugin;
|
import ru.noties.markwon.AbstractMarkwonPlugin;
|
||||||
import ru.noties.markwon.MarkwonPlugin;
|
import ru.noties.markwon.MarkwonPlugin;
|
||||||
import ru.noties.markwon.core.CorePlugin;
|
import ru.noties.markwon.core.CorePlugin;
|
||||||
import ru.noties.markwon.image.ImagesPlugin;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
@ -19,13 +19,7 @@ import ru.noties.markwon.AbstractMarkwonPlugin;
|
|||||||
import ru.noties.markwon.MarkwonVisitor;
|
import ru.noties.markwon.MarkwonVisitor;
|
||||||
import ru.noties.markwon.RenderProps;
|
import ru.noties.markwon.RenderProps;
|
||||||
import ru.noties.markwon.image.AsyncDrawableLoader;
|
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.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
|
* @since 3.0.0
|
||||||
|
@ -10,7 +10,6 @@ import java.io.InputStream;
|
|||||||
|
|
||||||
import pl.droidsonroids.gif.GifDrawable;
|
import pl.droidsonroids.gif.GifDrawable;
|
||||||
import ru.noties.markwon.image.DrawableUtils;
|
import ru.noties.markwon.image.DrawableUtils;
|
||||||
import ru.noties.markwon.image.MediaDecoder;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 1.1.0
|
* @since 1.1.0
|
||||||
|
@ -4,7 +4,6 @@ import android.support.annotation.NonNull;
|
|||||||
|
|
||||||
import ru.noties.markwon.AbstractMarkwonPlugin;
|
import ru.noties.markwon.AbstractMarkwonPlugin;
|
||||||
import ru.noties.markwon.image.AsyncDrawableLoader;
|
import ru.noties.markwon.image.AsyncDrawableLoader;
|
||||||
import ru.noties.markwon.image.ImagesPlugin;
|
|
||||||
import ru.noties.markwon.priority.Priority;
|
import ru.noties.markwon.priority.Priority;
|
||||||
|
|
||||||
public class GifPlugin extends AbstractMarkwonPlugin {
|
public class GifPlugin extends AbstractMarkwonPlugin {
|
||||||
|
@ -7,8 +7,6 @@ import java.util.Arrays;
|
|||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import ru.noties.markwon.AbstractMarkwonPlugin;
|
import ru.noties.markwon.AbstractMarkwonPlugin;
|
||||||
import ru.noties.markwon.image.AsyncDrawableLoader;
|
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;
|
import ru.noties.markwon.priority.Priority;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,8 +11,6 @@ import okhttp3.OkHttpClient;
|
|||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
import okhttp3.ResponseBody;
|
import okhttp3.ResponseBody;
|
||||||
import ru.noties.markwon.image.ImageItem;
|
|
||||||
import ru.noties.markwon.image.SchemeHandler;
|
|
||||||
|
|
||||||
class OkHttpSchemeHandler extends SchemeHandler {
|
class OkHttpSchemeHandler extends SchemeHandler {
|
||||||
|
|
||||||
|
@ -14,7 +14,6 @@ import com.caverock.androidsvg.SVGParseException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
import ru.noties.markwon.image.DrawableUtils;
|
import ru.noties.markwon.image.DrawableUtils;
|
||||||
import ru.noties.markwon.image.MediaDecoder;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 1.1.0
|
* @since 1.1.0
|
||||||
|
@ -5,7 +5,6 @@ import android.support.annotation.NonNull;
|
|||||||
|
|
||||||
import ru.noties.markwon.AbstractMarkwonPlugin;
|
import ru.noties.markwon.AbstractMarkwonPlugin;
|
||||||
import ru.noties.markwon.image.AsyncDrawableLoader;
|
import ru.noties.markwon.image.AsyncDrawableLoader;
|
||||||
import ru.noties.markwon.image.ImagesPlugin;
|
|
||||||
import ru.noties.markwon.priority.Priority;
|
import ru.noties.markwon.priority.Priority;
|
||||||
|
|
||||||
public class SvgPlugin extends AbstractMarkwonPlugin {
|
public class SvgPlugin extends AbstractMarkwonPlugin {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
POM_NAME=Image
|
POM_NAME=Image
|
||||||
POM_ARTIFACT_ID=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
|
POM_PACKAGING=aar
|
@ -1,6 +1,5 @@
|
|||||||
package ru.noties.markwon.image;
|
package ru.noties.markwon.image;
|
||||||
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
@ -10,88 +9,69 @@ import java.util.Map;
|
|||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
public class AsyncDrawableLoaderBuilder {
|
class AsyncDrawableLoaderBuilder {
|
||||||
|
|
||||||
ExecutorService executorService;
|
ExecutorService executorService;
|
||||||
final Map<String, SchemeHandler> schemeHandlers = new HashMap<>(3);
|
final Map<String, SchemeHandler> schemeHandlers = new HashMap<>(3);
|
||||||
final Map<String, MediaDecoder> mediaDecoders = new HashMap<>(3);
|
final Map<String, MediaDecoder> mediaDecoders = new HashMap<>(3);
|
||||||
MediaDecoder defaultMediaDecoder;
|
MediaDecoder defaultMediaDecoder;
|
||||||
DrawableProvider placeholderDrawableProvider;
|
ImagesPlugin.PlaceholderProvider placeholderProvider;
|
||||||
DrawableProvider errorDrawableProvider;
|
ImagesPlugin.ErrorHandler errorHandler;
|
||||||
|
|
||||||
@NonNull
|
boolean isBuilt;
|
||||||
public AsyncDrawableLoaderBuilder executorService(@NonNull ExecutorService executorService) {
|
|
||||||
|
void executorService(@NonNull ExecutorService executorService) {
|
||||||
this.executorService = executorService;
|
this.executorService = executorService;
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
void addSchemeHandler(@NonNull SchemeHandler schemeHandler) {
|
||||||
public AsyncDrawableLoaderBuilder addSchemeHandler(@NonNull String scheme, @NonNull SchemeHandler schemeHandler) {
|
for (String scheme : schemeHandler.supportedSchemes()) {
|
||||||
schemeHandlers.put(scheme, schemeHandler);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public AsyncDrawableLoaderBuilder addSchemeHandler(@NonNull Collection<String> schemes, @NonNull SchemeHandler schemeHandler) {
|
|
||||||
for (String scheme : schemes) {
|
|
||||||
schemeHandlers.put(scheme, schemeHandler);
|
schemeHandlers.put(scheme, schemeHandler);
|
||||||
}
|
}
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
void addMediaDecoder(@NonNull MediaDecoder mediaDecoder) {
|
||||||
public AsyncDrawableLoaderBuilder addMediaDecoder(@NonNull String contentType, @NonNull MediaDecoder mediaDecoder) {
|
final Collection<String> supportedTypes = mediaDecoder.supportedTypes();
|
||||||
mediaDecoders.put(contentType, mediaDecoder);
|
if (supportedTypes.isEmpty()) {
|
||||||
return this;
|
// todo: we should think about this little _side-effect_... does it worth it?
|
||||||
}
|
defaultMediaDecoder = mediaDecoder;
|
||||||
|
} else {
|
||||||
@NonNull
|
for (String type : supportedTypes) {
|
||||||
public AsyncDrawableLoaderBuilder addMediaDecoder(@NonNull Collection<String> contentTypes, @NonNull MediaDecoder mediaDecoder) {
|
mediaDecoders.put(type, mediaDecoder);
|
||||||
for (String contentType : contentTypes) {
|
}
|
||||||
mediaDecoders.put(contentType, mediaDecoder);
|
|
||||||
}
|
}
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
void defaultMediaDecoder(@Nullable MediaDecoder mediaDecoder) {
|
||||||
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) {
|
|
||||||
this.defaultMediaDecoder = 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
|
* @since 3.0.0
|
||||||
*/
|
*/
|
||||||
@NonNull
|
void placeholderProvider(@NonNull ImagesPlugin.PlaceholderProvider placeholderDrawableProvider) {
|
||||||
public AsyncDrawableLoaderBuilder placeholderDrawableProvider(@NonNull DrawableProvider placeholderDrawableProvider) {
|
this.placeholderProvider = placeholderDrawableProvider;
|
||||||
this.placeholderDrawableProvider = placeholderDrawableProvider;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 3.0.0
|
* @since 3.0.0
|
||||||
*/
|
*/
|
||||||
@NonNull
|
void errorHandler(@NonNull ImagesPlugin.ErrorHandler errorHandler) {
|
||||||
public AsyncDrawableLoaderBuilder errorDrawableProvider(@NonNull DrawableProvider errorDrawableProvider) {
|
this.errorHandler = errorHandler;
|
||||||
this.errorDrawableProvider = errorDrawableProvider;
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public AsyncDrawableLoader build() {
|
AsyncDrawableLoader build() {
|
||||||
|
|
||||||
|
isBuilt = true;
|
||||||
|
|
||||||
// if we have no schemeHandlers -> we cannot show anything
|
// if we have no schemeHandlers -> we cannot show anything
|
||||||
// OR if we have no media decoders
|
// OR if we have no media decoders
|
||||||
|
@ -6,9 +6,8 @@ import android.os.Handler;
|
|||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@ -22,8 +21,8 @@ class AsyncDrawableLoaderImpl extends AsyncDrawableLoader {
|
|||||||
private final Map<String, SchemeHandler> schemeHandlers;
|
private final Map<String, SchemeHandler> schemeHandlers;
|
||||||
private final Map<String, MediaDecoder> mediaDecoders;
|
private final Map<String, MediaDecoder> mediaDecoders;
|
||||||
private final MediaDecoder defaultMediaDecoder;
|
private final MediaDecoder defaultMediaDecoder;
|
||||||
private final DrawableProvider placeholderDrawableProvider;
|
private final ImagesPlugin.PlaceholderProvider placeholderProvider;
|
||||||
private final DrawableProvider errorDrawableProvider;
|
private final ImagesPlugin.ErrorHandler errorHandler;
|
||||||
|
|
||||||
private final Handler mainThread;
|
private final Handler mainThread;
|
||||||
|
|
||||||
@ -31,13 +30,13 @@ class AsyncDrawableLoaderImpl extends AsyncDrawableLoader {
|
|||||||
// for the same destination
|
// for the same destination
|
||||||
private final Map<WeakReference<AsyncDrawable>, Future<?>> requests = new HashMap<>(2);
|
private final Map<WeakReference<AsyncDrawable>, Future<?>> requests = new HashMap<>(2);
|
||||||
|
|
||||||
AsyncDrawableLoaderImpl(@NonNull Builder builder) {
|
AsyncDrawableLoaderImpl(@NonNull AsyncDrawableLoaderBuilder builder) {
|
||||||
this.executorService = builder.executorService;
|
this.executorService = builder.executorService;
|
||||||
this.schemeHandlers = builder.schemeHandlers;
|
this.schemeHandlers = builder.schemeHandlers;
|
||||||
this.mediaDecoders = builder.mediaDecoders;
|
this.mediaDecoders = builder.mediaDecoders;
|
||||||
this.defaultMediaDecoder = builder.defaultMediaDecoder;
|
this.defaultMediaDecoder = builder.defaultMediaDecoder;
|
||||||
this.placeholderDrawableProvider = builder.placeholderDrawableProvider;
|
this.placeholderProvider = builder.placeholderProvider;
|
||||||
this.errorDrawableProvider = builder.errorDrawableProvider;
|
this.errorHandler = builder.errorHandler;
|
||||||
this.mainThread = new Handler(Looper.getMainLooper());
|
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
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Drawable placeholder() {
|
public Drawable placeholder(@NonNull AsyncDrawable drawable) {
|
||||||
return placeholderDrawableProvider != null
|
return placeholderProvider != null
|
||||||
? placeholderDrawableProvider.provide()
|
? placeholderProvider.providePlaceholder(drawable)
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
private Future<?> execute(@NonNull final String destination, @NonNull final WeakReference<AsyncDrawable> reference) {
|
private Future<?> execute(@NonNull final String destination, @NonNull final WeakReference<AsyncDrawable> reference) {
|
||||||
|
|
||||||
// todo: error handing (simply applying errorDrawable is not a good solution
|
// todo: more efficient DefaultImageMediaDecoder... BitmapFactory.decodeStream is a bit not optimal
|
||||||
// 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
|
// 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
|
// image bounds (but we will need to cache inputStream in order to inspect and optimize
|
||||||
// input image...)
|
// input image...)
|
||||||
@ -197,71 +166,60 @@ class AsyncDrawableLoaderImpl extends AsyncDrawableLoader {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|
||||||
final ImageItem item;
|
|
||||||
|
|
||||||
final Uri uri = Uri.parse(destination);
|
final Uri uri = Uri.parse(destination);
|
||||||
|
|
||||||
final SchemeHandler schemeHandler = schemeHandlers.get(uri.getScheme());
|
Drawable drawable = null;
|
||||||
|
|
||||||
if (schemeHandler != null) {
|
try {
|
||||||
item = schemeHandler.handle(destination, uri);
|
// obtain scheme handler
|
||||||
} else {
|
final SchemeHandler schemeHandler = schemeHandlers.get(uri.getScheme());
|
||||||
item = null;
|
if (schemeHandler != null) {
|
||||||
}
|
|
||||||
|
|
||||||
final InputStream inputStream = item != null
|
// handle scheme
|
||||||
? item.inputStream()
|
final ImageItem imageItem = schemeHandler.handle(destination, uri);
|
||||||
: null;
|
|
||||||
|
|
||||||
Drawable result = null;
|
// if resulting imageItem needs further decoding -> proceed
|
||||||
|
if (imageItem.hasDecodingNeeded()) {
|
||||||
|
|
||||||
if (inputStream != null) {
|
final ImageItem.WithDecodingNeeded withDecodingNeeded = imageItem.getAsWithDecodingNeeded();
|
||||||
try {
|
|
||||||
|
|
||||||
MediaDecoder mediaDecoder = mediaDecoders.get(item.contentType());
|
MediaDecoder mediaDecoder = mediaDecoders.get(withDecodingNeeded.contentType());
|
||||||
if (mediaDecoder == null) {
|
|
||||||
mediaDecoder = defaultMediaDecoder;
|
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) {
|
} catch (Throwable t) {
|
||||||
result = mediaDecoder.decode(inputStream);
|
if (errorHandler != null) {
|
||||||
}
|
drawable = errorHandler.handleError(destination, t);
|
||||||
|
} else {
|
||||||
} finally {
|
// else simply log the error
|
||||||
try {
|
Log.e("MARKWON-IMAGE", "Error loading image: " + destination, t);
|
||||||
inputStream.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if result is null, we assume it's an error
|
final Drawable out = drawable;
|
||||||
if (result == null) {
|
|
||||||
result = errorDrawableProvider != null
|
|
||||||
? errorDrawableProvider.provide()
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Drawable out = result;
|
|
||||||
|
|
||||||
mainThread.post(new Runnable() {
|
mainThread.post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|
||||||
if (out != null) {
|
if (out != null) {
|
||||||
|
// AsyncDrawable cannot change destination, so if it's
|
||||||
// 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.
|
// attached and not garbage-collected, we can deliver the result.
|
||||||
// Note that there is no cache, so attach/detach of drawables
|
// Note that there is no cache, so attach/detach of drawables
|
||||||
// will always request a new entry.. (comment @since 3.1.0-SNAPSHOT)
|
// will always request a new entry.. (comment @since 3.1.0-SNAPSHOT)
|
||||||
|
@ -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<String> supportedTypes() {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
package ru.noties.markwon.image;
|
package ru.noties.markwon.image;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
@ -11,6 +10,8 @@ import org.commonmark.node.Image;
|
|||||||
import org.commonmark.node.Link;
|
import org.commonmark.node.Link;
|
||||||
import org.commonmark.node.Node;
|
import org.commonmark.node.Node;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
import ru.noties.markwon.AbstractMarkwonPlugin;
|
import ru.noties.markwon.AbstractMarkwonPlugin;
|
||||||
import ru.noties.markwon.MarkwonConfiguration;
|
import ru.noties.markwon.MarkwonConfiguration;
|
||||||
import ru.noties.markwon.MarkwonSpansFactory;
|
import ru.noties.markwon.MarkwonSpansFactory;
|
||||||
@ -23,55 +24,123 @@ public class ImagesPlugin extends AbstractMarkwonPlugin {
|
|||||||
/**
|
/**
|
||||||
* @since 4.0.0-SNAPSHOT
|
* @since 4.0.0-SNAPSHOT
|
||||||
*/
|
*/
|
||||||
public interface DrawableProvider {
|
public interface ImagesConfigure {
|
||||||
@Nullable
|
void configureImages(@NonNull ImagesPlugin plugin);
|
||||||
Drawable provide();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static ImagesPlugin create(@NonNull Context context) {
|
|
||||||
return new ImagesPlugin(context, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Special scheme that is used {@code file:///android_asset/}
|
* @since 4.0.0-SNAPSHOT
|
||||||
*
|
*/
|
||||||
* @param context
|
public interface PlaceholderProvider {
|
||||||
* @return
|
@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
|
@NonNull
|
||||||
public static ImagesPlugin createWithAssets(@NonNull Context context) {
|
public static ImagesPlugin createEmpty() {
|
||||||
return new ImagesPlugin(context, true);
|
return new ImagesPlugin();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Context context;
|
@NonNull
|
||||||
private final boolean useAssets;
|
public static ImagesPlugin create(@NonNull ImagesConfigure configure) {
|
||||||
|
final ImagesPlugin plugin = new ImagesPlugin();
|
||||||
protected ImagesPlugin(Context context, boolean useAssets) {
|
configure.configureImages(plugin);
|
||||||
this.context = context;
|
return plugin;
|
||||||
this.useAssets = useAssets;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {
|
* Optional (by default new cached thread executor will be used)
|
||||||
//
|
*
|
||||||
// final FileSchemeHandler fileSchemeHandler = useAssets
|
* @since 4.0.0-SNAPSHOT
|
||||||
// ? FileSchemeHandler.createWithAssets(context.getAssets())
|
*/
|
||||||
// : FileSchemeHandler.create();
|
@NonNull
|
||||||
//
|
public ImagesPlugin executorService(@NonNull ExecutorService executorService) {
|
||||||
// builder
|
checkBuilderState();
|
||||||
// .addSchemeHandler(DataUriSchemeHandler.SCHEME, DataUriSchemeHandler.create())
|
builder.executorService(executorService);
|
||||||
// .addSchemeHandler(FileSchemeHandler.SCHEME, fileSchemeHandler)
|
return this;
|
||||||
// .addSchemeHandler(
|
}
|
||||||
// Arrays.asList(
|
|
||||||
// NetworkSchemeHandler.SCHEME_HTTP,
|
/**
|
||||||
// NetworkSchemeHandler.SCHEME_HTTPS),
|
* @see SchemeHandler
|
||||||
// NetworkSchemeHandler.create())
|
* @see ru.noties.markwon.image.data.DataUriSchemeHandler
|
||||||
// .defaultMediaDecoder(ImageMediaDecoder.create(context.getResources()));
|
* @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
|
@Override
|
||||||
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
|
public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
|
||||||
@ -132,4 +201,11 @@ public class ImagesPlugin extends AbstractMarkwonPlugin {
|
|||||||
public void afterSetText(@NonNull TextView textView) {
|
public void afterSetText(@NonNull TextView textView) {
|
||||||
AsyncDrawableScheduler.schedule(textView);
|
AsyncDrawableScheduler.schedule(textView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkBuilderState() {
|
||||||
|
if (builder.isBuilt) {
|
||||||
|
throw new IllegalStateException("ImagesPlugin has already been configured " +
|
||||||
|
"and cannot be modified any further");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import android.support.annotation.NonNull;
|
|||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 3.0.0
|
* @since 3.0.0
|
||||||
@ -16,14 +17,17 @@ public abstract class MediaDecoder {
|
|||||||
* <ul>
|
* <ul>
|
||||||
* <li>Returns `non-null` drawable</li>
|
* <li>Returns `non-null` drawable</li>
|
||||||
* <li>Added `contentType` method parameter</li>
|
* <li>Added `contentType` method parameter</li>
|
||||||
* <li>Added `throws Exception` to method signature</li>
|
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
|
||||||
* @throws Exception since 4.0.0-SNAPSHOT
|
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public abstract Drawable decode(
|
public abstract Drawable decode(
|
||||||
@Nullable String contentType,
|
@Nullable String contentType,
|
||||||
@NonNull InputStream inputStream
|
@NonNull InputStream inputStream
|
||||||
) throws Exception;
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 4.0.0-SNAPSHOT
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public abstract Collection<String> supportedTypes();
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ package ru.noties.markwon.image;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 3.0.0
|
* @since 3.0.0
|
||||||
*/
|
*/
|
||||||
@ -12,13 +14,17 @@ public abstract class SchemeHandler {
|
|||||||
* Changes since 4.0.0-SNAPSHOT:
|
* Changes since 4.0.0-SNAPSHOT:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Returns `non-null` image-item</li>
|
* <li>Returns `non-null` image-item</li>
|
||||||
* <li>added `throws Exception` to method signature</li>
|
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @throws Exception since 4.0.0-SNAPSHOT
|
|
||||||
* @see ImageItem#withResult(android.graphics.drawable.Drawable)
|
* @see ImageItem#withResult(android.graphics.drawable.Drawable)
|
||||||
* @see ImageItem#withDecodingNeeded(String, java.io.InputStream)
|
* @see ImageItem#withDecodingNeeded(String, java.io.InputStream)
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@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<String> supportedSchemes();
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ import android.net.Uri;
|
|||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
import ru.noties.markwon.image.ImageItem;
|
import ru.noties.markwon.image.ImageItem;
|
||||||
import ru.noties.markwon.image.SchemeHandler;
|
import ru.noties.markwon.image.SchemeHandler;
|
||||||
@ -61,4 +63,10 @@ public class DataUriSchemeHandler extends SchemeHandler {
|
|||||||
dataUri.contentType(),
|
dataUri.contentType(),
|
||||||
new ByteArrayInputStream(bytes));
|
new ByteArrayInputStream(bytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Collection<String> supportedSchemes() {
|
||||||
|
return Collections.singleton(SCHEME);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,8 @@ import java.io.FileInputStream;
|
|||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import ru.noties.markwon.image.ImageItem;
|
import ru.noties.markwon.image.ImageItem;
|
||||||
@ -111,4 +113,10 @@ public class FileSchemeHandler extends SchemeHandler {
|
|||||||
|
|
||||||
return ImageItem.withDecodingNeeded(contentType, inputStream);
|
return ImageItem.withDecodingNeeded(contentType, inputStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Collection<String> supportedSchemes() {
|
||||||
|
return Collections.singleton(SCHEME);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@ import android.support.annotation.Nullable;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
import pl.droidsonroids.gif.GifDrawable;
|
import pl.droidsonroids.gif.GifDrawable;
|
||||||
import ru.noties.markwon.image.DrawableUtils;
|
import ru.noties.markwon.image.DrawableUtils;
|
||||||
@ -58,6 +60,12 @@ public class GifMediaDecoder extends MediaDecoder {
|
|||||||
return drawable;
|
return drawable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Collection<String> supportedTypes() {
|
||||||
|
return Collections.singleton(CONTENT_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
protected GifDrawable newGifDrawable(@NonNull byte[] bytes) throws IOException {
|
protected GifDrawable newGifDrawable(@NonNull byte[] bytes) throws IOException {
|
||||||
return new GifDrawable(bytes);
|
return new GifDrawable(bytes);
|
||||||
|
@ -9,6 +9,8 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
import ru.noties.markwon.image.ImageItem;
|
import ru.noties.markwon.image.ImageItem;
|
||||||
import ru.noties.markwon.image.SchemeHandler;
|
import ru.noties.markwon.image.SchemeHandler;
|
||||||
@ -56,6 +58,12 @@ public class NetworkSchemeHandler extends SchemeHandler {
|
|||||||
return imageItem;
|
return imageItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Collection<String> supportedSchemes() {
|
||||||
|
return Arrays.asList(SCHEME_HTTP, SCHEME_HTTPS);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
static String contentType(@Nullable String contentType) {
|
static String contentType(@Nullable String contentType) {
|
||||||
|
|
||||||
|
@ -3,8 +3,9 @@ package ru.noties.markwon.image.network;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
@ -16,7 +17,7 @@ import ru.noties.markwon.image.SchemeHandler;
|
|||||||
/**
|
/**
|
||||||
* @since 4.0.0-SNAPSHOT
|
* @since 4.0.0-SNAPSHOT
|
||||||
*/
|
*/
|
||||||
class OkHttpNetworkSchemeHandler extends SchemeHandler {
|
public class OkHttpNetworkSchemeHandler extends SchemeHandler {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see #create(OkHttpClient)
|
* @see #create(OkHttpClient)
|
||||||
@ -51,8 +52,8 @@ class OkHttpNetworkSchemeHandler extends SchemeHandler {
|
|||||||
final Response response;
|
final Response response;
|
||||||
try {
|
try {
|
||||||
response = client.newCall(request).execute();
|
response = client.newCall(request).execute();
|
||||||
} catch (IOException e) {
|
} catch (Throwable t) {
|
||||||
throw new IllegalStateException("Exception obtaining network resource: " + raw, e);
|
throw new IllegalStateException("Exception obtaining network resource: " + raw, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response == null) {
|
if (response == null) {
|
||||||
@ -68,8 +69,18 @@ class OkHttpNetworkSchemeHandler extends SchemeHandler {
|
|||||||
throw new IllegalStateException("Response does not contain body: " + raw);
|
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);
|
return ImageItem.withDecodingNeeded(contentType, inputStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Collection<String> supportedSchemes() {
|
||||||
|
return Arrays.asList(
|
||||||
|
NetworkSchemeHandler.SCHEME_HTTP,
|
||||||
|
NetworkSchemeHandler.SCHEME_HTTPS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,8 @@ import com.caverock.androidsvg.SVG;
|
|||||||
import com.caverock.androidsvg.SVGParseException;
|
import com.caverock.androidsvg.SVGParseException;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
import ru.noties.markwon.image.DrawableUtils;
|
import ru.noties.markwon.image.DrawableUtils;
|
||||||
import ru.noties.markwon.image.MediaDecoder;
|
import ru.noties.markwon.image.MediaDecoder;
|
||||||
@ -71,4 +73,10 @@ public class SvgMediaDecoder extends MediaDecoder {
|
|||||||
DrawableUtils.applyIntrinsicBounds(drawable);
|
DrawableUtils.applyIntrinsicBounds(drawable);
|
||||||
return drawable;
|
return drawable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Collection<String> supportedTypes() {
|
||||||
|
return Collections.singleton(CONTENT_TYPE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,9 +39,7 @@ dependencies {
|
|||||||
implementation project(':markwon-ext-tables')
|
implementation project(':markwon-ext-tables')
|
||||||
implementation project(':markwon-ext-tasklist')
|
implementation project(':markwon-ext-tasklist')
|
||||||
implementation project(':markwon-html')
|
implementation project(':markwon-html')
|
||||||
implementation project(':markwon-image-gif')
|
implementation project(':markwon-image')
|
||||||
implementation project(':markwon-image-okhttp')
|
|
||||||
implementation project(':markwon-image-svg')
|
|
||||||
implementation project(':markwon-syntax-highlight')
|
implementation project(':markwon-syntax-highlight')
|
||||||
implementation project(':markwon-recycler')
|
implementation project(':markwon-recycler')
|
||||||
implementation project(':markwon-recycler-table')
|
implementation project(':markwon-recycler-table')
|
||||||
|
@ -22,10 +22,6 @@ import ru.noties.markwon.MarkwonVisitor;
|
|||||||
import ru.noties.markwon.movement.MovementMethodPlugin;
|
import ru.noties.markwon.movement.MovementMethodPlugin;
|
||||||
import ru.noties.markwon.core.MarkwonTheme;
|
import ru.noties.markwon.core.MarkwonTheme;
|
||||||
import ru.noties.markwon.image.AsyncDrawableLoader;
|
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 {
|
public class BasicPluginsActivity extends Activity {
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ import android.support.annotation.Nullable;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import ru.noties.markwon.Markwon;
|
import ru.noties.markwon.Markwon;
|
||||||
import ru.noties.markwon.image.ImagesPlugin;
|
|
||||||
import ru.noties.markwon.sample.R;
|
import ru.noties.markwon.sample.R;
|
||||||
|
|
||||||
public class CustomExtensionActivity extends Activity {
|
public class CustomExtensionActivity extends Activity {
|
||||||
|
@ -7,8 +7,6 @@ import org.commonmark.parser.Parser;
|
|||||||
|
|
||||||
import ru.noties.markwon.AbstractMarkwonPlugin;
|
import ru.noties.markwon.AbstractMarkwonPlugin;
|
||||||
import ru.noties.markwon.MarkwonVisitor;
|
import ru.noties.markwon.MarkwonVisitor;
|
||||||
import ru.noties.markwon.image.ImagesPlugin;
|
|
||||||
import ru.noties.markwon.priority.Priority;
|
|
||||||
|
|
||||||
public class IconPlugin extends AbstractMarkwonPlugin {
|
public class IconPlugin extends AbstractMarkwonPlugin {
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import ru.noties.markwon.Markwon;
|
import ru.noties.markwon.Markwon;
|
||||||
import ru.noties.markwon.ext.latex.JLatexMathPlugin;
|
import ru.noties.markwon.ext.latex.JLatexMathPlugin;
|
||||||
import ru.noties.markwon.image.ImagesPlugin;
|
|
||||||
import ru.noties.markwon.sample.R;
|
import ru.noties.markwon.sample.R;
|
||||||
|
|
||||||
public class LatexActivity extends Activity {
|
public class LatexActivity extends Activity {
|
||||||
|
@ -26,7 +26,6 @@ import ru.noties.markwon.MarkwonConfiguration;
|
|||||||
import ru.noties.markwon.MarkwonVisitor;
|
import ru.noties.markwon.MarkwonVisitor;
|
||||||
import ru.noties.markwon.core.CorePlugin;
|
import ru.noties.markwon.core.CorePlugin;
|
||||||
import ru.noties.markwon.html.HtmlPlugin;
|
import ru.noties.markwon.html.HtmlPlugin;
|
||||||
import ru.noties.markwon.image.ImagesPlugin;
|
|
||||||
import ru.noties.markwon.image.svg.SvgPlugin;
|
import ru.noties.markwon.image.svg.SvgPlugin;
|
||||||
import ru.noties.markwon.recycler.MarkwonAdapter;
|
import ru.noties.markwon.recycler.MarkwonAdapter;
|
||||||
import ru.noties.markwon.recycler.SimpleEntry;
|
import ru.noties.markwon.recycler.SimpleEntry;
|
||||||
|
@ -7,9 +7,6 @@ include ':app', ':sample',
|
|||||||
':markwon-ext-tasklist',
|
':markwon-ext-tasklist',
|
||||||
':markwon-html',
|
':markwon-html',
|
||||||
':markwon-image',
|
':markwon-image',
|
||||||
':markwon-image-gif',
|
|
||||||
':markwon-image-okhttp',
|
|
||||||
':markwon-image-svg',
|
|
||||||
':markwon-recycler',
|
':markwon-recycler',
|
||||||
':markwon-recycler-table',
|
':markwon-recycler-table',
|
||||||
':markwon-syntax-highlight',
|
':markwon-syntax-highlight',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user