Merge pull request #119 from noties/develop
3.0.1 * Add `AsyncDrawableLoader.Builder#implementation` method (#109) * AsyncDrawable allow placeholder to have independent size (#115) * `addFactory` method for MarkwonSpansFactory * Add optional spans for list blocks (bullet and ordered) * AsyncDrawable placeholder bounds fix * SpannableBuilder setSpans allow array of arrays * Add `requireFactory` method to MarkwonSpansFactory * Add DrawableUtils
This commit is contained in:
commit
77b34552b9
@ -1,6 +1,16 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
# 3.0.0
|
# 3.0.1
|
||||||
|
* Add `AsyncDrawableLoader.Builder#implementation` method (<GithubIssue id="109" />)
|
||||||
|
* AsyncDrawable allow placeholder to have independent size (<GithubIssue id="115" />)
|
||||||
|
* `addFactory` method for MarkwonSpansFactory
|
||||||
|
* Add optional spans for list blocks (bullet and ordered)
|
||||||
|
* AsyncDrawable placeholder bounds fix
|
||||||
|
* SpannableBuilder setSpans allow array of arrays
|
||||||
|
* Add `requireFactory` method to MarkwonSpansFactory
|
||||||
|
* Add DrawableUtils
|
||||||
|
|
||||||
|
## 3.0.0
|
||||||
* Plugins, plugins, plugins
|
* Plugins, plugins, plugins
|
||||||
* Split basic functionality blocks into standalone modules
|
* Split basic functionality blocks into standalone modules
|
||||||
* Maven artifacts group changed to `ru.noties.markwon` (previously had been `ru.noties`)
|
* Maven artifacts group changed to `ru.noties.markwon` (previously had been `ru.noties`)
|
||||||
|
@ -6,7 +6,7 @@ org.gradle.configureondemand=true
|
|||||||
android.enableBuildCache=true
|
android.enableBuildCache=true
|
||||||
android.buildCacheDir=build/pre-dex-cache
|
android.buildCacheDir=build/pre-dex-cache
|
||||||
|
|
||||||
VERSION_NAME=3.0.0
|
VERSION_NAME=3.0.1
|
||||||
|
|
||||||
GROUP=ru.noties.markwon
|
GROUP=ru.noties.markwon
|
||||||
POM_DESCRIPTION=Markwon markdown for Android
|
POM_DESCRIPTION=Markwon markdown for Android
|
||||||
|
@ -34,12 +34,32 @@ public interface MarkwonSpansFactory {
|
|||||||
@NonNull
|
@NonNull
|
||||||
<N extends Node> Builder setFactory(@NonNull Class<N> node, @Nullable SpanFactory factory);
|
<N extends Node> Builder setFactory(@NonNull Class<N> node, @Nullable SpanFactory factory);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to add a {@link SpanFactory} for a Node. This method will merge existing
|
||||||
|
* {@link SpanFactory} with the specified one.
|
||||||
|
*
|
||||||
|
* @since 3.0.1
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
<N extends Node> Builder addFactory(@NonNull Class<N> node, @NonNull SpanFactory factory);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Can be useful when <em>enhancing</em> an already defined SpanFactory with another one.
|
* Can be useful when <em>enhancing</em> an already defined SpanFactory with another one.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
<N extends Node> SpanFactory getFactory(@NonNull Class<N> node);
|
<N extends Node> SpanFactory getFactory(@NonNull Class<N> node);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To obtain current {@link SpanFactory} associated with specified node. Can be used
|
||||||
|
* when SpanFactory must be present for node. If it\'s not added/registered a runtime
|
||||||
|
* exception will be thrown
|
||||||
|
*
|
||||||
|
* @see #getFactory(Class)
|
||||||
|
* @since 3.0.1
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
<N extends Node> SpanFactory requireFactory(@NonNull Class<N> node);
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
MarkwonSpansFactory build();
|
MarkwonSpansFactory build();
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,10 @@ import android.support.annotation.Nullable;
|
|||||||
|
|
||||||
import org.commonmark.node.Node;
|
import org.commonmark.node.Node;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,16 +54,71 @@ class MarkwonSpansFactoryImpl implements MarkwonSpansFactory {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public <N extends Node> Builder addFactory(@NonNull Class<N> node, @NonNull SpanFactory factory) {
|
||||||
|
// if there is no factory registered for this node -> just add it
|
||||||
|
final SpanFactory existing = factories.get(node);
|
||||||
|
if (existing == null) {
|
||||||
|
factories.put(node, factory);
|
||||||
|
} else {
|
||||||
|
// existing span factory can be of CompositeSpanFactory at this point -> append to it
|
||||||
|
if (existing instanceof CompositeSpanFactory) {
|
||||||
|
((CompositeSpanFactory) existing).factories.add(factory);
|
||||||
|
} else {
|
||||||
|
// if it's not composite at this point -> make it
|
||||||
|
final CompositeSpanFactory compositeSpanFactory =
|
||||||
|
new CompositeSpanFactory(existing, factory);
|
||||||
|
factories.put(node, compositeSpanFactory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public <N extends Node> SpanFactory getFactory(@NonNull Class<N> node) {
|
public <N extends Node> SpanFactory getFactory(@NonNull Class<N> node) {
|
||||||
return factories.get(node);
|
return factories.get(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public <N extends Node> SpanFactory requireFactory(@NonNull Class<N> node) {
|
||||||
|
final SpanFactory factory = getFactory(node);
|
||||||
|
if (factory == null) {
|
||||||
|
throw new NullPointerException(node.getName());
|
||||||
|
}
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public MarkwonSpansFactory build() {
|
public MarkwonSpansFactory build() {
|
||||||
return new MarkwonSpansFactoryImpl(Collections.unmodifiableMap(factories));
|
return new MarkwonSpansFactoryImpl(Collections.unmodifiableMap(factories));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class CompositeSpanFactory implements SpanFactory {
|
||||||
|
|
||||||
|
final List<SpanFactory> factories;
|
||||||
|
|
||||||
|
CompositeSpanFactory(@NonNull SpanFactory first, @NonNull SpanFactory second) {
|
||||||
|
this.factories = new ArrayList<>(3);
|
||||||
|
this.factories.add(first);
|
||||||
|
this.factories.add(second);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) {
|
||||||
|
// please note that we do not check it factory itself returns an array of spans,
|
||||||
|
// as this behaviour is supported now (previously we supported only a single-level array)
|
||||||
|
final int length = factories.size();
|
||||||
|
final Object[] out = new Object[length];
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
out[i] = factories.get(i).getSpans(configuration, props);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,13 +38,9 @@ public class SpannableBuilder implements Appendable, CharSequence {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spans.getClass().isArray()) {
|
// @since 3.0.1 we introduce another method that recursively applies spans
|
||||||
for (Object o : ((Object[]) spans)) {
|
// allowing array of arrays (and more)
|
||||||
builder.setSpan(o, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
setSpansInternal(builder, spans, start, end);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
builder.setSpan(spans, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,4 +402,24 @@ public class SpannableBuilder implements Appendable, CharSequence {
|
|||||||
super(text);
|
super(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 3.0.1
|
||||||
|
*/
|
||||||
|
private static void setSpansInternal(
|
||||||
|
@NonNull SpannableBuilder builder,
|
||||||
|
@Nullable Object spans,
|
||||||
|
int start,
|
||||||
|
int end) {
|
||||||
|
if (spans != null) {
|
||||||
|
if (spans.getClass().isArray()) {
|
||||||
|
for (Object o : ((Object[]) spans)) {
|
||||||
|
// @since 3.0.1 recursively apply spans (allow array of arrays)
|
||||||
|
setSpansInternal(builder, o, start, end);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
builder.setSpan(spans, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,10 +17,16 @@ public class SimpleBlockNodeVisitor implements MarkwonVisitor.NodeVisitor<Node>
|
|||||||
@Override
|
@Override
|
||||||
public void visit(@NonNull MarkwonVisitor visitor, @NonNull Node node) {
|
public void visit(@NonNull MarkwonVisitor visitor, @NonNull Node node) {
|
||||||
|
|
||||||
|
// @since 3.0.1 we keep track of start in order to apply spans (optionally)
|
||||||
|
final int length = visitor.length();
|
||||||
|
|
||||||
visitor.ensureNewLine();
|
visitor.ensureNewLine();
|
||||||
|
|
||||||
visitor.visitChildren(node);
|
visitor.visitChildren(node);
|
||||||
|
|
||||||
|
// @since 3.0.1 we apply optional spans
|
||||||
|
visitor.setSpansForNodeOptional(node, length);
|
||||||
|
|
||||||
if (visitor.hasNext(node)) {
|
if (visitor.hasNext(node)) {
|
||||||
visitor.ensureNewLine();
|
visitor.ensureNewLine();
|
||||||
visitor.forceNewLine();
|
visitor.forceNewLine();
|
||||||
|
@ -42,22 +42,7 @@ public class AsyncDrawable extends Drawable {
|
|||||||
|
|
||||||
final Drawable placeholder = loader.placeholder();
|
final Drawable placeholder = loader.placeholder();
|
||||||
if (placeholder != null) {
|
if (placeholder != null) {
|
||||||
|
setPlaceholderResult(placeholder);
|
||||||
// process placeholder bounds
|
|
||||||
final Rect bounds = placeholder.getBounds();
|
|
||||||
if (bounds.isEmpty()) {
|
|
||||||
// set intrinsic bounds
|
|
||||||
final Rect rect = new Rect(
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
placeholder.getIntrinsicWidth(),
|
|
||||||
placeholder.getIntrinsicHeight());
|
|
||||||
placeholder.setBounds(rect);
|
|
||||||
setBounds(rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply placeholder immediately if we have one
|
|
||||||
setResult(placeholder);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,6 +95,38 @@ public class AsyncDrawable extends Drawable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 3.0.1
|
||||||
|
*/
|
||||||
|
protected void setPlaceholderResult(@NonNull Drawable placeholder) {
|
||||||
|
// okay, if placeholder has bounds -> use it, otherwise use original imageSize
|
||||||
|
|
||||||
|
final Rect rect = placeholder.getBounds();
|
||||||
|
|
||||||
|
if (rect.isEmpty()) {
|
||||||
|
// if bounds are empty -> just use placeholder as a regular result
|
||||||
|
DrawableUtils.applyIntrinsicBounds(placeholder);
|
||||||
|
setResult(placeholder);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// this condition should not be true for placeholder (at least for now)
|
||||||
|
if (result != null) {
|
||||||
|
// but it is, unregister current result
|
||||||
|
result.setCallback(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// placeholder has bounds specified -> use them until we have real result
|
||||||
|
this.result = placeholder;
|
||||||
|
this.result.setCallback(callback);
|
||||||
|
|
||||||
|
// use bounds directly
|
||||||
|
setBounds(rect);
|
||||||
|
|
||||||
|
// just in case -> so we do not update placeholder when we have canvas dimensions
|
||||||
|
waitingForDimensions = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setResult(@NonNull Drawable result) {
|
public void setResult(@NonNull Drawable result) {
|
||||||
|
|
||||||
// if we have previous one, detach it
|
// if we have previous one, detach it
|
||||||
@ -123,6 +140,24 @@ public class AsyncDrawable extends Drawable {
|
|||||||
initBounds();
|
initBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove result from this drawable (for example, in case of cancellation)
|
||||||
|
*
|
||||||
|
* @since 3.0.1
|
||||||
|
*/
|
||||||
|
public void clearResult() {
|
||||||
|
|
||||||
|
final Drawable result = this.result;
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
result.setCallback(null);
|
||||||
|
this.result = null;
|
||||||
|
|
||||||
|
// clear bounds
|
||||||
|
setBounds(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void initBounds() {
|
private void initBounds() {
|
||||||
|
|
||||||
if (canvasWidth == 0) {
|
if (canvasWidth == 0) {
|
||||||
|
@ -53,6 +53,8 @@ public abstract class AsyncDrawableLoader {
|
|||||||
DrawableProvider placeholderDrawableProvider;
|
DrawableProvider placeholderDrawableProvider;
|
||||||
DrawableProvider errorDrawableProvider;
|
DrawableProvider errorDrawableProvider;
|
||||||
|
|
||||||
|
AsyncDrawableLoader implementation;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public Builder executorService(@NonNull ExecutorService executorService) {
|
public Builder executorService(@NonNull ExecutorService executorService) {
|
||||||
this.executorService = executorService;
|
this.executorService = executorService;
|
||||||
@ -123,9 +125,29 @@ public abstract class AsyncDrawableLoader {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Please note that if implementation is supplied, all configuration properties
|
||||||
|
* (scheme-handlers, media-decoders, placeholder, etc) of this builder instance
|
||||||
|
* will be ignored.
|
||||||
|
*
|
||||||
|
* @param implementation {@link AsyncDrawableLoader} implementation to be used.
|
||||||
|
* @since 3.0.1
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public Builder implementation(@NonNull AsyncDrawableLoader implementation) {
|
||||||
|
this.implementation = implementation;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public AsyncDrawableLoader build() {
|
public AsyncDrawableLoader build() {
|
||||||
|
|
||||||
|
// NB, all other configuration properties will be ignored if
|
||||||
|
// implementation is specified
|
||||||
|
if (implementation != null) {
|
||||||
|
return implementation;
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
if (schemeHandlers.size() == 0
|
if (schemeHandlers.size() == 0
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package ru.noties.markwon.image;
|
||||||
|
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.support.annotation.CheckResult;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 3.0.1
|
||||||
|
*/
|
||||||
|
public abstract class DrawableUtils {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@CheckResult
|
||||||
|
public static Rect intrinsicBounds(@NonNull Drawable drawable) {
|
||||||
|
return new Rect(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void applyIntrinsicBounds(@NonNull Drawable drawable) {
|
||||||
|
drawable.setBounds(intrinsicBounds(drawable));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void applyIntrinsicBoundsIfEmpty(@NonNull Drawable drawable) {
|
||||||
|
if (drawable.getBounds().isEmpty()) {
|
||||||
|
drawable.setBounds(intrinsicBounds(drawable));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DrawableUtils() {
|
||||||
|
}
|
||||||
|
}
|
@ -10,8 +10,6 @@ import android.support.annotation.Nullable;
|
|||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
import ru.noties.markwon.utils.DrawableUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class can be used as the last {@link MediaDecoder} to _try_ to handle all rest cases.
|
* 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.
|
* Here we just assume that supplied InputStream is of image type and try to decode it.
|
||||||
@ -42,7 +40,7 @@ public class ImageMediaDecoder extends MediaDecoder {
|
|||||||
final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
|
final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
|
||||||
if (bitmap != null) {
|
if (bitmap != null) {
|
||||||
out = new BitmapDrawable(resources, bitmap);
|
out = new BitmapDrawable(resources, bitmap);
|
||||||
DrawableUtils.intrinsicBounds(out);
|
DrawableUtils.applyIntrinsicBounds(out);
|
||||||
} else {
|
} else {
|
||||||
out = null;
|
out = null;
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,16 @@ package ru.noties.markwon.utils;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Please use {@link ru.noties.markwon.image.DrawableUtils}
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
public abstract class DrawableUtils {
|
public abstract class DrawableUtils {
|
||||||
|
|
||||||
public static void intrinsicBounds(@NonNull Drawable drawable) {
|
public static void intrinsicBounds(@NonNull Drawable drawable) {
|
||||||
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
private DrawableUtils() {}
|
private DrawableUtils() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,13 +13,18 @@ import org.junit.runner.RunWith;
|
|||||||
import org.robolectric.RobolectricTestRunner;
|
import org.robolectric.RobolectricTestRunner;
|
||||||
import org.robolectric.annotation.Config;
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
|
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;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
@Config(manifest = Config.NONE)
|
@Config(manifest = Config.NONE)
|
||||||
@ -87,4 +92,58 @@ public class MarkwonSpansFactoryImplTest {
|
|||||||
assertNull(factory.get(type));
|
assertNull(factory.get(type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void composite_span_factory() {
|
||||||
|
// validate that composite span factory returns (calls) all span-factories
|
||||||
|
|
||||||
|
final SpanFactory first = mock(SpanFactory.class);
|
||||||
|
final SpanFactory second = mock(SpanFactory.class);
|
||||||
|
|
||||||
|
final MarkwonSpansFactoryImpl.CompositeSpanFactory factory =
|
||||||
|
new MarkwonSpansFactoryImpl.CompositeSpanFactory(first, second);
|
||||||
|
|
||||||
|
final Object spans = factory.getSpans(mock(MarkwonConfiguration.class), mock(RenderProps.class));
|
||||||
|
assertNotNull(spans);
|
||||||
|
assertTrue(spans.getClass().isArray());
|
||||||
|
assertEquals(2, ((Object[]) spans).length);
|
||||||
|
|
||||||
|
verify(first, times(1)).getSpans(any(MarkwonConfiguration.class), any(RenderProps.class));
|
||||||
|
verify(second, times(1)).getSpans(any(MarkwonConfiguration.class), any(RenderProps.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void builder_add_factory() {
|
||||||
|
// here is what we should validate:
|
||||||
|
// * if we call addFactory and there is none already -> supplied factory
|
||||||
|
// * if there is
|
||||||
|
// * * if not composite -> make composite
|
||||||
|
// * * if composite -> add to it
|
||||||
|
|
||||||
|
final MarkwonSpansFactoryImpl.BuilderImpl builder = new MarkwonSpansFactoryImpl.BuilderImpl();
|
||||||
|
|
||||||
|
final SpanFactory first = mock(SpanFactory.class);
|
||||||
|
final SpanFactory second = mock(SpanFactory.class);
|
||||||
|
final SpanFactory third = mock(SpanFactory.class);
|
||||||
|
|
||||||
|
final Class<Node> node = Node.class;
|
||||||
|
|
||||||
|
// assert none yet
|
||||||
|
assertNull(builder.getFactory(node));
|
||||||
|
|
||||||
|
// add first, none yet -> it should be added without modifications
|
||||||
|
builder.addFactory(node, first);
|
||||||
|
assertEquals(first, builder.getFactory(node));
|
||||||
|
|
||||||
|
// add second -> composite factory will be created
|
||||||
|
builder.addFactory(node, second);
|
||||||
|
final MarkwonSpansFactoryImpl.CompositeSpanFactory compositeSpanFactory =
|
||||||
|
(MarkwonSpansFactoryImpl.CompositeSpanFactory) builder.getFactory(node);
|
||||||
|
assertNotNull(compositeSpanFactory);
|
||||||
|
assertEquals(Arrays.asList(first, second), compositeSpanFactory.factories);
|
||||||
|
|
||||||
|
builder.addFactory(node, third);
|
||||||
|
assertEquals(compositeSpanFactory, builder.getFactory(node));
|
||||||
|
assertEquals(Arrays.asList(first, second, third), compositeSpanFactory.factories);
|
||||||
|
}
|
||||||
}
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package ru.noties.markwon;
|
||||||
|
|
||||||
|
import org.commonmark.node.BlockQuote;
|
||||||
|
import org.commonmark.node.Image;
|
||||||
|
import org.commonmark.node.Link;
|
||||||
|
import org.commonmark.node.ListItem;
|
||||||
|
import org.commonmark.node.Text;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
public class MarkwonSpansFactoryTest {
|
||||||
|
|
||||||
|
private MarkwonSpansFactoryImpl.BuilderImpl builder;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
builder = new MarkwonSpansFactoryImpl.BuilderImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void builder_set() {
|
||||||
|
final SpanFactory factory = mock(SpanFactory.class);
|
||||||
|
builder.setFactory(Text.class, factory);
|
||||||
|
builder.setFactory(Text.class, factory);
|
||||||
|
assertEquals(factory, builder.build().get(Text.class));
|
||||||
|
assertEquals(factory, builder.build().require(Text.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void builder_get_absent() {
|
||||||
|
// nothing is present
|
||||||
|
assertNull(builder.getFactory(Image.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void builder_get_present() {
|
||||||
|
final SpanFactory factory = mock(SpanFactory.class);
|
||||||
|
builder.setFactory(ListItem.class, factory);
|
||||||
|
assertEquals(factory, builder.getFactory(ListItem.class));
|
||||||
|
assertEquals(factory, builder.requireFactory(ListItem.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void builder_require_fail() {
|
||||||
|
try {
|
||||||
|
builder.requireFactory(Link.class);
|
||||||
|
fail();
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
assertTrue(e.getMessage(), e.getMessage().contains(Link.class.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void instance_require_fail() {
|
||||||
|
try {
|
||||||
|
builder.build().require(BlockQuote.class);
|
||||||
|
fail();
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
assertTrue(e.getMessage(), e.getMessage().contains(BlockQuote.class.getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -224,22 +224,30 @@ public class SpannableBuilderTest {
|
|||||||
|
|
||||||
assertTrue(builder.getSpans(0, builder.length()).isEmpty());
|
assertTrue(builder.getSpans(0, builder.length()).isEmpty());
|
||||||
|
|
||||||
|
final Object[] flatSpans = {
|
||||||
|
new Object(),
|
||||||
|
new Object(),
|
||||||
|
new Object(),
|
||||||
|
new Object(),
|
||||||
|
new Object()
|
||||||
|
};
|
||||||
|
|
||||||
final Object[] spans = {
|
final Object[] spans = {
|
||||||
new Object[]{
|
new Object[]{
|
||||||
new Object(), new Object()
|
flatSpans[0], flatSpans[1]
|
||||||
},
|
},
|
||||||
new Object[]{
|
new Object[]{
|
||||||
new Object(), new Object(), new Object()
|
flatSpans[2], flatSpans[3], flatSpans[4]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
setSpans(builder, spans, 0, 1);
|
setSpans(builder, spans, 0, 1);
|
||||||
|
|
||||||
final List<SpannableBuilder.Span> actual = builder.getSpans(0, builder.length());
|
final List<SpannableBuilder.Span> actual = builder.getSpans(0, builder.length());
|
||||||
assertEquals(2, actual.size());
|
assertEquals(flatSpans.length, actual.size());
|
||||||
|
|
||||||
for (int i = 0, length = spans.length; i < length; i++) {
|
for (int i = 0, length = spans.length; i < length; i++) {
|
||||||
assertEquals(spans[i], actual.get(i).what);
|
assertEquals(flatSpans[i], actual.get(i).what);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,12 +156,24 @@ public class CorePluginTest {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public <N extends Node> MarkwonSpansFactory.Builder addFactory(@NonNull Class<N> node, @NonNull SpanFactory factory) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public <N extends Node> SpanFactory getFactory(@NonNull Class<N> node) {
|
public <N extends Node> SpanFactory getFactory(@NonNull Class<N> node) {
|
||||||
throw new RuntimeException();
|
throw new RuntimeException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public <N extends Node> SpanFactory requireFactory(@NonNull Class<N> node) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public MarkwonSpansFactory build() {
|
public MarkwonSpansFactory build() {
|
||||||
|
@ -9,8 +9,8 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
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.MediaDecoder;
|
import ru.noties.markwon.image.MediaDecoder;
|
||||||
import ru.noties.markwon.utils.DrawableUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 1.1.0
|
* @since 1.1.0
|
||||||
@ -41,7 +41,7 @@ public class GifMediaDecoder extends MediaDecoder {
|
|||||||
if (bytes != null) {
|
if (bytes != null) {
|
||||||
try {
|
try {
|
||||||
out = newGifDrawable(bytes);
|
out = newGifDrawable(bytes);
|
||||||
DrawableUtils.intrinsicBounds(out);
|
DrawableUtils.applyIntrinsicBounds(out);
|
||||||
|
|
||||||
if (!autoPlayGif) {
|
if (!autoPlayGif) {
|
||||||
((GifDrawable) out).pause();
|
((GifDrawable) out).pause();
|
||||||
|
@ -13,8 +13,8 @@ import com.caverock.androidsvg.SVGParseException;
|
|||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import ru.noties.markwon.image.DrawableUtils;
|
||||||
import ru.noties.markwon.image.MediaDecoder;
|
import ru.noties.markwon.image.MediaDecoder;
|
||||||
import ru.noties.markwon.utils.DrawableUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 1.1.0
|
* @since 1.1.0
|
||||||
@ -65,7 +65,7 @@ public class SvgMediaDecoder extends MediaDecoder {
|
|||||||
svg.renderToCanvas(canvas);
|
svg.renderToCanvas(canvas);
|
||||||
|
|
||||||
out = new BitmapDrawable(resources, bitmap);
|
out = new BitmapDrawable(resources, bitmap);
|
||||||
DrawableUtils.intrinsicBounds(out);
|
DrawableUtils.applyIntrinsicBounds(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user