From 29ebfebfd89cd7d7f656585b1e89d42235ee146c Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Tue, 26 Mar 2019 13:16:48 +0300 Subject: [PATCH 01/15] Add AsyncDrawableLoader.Builder#implementation method --- gradle.properties | 2 +- .../markwon/image/AsyncDrawableLoader.java | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 99e26be7..0361db47 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ org.gradle.configureondemand=true android.enableBuildCache=true android.buildCacheDir=build/pre-dex-cache -VERSION_NAME=3.0.0 +VERSION_NAME=3.0.1-SNAPSHOT GROUP=ru.noties.markwon POM_DESCRIPTION=Markwon markdown for Android diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoader.java b/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoader.java index fe6506ee..5eadbe93 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoader.java +++ b/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoader.java @@ -53,6 +53,8 @@ public abstract class AsyncDrawableLoader { DrawableProvider placeholderDrawableProvider; DrawableProvider errorDrawableProvider; + AsyncDrawableLoader implementation; + @NonNull public Builder executorService(@NonNull ExecutorService executorService) { this.executorService = executorService; @@ -123,9 +125,29 @@ public abstract class AsyncDrawableLoader { 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-SNAPSHOT + */ + @NonNull + public Builder implementation(@NonNull AsyncDrawableLoader implementation) { + this.implementation = implementation; + return this; + } + @NonNull 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 // OR if we have no media decoders if (schemeHandlers.size() == 0 From a7163b8cf83c8ad21a47346168eebda2353dc4d8 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Tue, 26 Mar 2019 13:52:11 +0300 Subject: [PATCH 02/15] Add DrawableUtils --- .../noties/markwon/image/AsyncDrawable.java | 29 ++++++++++++++----- .../noties/markwon/image/DrawableUtils.java | 25 ++++++++++++++++ 2 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 markwon-core/src/main/java/ru/noties/markwon/image/DrawableUtils.java diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java b/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java index 696f6529..b9cdf235 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java +++ b/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java @@ -44,14 +44,9 @@ public class AsyncDrawable extends Drawable { if (placeholder != null) { // 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()); + if (placeholder.getBounds().isEmpty()) { + // set intrinsic bounds for both drawables (this one and placeholder) + final Rect rect = DrawableUtils.intrinsicBounds(placeholder); placeholder.setBounds(rect); setBounds(rect); } @@ -123,6 +118,24 @@ public class AsyncDrawable extends Drawable { initBounds(); } + /** + * Remove result from this drawable (for example, in case of cancellation) + * + * @since 3.0.1-SNAPSHOT + */ + 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() { if (canvasWidth == 0) { diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/DrawableUtils.java b/markwon-core/src/main/java/ru/noties/markwon/image/DrawableUtils.java new file mode 100644 index 00000000..9ccf11c4 --- /dev/null +++ b/markwon-core/src/main/java/ru/noties/markwon/image/DrawableUtils.java @@ -0,0 +1,25 @@ +package ru.noties.markwon.image; + +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; + +/** + * @since 3.0.1-SNAPSHOT + */ +public abstract class DrawableUtils { + + @NonNull + public static Rect intrinsicBounds(@NonNull Drawable drawable) { + return new Rect(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + } + + public static void applyIntrinsicBoundsIfEmpty(@NonNull Drawable drawable) { + if (drawable.getBounds().isEmpty()) { + drawable.setBounds(intrinsicBounds(drawable)); + } + } + + private DrawableUtils() { + } +} From f2e6eaad3607fef7e122558a6edd30fbd64fcbba Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Thu, 28 Mar 2019 12:54:10 +0300 Subject: [PATCH 03/15] Add requireFactory method to MarkwonSpansFactory --- .../java/ru/noties/markwon/MarkwonSpansFactory.java | 11 +++++++++++ .../ru/noties/markwon/MarkwonSpansFactoryImpl.java | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java b/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java index 8cf25a28..4a101cf2 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java +++ b/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java @@ -40,6 +40,17 @@ public interface MarkwonSpansFactory { @Nullable SpanFactory getFactory(@NonNull Class 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-SNAPSHOT + */ + @NonNull + SpanFactory requireFactory(@NonNull Class node); + @NonNull MarkwonSpansFactory build(); } diff --git a/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactoryImpl.java b/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactoryImpl.java index ef1906d8..3acce5c1 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactoryImpl.java +++ b/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactoryImpl.java @@ -58,6 +58,16 @@ class MarkwonSpansFactoryImpl implements MarkwonSpansFactory { return factories.get(node); } + @NonNull + @Override + public SpanFactory requireFactory(@NonNull Class node) { + final SpanFactory factory = getFactory(node); + if (factory == null) { + throw new NullPointerException(node.getName()); + } + return factory; + } + @NonNull @Override public MarkwonSpansFactory build() { From 947f57d0eb1c9e4d77051701a929a9d4e6accf0e Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Thu, 28 Mar 2019 13:09:18 +0300 Subject: [PATCH 04/15] Add tests for MarkwonSpansFactory --- .../markwon/MarkwonSpansFactoryTest.java | 68 +++++++++++++++++++ .../noties/markwon/core/CorePluginTest.java | 6 ++ 2 files changed, 74 insertions(+) create mode 100644 markwon-core/src/test/java/ru/noties/markwon/MarkwonSpansFactoryTest.java diff --git a/markwon-core/src/test/java/ru/noties/markwon/MarkwonSpansFactoryTest.java b/markwon-core/src/test/java/ru/noties/markwon/MarkwonSpansFactoryTest.java new file mode 100644 index 00000000..a2cb36ff --- /dev/null +++ b/markwon-core/src/test/java/ru/noties/markwon/MarkwonSpansFactoryTest.java @@ -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())); + } + } +} \ No newline at end of file diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/CorePluginTest.java b/markwon-core/src/test/java/ru/noties/markwon/core/CorePluginTest.java index 1cbf908b..f3edf945 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/core/CorePluginTest.java +++ b/markwon-core/src/test/java/ru/noties/markwon/core/CorePluginTest.java @@ -162,6 +162,12 @@ public class CorePluginTest { throw new RuntimeException(); } + @NonNull + @Override + public SpanFactory requireFactory(@NonNull Class node) { + throw new RuntimeException(); + } + @NonNull @Override public MarkwonSpansFactory build() { From ad284c756bf62b513dfc90251d2a8189536ad70d Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Thu, 28 Mar 2019 13:28:02 +0300 Subject: [PATCH 05/15] SpannableBuilder setSpans allow array of arrays --- .../ru/noties/markwon/SpannableBuilder.java | 30 ++++++++++++++----- .../noties/markwon/SpannableBuilderTest.java | 16 +++++++--- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/markwon-core/src/main/java/ru/noties/markwon/SpannableBuilder.java b/markwon-core/src/main/java/ru/noties/markwon/SpannableBuilder.java index 0b99d05c..f0a6aa93 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/SpannableBuilder.java +++ b/markwon-core/src/main/java/ru/noties/markwon/SpannableBuilder.java @@ -38,13 +38,9 @@ public class SpannableBuilder implements Appendable, CharSequence { return; } - if (spans.getClass().isArray()) { - for (Object o : ((Object[]) spans)) { - builder.setSpan(o, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } else { - builder.setSpan(spans, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } + // @since 3.0.1-SNAPSHOT we introduce another method that recursively applies spans + // allowing array of arrays (and more) + setSpansInternal(builder, spans, start, end); } } @@ -406,4 +402,24 @@ public class SpannableBuilder implements Appendable, CharSequence { super(text); } } + + /** + * @since 3.0.1-SNAPSHOT + */ + 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-SNAPSHOT recursively apply spans (allow array of arrays) + setSpansInternal(builder, o, start, end); + } + } else { + builder.setSpan(spans, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + } } diff --git a/markwon-core/src/test/java/ru/noties/markwon/SpannableBuilderTest.java b/markwon-core/src/test/java/ru/noties/markwon/SpannableBuilderTest.java index a1daf218..3535e19b 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/SpannableBuilderTest.java +++ b/markwon-core/src/test/java/ru/noties/markwon/SpannableBuilderTest.java @@ -224,22 +224,30 @@ public class SpannableBuilderTest { assertTrue(builder.getSpans(0, builder.length()).isEmpty()); + final Object[] flatSpans = { + new Object(), + new Object(), + new Object(), + new Object(), + new Object() + }; + final Object[] spans = { new Object[]{ - new Object(), new Object() + flatSpans[0], flatSpans[1] }, new Object[]{ - new Object(), new Object(), new Object() + flatSpans[2], flatSpans[3], flatSpans[4] } }; setSpans(builder, spans, 0, 1); final List 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++) { - assertEquals(spans[i], actual.get(i).what); + assertEquals(flatSpans[i], actual.get(i).what); } } From 5c0eead38d06744d54660d08bf46a9b6aed6b065 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Fri, 5 Apr 2019 14:50:57 +0300 Subject: [PATCH 06/15] Attempt at comments for documentation --- docs/.vuepress/components/Comments.vue | 31 ++++++++++++++++++++++++++ docs/docs/v3/core/getting-started.md | 2 ++ 2 files changed, 33 insertions(+) create mode 100644 docs/.vuepress/components/Comments.vue diff --git a/docs/.vuepress/components/Comments.vue b/docs/.vuepress/components/Comments.vue new file mode 100644 index 00000000..36b92eab --- /dev/null +++ b/docs/.vuepress/components/Comments.vue @@ -0,0 +1,31 @@ + + + diff --git a/docs/docs/v3/core/getting-started.md b/docs/docs/v3/core/getting-started.md index 652cfb7c..ec4ca023 100644 --- a/docs/docs/v3/core/getting-started.md +++ b/docs/docs/v3/core/getting-started.md @@ -63,3 +63,5 @@ This section is kept due to historical reasons. Starting with version \ No newline at end of file From f4e0ecc4fb689326079da76c13da7bcb22641bda Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Fri, 5 Apr 2019 14:57:57 +0300 Subject: [PATCH 07/15] Revert commenting (does now work) --- docs/.vuepress/components/Comments.vue | 31 -------------------------- docs/docs/v3/core/getting-started.md | 2 -- 2 files changed, 33 deletions(-) delete mode 100644 docs/.vuepress/components/Comments.vue diff --git a/docs/.vuepress/components/Comments.vue b/docs/.vuepress/components/Comments.vue deleted file mode 100644 index 36b92eab..00000000 --- a/docs/.vuepress/components/Comments.vue +++ /dev/null @@ -1,31 +0,0 @@ - - - diff --git a/docs/docs/v3/core/getting-started.md b/docs/docs/v3/core/getting-started.md index ec4ca023..652cfb7c 100644 --- a/docs/docs/v3/core/getting-started.md +++ b/docs/docs/v3/core/getting-started.md @@ -63,5 +63,3 @@ This section is kept due to historical reasons. Starting with version \ No newline at end of file From f0a03b7df60c6b0cf4c227875adc2c968e2d5408 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Mon, 15 Apr 2019 12:56:45 +0300 Subject: [PATCH 08/15] AsyncDrawable placeholder bounds fix --- .../src/main/java/ru/noties/markwon/image/AsyncDrawable.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java b/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java index b9cdf235..9d180673 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java +++ b/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java @@ -45,10 +45,8 @@ public class AsyncDrawable extends Drawable { // process placeholder bounds if (placeholder.getBounds().isEmpty()) { - // set intrinsic bounds for both drawables (this one and placeholder) final Rect rect = DrawableUtils.intrinsicBounds(placeholder); placeholder.setBounds(rect); - setBounds(rect); } // apply placeholder immediately if we have one From 1f3c50da038a5c283783eb8658682c3bb86f3a2a Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Tue, 16 Apr 2019 11:58:00 +0300 Subject: [PATCH 09/15] Fix DrawableUtils (intrinsic bounds) --- .../main/java/ru/noties/markwon/image/AsyncDrawable.java | 7 ++----- .../main/java/ru/noties/markwon/image/DrawableUtils.java | 6 ++++++ .../java/ru/noties/markwon/image/ImageMediaDecoder.java | 4 +--- .../main/java/ru/noties/markwon/utils/DrawableUtils.java | 7 ++++++- .../java/ru/noties/markwon/image/gif/GifMediaDecoder.java | 4 ++-- .../java/ru/noties/markwon/image/svg/SvgMediaDecoder.java | 4 ++-- 6 files changed, 19 insertions(+), 13 deletions(-) diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java b/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java index 9d180673..2ab11fa2 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java +++ b/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java @@ -43,11 +43,8 @@ public class AsyncDrawable extends Drawable { final Drawable placeholder = loader.placeholder(); if (placeholder != null) { - // process placeholder bounds - if (placeholder.getBounds().isEmpty()) { - final Rect rect = DrawableUtils.intrinsicBounds(placeholder); - placeholder.setBounds(rect); - } + // process placeholder bounds (if it's not empty -> ignore, use whatever bounds that were set) + DrawableUtils.applyIntrinsicBoundsIfEmpty(placeholder); // apply placeholder immediately if we have one setResult(placeholder); diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/DrawableUtils.java b/markwon-core/src/main/java/ru/noties/markwon/image/DrawableUtils.java index 9ccf11c4..97537186 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/image/DrawableUtils.java +++ b/markwon-core/src/main/java/ru/noties/markwon/image/DrawableUtils.java @@ -2,6 +2,7 @@ package ru.noties.markwon.image; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.support.annotation.CheckResult; import android.support.annotation.NonNull; /** @@ -10,10 +11,15 @@ import android.support.annotation.NonNull; 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)); diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/ImageMediaDecoder.java b/markwon-core/src/main/java/ru/noties/markwon/image/ImageMediaDecoder.java index 796d016e..c510c4d8 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/image/ImageMediaDecoder.java +++ b/markwon-core/src/main/java/ru/noties/markwon/image/ImageMediaDecoder.java @@ -10,8 +10,6 @@ import android.support.annotation.Nullable; 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. * 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); if (bitmap != null) { out = new BitmapDrawable(resources, bitmap); - DrawableUtils.intrinsicBounds(out); + DrawableUtils.applyIntrinsicBounds(out); } else { out = null; } diff --git a/markwon-core/src/main/java/ru/noties/markwon/utils/DrawableUtils.java b/markwon-core/src/main/java/ru/noties/markwon/utils/DrawableUtils.java index 34342093..0f719339 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/utils/DrawableUtils.java +++ b/markwon-core/src/main/java/ru/noties/markwon/utils/DrawableUtils.java @@ -3,11 +3,16 @@ package ru.noties.markwon.utils; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; +/** + * @deprecated Please use {@link ru.noties.markwon.image.DrawableUtils} + */ +@Deprecated public abstract class DrawableUtils { public static void intrinsicBounds(@NonNull Drawable drawable) { drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); } - private DrawableUtils() {} + private DrawableUtils() { + } } diff --git a/markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifMediaDecoder.java b/markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifMediaDecoder.java index 7a6600b8..6f5eda18 100644 --- a/markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifMediaDecoder.java +++ b/markwon-image-gif/src/main/java/ru/noties/markwon/image/gif/GifMediaDecoder.java @@ -9,8 +9,8 @@ import java.io.IOException; import java.io.InputStream; import pl.droidsonroids.gif.GifDrawable; +import ru.noties.markwon.image.DrawableUtils; import ru.noties.markwon.image.MediaDecoder; -import ru.noties.markwon.utils.DrawableUtils; /** * @since 1.1.0 @@ -41,7 +41,7 @@ public class GifMediaDecoder extends MediaDecoder { if (bytes != null) { try { out = newGifDrawable(bytes); - DrawableUtils.intrinsicBounds(out); + DrawableUtils.applyIntrinsicBounds(out); if (!autoPlayGif) { ((GifDrawable) out).pause(); diff --git a/markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgMediaDecoder.java b/markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgMediaDecoder.java index 5c8661d8..5ee71c61 100644 --- a/markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgMediaDecoder.java +++ b/markwon-image-svg/src/main/java/ru/noties/markwon/image/svg/SvgMediaDecoder.java @@ -13,8 +13,8 @@ import com.caverock.androidsvg.SVGParseException; import java.io.InputStream; +import ru.noties.markwon.image.DrawableUtils; import ru.noties.markwon.image.MediaDecoder; -import ru.noties.markwon.utils.DrawableUtils; /** * @since 1.1.0 @@ -65,7 +65,7 @@ public class SvgMediaDecoder extends MediaDecoder { svg.renderToCanvas(canvas); out = new BitmapDrawable(resources, bitmap); - DrawableUtils.intrinsicBounds(out); + DrawableUtils.applyIntrinsicBounds(out); } return out; From 740ff1013ca49eccdfc76c853e4c82198738efb1 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Tue, 16 Apr 2019 12:23:03 +0300 Subject: [PATCH 10/15] AsyncDrawable allow placeholder to have independent size --- .../noties/markwon/image/AsyncDrawable.java | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java b/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java index 2ab11fa2..2b9f5aef 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java +++ b/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java @@ -42,12 +42,7 @@ public class AsyncDrawable extends Drawable { final Drawable placeholder = loader.placeholder(); if (placeholder != null) { - - // process placeholder bounds (if it's not empty -> ignore, use whatever bounds that were set) - DrawableUtils.applyIntrinsicBoundsIfEmpty(placeholder); - - // apply placeholder immediately if we have one - setResult(placeholder); + setPlaceholderResult(placeholder); } } @@ -100,6 +95,38 @@ public class AsyncDrawable extends Drawable { } } + /** + * @since 3.0.1-SNAPSHOT + */ + 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) { // if we have previous one, detach it From 7f3f3368be6b6e34a23ffb8b37faa61a7c377f99 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Fri, 19 Apr 2019 13:39:20 +0300 Subject: [PATCH 11/15] addFactory method for MarkwonSpansFactory --- .../noties/markwon/MarkwonSpansFactory.java | 9 +++ .../markwon/MarkwonSpansFactoryImpl.java | 47 +++++++++++++++ .../markwon/MarkwonSpansFactoryImplTest.java | 59 +++++++++++++++++++ .../noties/markwon/core/CorePluginTest.java | 6 ++ 4 files changed, 121 insertions(+) diff --git a/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java b/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java index 4a101cf2..1e2e4abe 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java +++ b/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java @@ -34,6 +34,15 @@ public interface MarkwonSpansFactory { @NonNull Builder setFactory(@NonNull Class 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-SNAPSHOT + */ + @NonNull + Builder addFactory(@NonNull Class node, @NonNull SpanFactory factory); + /** * Can be useful when enhancing an already defined SpanFactory with another one. */ diff --git a/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactoryImpl.java b/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactoryImpl.java index 3acce5c1..85a4ab33 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactoryImpl.java +++ b/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactoryImpl.java @@ -5,8 +5,10 @@ import android.support.annotation.Nullable; import org.commonmark.node.Node; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -52,6 +54,27 @@ class MarkwonSpansFactoryImpl implements MarkwonSpansFactory { return this; } + @NonNull + @Override + public Builder addFactory(@NonNull Class 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 @Override public SpanFactory getFactory(@NonNull Class node) { @@ -74,4 +97,28 @@ class MarkwonSpansFactoryImpl implements MarkwonSpansFactory { return new MarkwonSpansFactoryImpl(Collections.unmodifiableMap(factories)); } } + + static class CompositeSpanFactory implements SpanFactory { + + final List 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; + } + } } diff --git a/markwon-core/src/test/java/ru/noties/markwon/MarkwonSpansFactoryImplTest.java b/markwon-core/src/test/java/ru/noties/markwon/MarkwonSpansFactoryImplTest.java index 6245df0e..b66cfd0c 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/MarkwonSpansFactoryImplTest.java +++ b/markwon-core/src/test/java/ru/noties/markwon/MarkwonSpansFactoryImplTest.java @@ -13,13 +13,18 @@ import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import java.util.Arrays; import java.util.Collections; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) @@ -87,4 +92,58 @@ public class MarkwonSpansFactoryImplTest { 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.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); + } } \ No newline at end of file diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/CorePluginTest.java b/markwon-core/src/test/java/ru/noties/markwon/core/CorePluginTest.java index f3edf945..dd1d43cc 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/core/CorePluginTest.java +++ b/markwon-core/src/test/java/ru/noties/markwon/core/CorePluginTest.java @@ -156,6 +156,12 @@ public class CorePluginTest { return this; } + @NonNull + @Override + public MarkwonSpansFactory.Builder addFactory(@NonNull Class node, @NonNull SpanFactory factory) { + throw new RuntimeException(); + } + @Nullable @Override public SpanFactory getFactory(@NonNull Class node) { From 83995b05f47bb7b3620c24bf23bcd95069435e57 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Fri, 19 Apr 2019 13:50:14 +0300 Subject: [PATCH 12/15] Add optional spans for list blocks --- .../java/ru/noties/markwon/core/SimpleBlockNodeVisitor.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/SimpleBlockNodeVisitor.java b/markwon-core/src/main/java/ru/noties/markwon/core/SimpleBlockNodeVisitor.java index 1cc2fa02..e42d11ec 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/core/SimpleBlockNodeVisitor.java +++ b/markwon-core/src/main/java/ru/noties/markwon/core/SimpleBlockNodeVisitor.java @@ -17,10 +17,16 @@ public class SimpleBlockNodeVisitor implements MarkwonVisitor.NodeVisitor @Override public void visit(@NonNull MarkwonVisitor visitor, @NonNull Node node) { + // @since 3.0.1-SNAPSHOT we keep track of start in order to apply spans (optionally) + final int length = visitor.length(); + visitor.ensureNewLine(); visitor.visitChildren(node); + // @since 3.0.1-SNAPSHOT we apply optional spans + visitor.setSpansForNodeOptional(node, length); + if (visitor.hasNext(node)) { visitor.ensureNewLine(); visitor.forceNewLine(); From 5168fd7b809f7e90c020f10880a865826e8c92c6 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Thu, 25 Apr 2019 14:07:17 +0300 Subject: [PATCH 13/15] Update changelog --- docs/CHANGELOG.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index fd3e24e2..ce8c7278 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,16 @@ # Changelog -# 3.0.0 +# 3.0.1 +* Add `AsyncDrawableLoader.Builder#implementation` method () +* AsyncDrawable allow placeholder to have independent size () +* `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 * Split basic functionality blocks into standalone modules * Maven artifacts group changed to `ru.noties.markwon` (previously had been `ru.noties`) From 974d7891b537a2aa3b869944ef0f9885751d9f4a Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Thu, 25 Apr 2019 14:08:12 +0300 Subject: [PATCH 14/15] Update to version 3.0.1 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 0361db47..cb90cb8f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ org.gradle.configureondemand=true android.enableBuildCache=true android.buildCacheDir=build/pre-dex-cache -VERSION_NAME=3.0.1-SNAPSHOT +VERSION_NAME=3.0.1 GROUP=ru.noties.markwon POM_DESCRIPTION=Markwon markdown for Android From ab1e72e33982713921c7497f35e5e7dac389a5e0 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Thu, 25 Apr 2019 14:10:11 +0300 Subject: [PATCH 15/15] Update since tag for comments and documentation --- .../main/java/ru/noties/markwon/MarkwonSpansFactory.java | 4 ++-- .../src/main/java/ru/noties/markwon/SpannableBuilder.java | 6 +++--- .../java/ru/noties/markwon/core/SimpleBlockNodeVisitor.java | 4 ++-- .../main/java/ru/noties/markwon/image/AsyncDrawable.java | 4 ++-- .../java/ru/noties/markwon/image/AsyncDrawableLoader.java | 2 +- .../main/java/ru/noties/markwon/image/DrawableUtils.java | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java b/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java index 1e2e4abe..2ef3f47f 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java +++ b/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java @@ -38,7 +38,7 @@ public interface MarkwonSpansFactory { * 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-SNAPSHOT + * @since 3.0.1 */ @NonNull Builder addFactory(@NonNull Class node, @NonNull SpanFactory factory); @@ -55,7 +55,7 @@ public interface MarkwonSpansFactory { * exception will be thrown * * @see #getFactory(Class) - * @since 3.0.1-SNAPSHOT + * @since 3.0.1 */ @NonNull SpanFactory requireFactory(@NonNull Class node); diff --git a/markwon-core/src/main/java/ru/noties/markwon/SpannableBuilder.java b/markwon-core/src/main/java/ru/noties/markwon/SpannableBuilder.java index f0a6aa93..3c10f779 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/SpannableBuilder.java +++ b/markwon-core/src/main/java/ru/noties/markwon/SpannableBuilder.java @@ -38,7 +38,7 @@ public class SpannableBuilder implements Appendable, CharSequence { return; } - // @since 3.0.1-SNAPSHOT we introduce another method that recursively applies spans + // @since 3.0.1 we introduce another method that recursively applies spans // allowing array of arrays (and more) setSpansInternal(builder, spans, start, end); } @@ -404,7 +404,7 @@ public class SpannableBuilder implements Appendable, CharSequence { } /** - * @since 3.0.1-SNAPSHOT + * @since 3.0.1 */ private static void setSpansInternal( @NonNull SpannableBuilder builder, @@ -414,7 +414,7 @@ public class SpannableBuilder implements Appendable, CharSequence { if (spans != null) { if (spans.getClass().isArray()) { for (Object o : ((Object[]) spans)) { - // @since 3.0.1-SNAPSHOT recursively apply spans (allow array of arrays) + // @since 3.0.1 recursively apply spans (allow array of arrays) setSpansInternal(builder, o, start, end); } } else { diff --git a/markwon-core/src/main/java/ru/noties/markwon/core/SimpleBlockNodeVisitor.java b/markwon-core/src/main/java/ru/noties/markwon/core/SimpleBlockNodeVisitor.java index e42d11ec..31f04081 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/core/SimpleBlockNodeVisitor.java +++ b/markwon-core/src/main/java/ru/noties/markwon/core/SimpleBlockNodeVisitor.java @@ -17,14 +17,14 @@ public class SimpleBlockNodeVisitor implements MarkwonVisitor.NodeVisitor @Override public void visit(@NonNull MarkwonVisitor visitor, @NonNull Node node) { - // @since 3.0.1-SNAPSHOT we keep track of start in order to apply spans (optionally) + // @since 3.0.1 we keep track of start in order to apply spans (optionally) final int length = visitor.length(); visitor.ensureNewLine(); visitor.visitChildren(node); - // @since 3.0.1-SNAPSHOT we apply optional spans + // @since 3.0.1 we apply optional spans visitor.setSpansForNodeOptional(node, length); if (visitor.hasNext(node)) { diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java b/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java index 2b9f5aef..25cf6363 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java +++ b/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawable.java @@ -96,7 +96,7 @@ public class AsyncDrawable extends Drawable { } /** - * @since 3.0.1-SNAPSHOT + * @since 3.0.1 */ protected void setPlaceholderResult(@NonNull Drawable placeholder) { // okay, if placeholder has bounds -> use it, otherwise use original imageSize @@ -143,7 +143,7 @@ public class AsyncDrawable extends Drawable { /** * Remove result from this drawable (for example, in case of cancellation) * - * @since 3.0.1-SNAPSHOT + * @since 3.0.1 */ public void clearResult() { diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoader.java b/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoader.java index 5eadbe93..8b76acc5 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoader.java +++ b/markwon-core/src/main/java/ru/noties/markwon/image/AsyncDrawableLoader.java @@ -131,7 +131,7 @@ public abstract class AsyncDrawableLoader { * will be ignored. * * @param implementation {@link AsyncDrawableLoader} implementation to be used. - * @since 3.0.1-SNAPSHOT + * @since 3.0.1 */ @NonNull public Builder implementation(@NonNull AsyncDrawableLoader implementation) { diff --git a/markwon-core/src/main/java/ru/noties/markwon/image/DrawableUtils.java b/markwon-core/src/main/java/ru/noties/markwon/image/DrawableUtils.java index 97537186..a5134d52 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/image/DrawableUtils.java +++ b/markwon-core/src/main/java/ru/noties/markwon/image/DrawableUtils.java @@ -6,7 +6,7 @@ import android.support.annotation.CheckResult; import android.support.annotation.NonNull; /** - * @since 3.0.1-SNAPSHOT + * @since 3.0.1 */ public abstract class DrawableUtils {