commit
						8d3f0e908d
					
				| @ -1,5 +1,14 @@ | |||||||
| # Changelog | # Changelog | ||||||
| 
 | 
 | ||||||
|  | # 4.2.2 | ||||||
|  | * Fixed `AsyncDrawable` display when it has placeholder with empty bounds ([#189]) | ||||||
|  | * Fixed `syntax-highlight` where code input is empty string ([#192]) | ||||||
|  | * Add `appendFactory`/`prependFactory` in `MarkwonSpansFactory.Builder` for more explicit `SpanFactory` ordering ([#193]) | ||||||
|  | 
 | ||||||
|  | [#189]: https://github.com/noties/Markwon/issues/189 | ||||||
|  | [#192]: https://github.com/noties/Markwon/issues/192 | ||||||
|  | [#193]: https://github.com/noties/Markwon/issues/193 | ||||||
|  | 
 | ||||||
| # 4.2.1 | # 4.2.1 | ||||||
| * Fix SpannableBuilder `subSequence` method | * Fix SpannableBuilder `subSequence` method | ||||||
| * Introduce Nougat check in `BulletListItemSpan` to position bullet (for bullets to be | * Introduce Nougat check in `BulletListItemSpan` to position bullet (for bullets to be | ||||||
|  | |||||||
| @ -62,7 +62,12 @@ builder.setFactory(Link.class, new SpanFactory() { | |||||||
| 
 | 
 | ||||||
| --- | --- | ||||||
| 
 | 
 | ||||||
| Since <Badge text="3.0.1" /> you can _add_ multiple `SpanFactory` for a single node: | :::warning | ||||||
|  | Deprecated in <Badge text="4.2.2" type="error" vertical="middle" />. Use `appendFactory` or `prependFactory` for | ||||||
|  | more explicit factories ordering. `addFactories` behaves like new `prependFactory` method. | ||||||
|  | ::: | ||||||
|  | 
 | ||||||
|  | Since <Badge text="3.0.1" /><Badge text="4.2.2" type="error" /> you can _add_ multiple `SpanFactory` for a single node: | ||||||
| 
 | 
 | ||||||
| ```java | ```java | ||||||
| final Markwon markwon = Markwon.builder(context) | final Markwon markwon = Markwon.builder(context) | ||||||
| @ -81,6 +86,22 @@ final Markwon markwon = Markwon.builder(context) | |||||||
|         .build(); |         .build(); | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | ## appendFactory/prependFactory <Badge text="4.2.2" /> | ||||||
|  | 
 | ||||||
|  | * use `appendFactory` if you wish to add a factory **after** original (can be used to post-process original factory) | ||||||
|  | * use `prependFactory` if you wish to add a factory **before** original (original factory will be applied after this one) | ||||||
|  | 
 | ||||||
|  | ```java | ||||||
|  | @Override | ||||||
|  | public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { | ||||||
|  |     // `RemoveUnderlineSpan` will be added AFTER original, thus it will remove underline applied by original | ||||||
|  |     builder.appendFactory(Link.class, (configuration, props) -> new RemoveUnderlineSpan()); | ||||||
|  | 
 | ||||||
|  |     // will be added BEFORE origin (origin can override this) | ||||||
|  |     builder.prependFactory(Link.class, (configuration, props) -> new AbsoluteSizeSpan(48, true)); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| --- | --- | ||||||
| 
 | 
 | ||||||
| If you wish to inspect existing factory you can use: | If you wish to inspect existing factory you can use: | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ android.enableJetifier=true | |||||||
| android.enableBuildCache=true | android.enableBuildCache=true | ||||||
| android.buildCacheDir=build/pre-dex-cache | android.buildCacheDir=build/pre-dex-cache | ||||||
| 
 | 
 | ||||||
| VERSION_NAME=4.2.1 | VERSION_NAME=4.2.2 | ||||||
| 
 | 
 | ||||||
| GROUP=io.noties.markwon | GROUP=io.noties.markwon | ||||||
| POM_DESCRIPTION=Markwon markdown for Android | POM_DESCRIPTION=Markwon markdown for Android | ||||||
|  | |||||||
| @ -39,10 +39,34 @@ public interface MarkwonSpansFactory { | |||||||
|          * {@link SpanFactory} with the specified one. |          * {@link SpanFactory} with the specified one. | ||||||
|          * |          * | ||||||
|          * @since 3.0.1 |          * @since 3.0.1 | ||||||
|  |          * @deprecated 4.2.2 consider using {@link #appendFactory(Class, SpanFactory)} or | ||||||
|  |          * {@link #prependFactory(Class, SpanFactory)} methods for more explicit factory ordering. | ||||||
|  |          * `addFactory` behaved like {@link #prependFactory(Class, SpanFactory)}, so | ||||||
|  |          * this method call can be replaced with it | ||||||
|          */ |          */ | ||||||
|         @NonNull |         @NonNull | ||||||
|  |         @Deprecated | ||||||
|         <N extends Node> Builder addFactory(@NonNull Class<N> node, @NonNull SpanFactory factory); |         <N extends Node> Builder addFactory(@NonNull Class<N> node, @NonNull SpanFactory factory); | ||||||
| 
 | 
 | ||||||
|  |         /** | ||||||
|  |          * Append a factory to existing one (or make the first one for specified node). Specified factory | ||||||
|  |          * will be called <strong>after</strong> original (if present) factory. Can be used to | ||||||
|  |          * <em>change</em> behavior or original span factory. | ||||||
|  |          * | ||||||
|  |          * @since 4.2.2 | ||||||
|  |          */ | ||||||
|  |         @NonNull | ||||||
|  |         <N extends Node> Builder appendFactory(@NonNull Class<N> node, @NonNull SpanFactory factory); | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Prepend a factory to existing one (or make the first one for specified node). Specified factory | ||||||
|  |          * will be called <string>before</string> original (if present) factory. | ||||||
|  |          * | ||||||
|  |          * @since 4.2.2 | ||||||
|  |          */ | ||||||
|  |         @NonNull | ||||||
|  |         <N extends Node> Builder prependFactory(@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. | ||||||
|          */ |          */ | ||||||
|  | |||||||
| @ -56,7 +56,32 @@ class MarkwonSpansFactoryImpl implements MarkwonSpansFactory { | |||||||
| 
 | 
 | ||||||
|         @NonNull |         @NonNull | ||||||
|         @Override |         @Override | ||||||
|  |         @Deprecated | ||||||
|         public <N extends Node> Builder addFactory(@NonNull Class<N> node, @NonNull SpanFactory factory) { |         public <N extends Node> Builder addFactory(@NonNull Class<N> node, @NonNull SpanFactory factory) { | ||||||
|  |             return prependFactory(node, factory); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @NonNull | ||||||
|  |         @Override | ||||||
|  |         public <N extends Node> Builder appendFactory(@NonNull Class<N> node, @NonNull SpanFactory factory) { | ||||||
|  |             final SpanFactory existing = factories.get(node); | ||||||
|  |             if (existing == null) { | ||||||
|  |                 factories.put(node, factory); | ||||||
|  |             } else { | ||||||
|  |                 if (existing instanceof CompositeSpanFactory) { | ||||||
|  |                     ((CompositeSpanFactory) existing).factories.add(0, factory); | ||||||
|  |                 } else { | ||||||
|  |                     final CompositeSpanFactory compositeSpanFactory = | ||||||
|  |                             new CompositeSpanFactory(factory, existing); | ||||||
|  |                     factories.put(node, compositeSpanFactory); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @NonNull | ||||||
|  |         @Override | ||||||
|  |         public <N extends Node> Builder prependFactory(@NonNull Class<N> node, @NonNull SpanFactory factory) { | ||||||
|             // if there is no factory registered for this node -> just add it |             // if there is no factory registered for this node -> just add it | ||||||
|             final SpanFactory existing = factories.get(node); |             final SpanFactory existing = factories.get(node); | ||||||
|             if (existing == null) { |             if (existing == null) { | ||||||
|  | |||||||
| @ -164,20 +164,44 @@ public class AsyncDrawable extends Drawable { | |||||||
|     @SuppressWarnings("WeakerAccess") |     @SuppressWarnings("WeakerAccess") | ||||||
|     protected void setPlaceholderResult(@NonNull Drawable placeholder) { |     protected void setPlaceholderResult(@NonNull Drawable placeholder) { | ||||||
|         // okay, if placeholder has bounds -> use it, otherwise use original imageSize |         // okay, if placeholder has bounds -> use it, otherwise use original imageSize | ||||||
|  |         // it's important to NOT pass to imageSizeResolver when placeholder has bounds | ||||||
|  |         // this is done, so actual result and placeholder can have _different_ | ||||||
|  |         // bounds. Assume image is loaded with HTML and has ImageSize width=100%, | ||||||
|  |         // so, even if placeholder has exact bounds, it will still be scaled up. | ||||||
|  | 
 | ||||||
|  |         // this condition should not be true for placeholder (at least for now) | ||||||
|  |         // (right now this method is always called from constructor) | ||||||
|  |         if (result != null) { | ||||||
|  |             // but it is, unregister current result | ||||||
|  |             result.setCallback(null); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         final Rect rect = placeholder.getBounds(); |         final Rect rect = placeholder.getBounds(); | ||||||
| 
 | 
 | ||||||
|         if (rect.isEmpty()) { |         if (rect.isEmpty()) { | ||||||
|             // if bounds are empty -> just use placeholder as a regular result |             // check for intrinsic bounds | ||||||
|             DrawableUtils.applyIntrinsicBounds(placeholder); |             final Rect intrinsic = DrawableUtils.intrinsicBounds(placeholder); | ||||||
|  |             if (intrinsic.isEmpty()) { | ||||||
|  |                 // @since 4.2.2 | ||||||
|  |                 // if intrinsic bounds are empty, use _any_ non-empty bounds, | ||||||
|  |                 // they must be non-empty so when result is obtained - proper invalidation will occur | ||||||
|  |                 // (0, 0, 1, 0) is still considered empty | ||||||
|  |                 placeholder.setBounds(0, 0, 1, 1); | ||||||
|  |             } else { | ||||||
|  |                 // use them | ||||||
|  |                 placeholder.setBounds(intrinsic); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // it is very important (if we have a placeholder) to set own bounds to it (and they must not be empty | ||||||
|  |             // otherwise result won't be rendered) | ||||||
|  |             // @since 4.2.2 | ||||||
|  |             setBounds(placeholder.getBounds()); | ||||||
|             setResult(placeholder); |             setResult(placeholder); | ||||||
|  | 
 | ||||||
|         } else { |         } else { | ||||||
| 
 | 
 | ||||||
|             // this condition should not be true for placeholder (at least for now) |             // this method is not the same as above, as we do not want to trigger image-size-resolver | ||||||
|             if (result != null) { |             // in case when placeholder has exact bounds | ||||||
|                 // but it is, unregister current result |  | ||||||
|                 result.setCallback(null); |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             // placeholder has bounds specified -> use them until we have real result |             // placeholder has bounds specified -> use them until we have real result | ||||||
|             this.result = placeholder; |             this.result = placeholder; | ||||||
|  | |||||||
| @ -113,6 +113,7 @@ public class MarkwonSpansFactoryImplTest { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Test |     @Test | ||||||
|  |     @Deprecated | ||||||
|     public void builder_add_factory() { |     public void builder_add_factory() { | ||||||
|         // here is what we should validate: |         // here is what we should validate: | ||||||
|         // * if we call addFactory and there is none already -> supplied factory |         // * if we call addFactory and there is none already -> supplied factory | ||||||
| @ -146,4 +147,74 @@ public class MarkwonSpansFactoryImplTest { | |||||||
|         assertEquals(compositeSpanFactory, builder.getFactory(node)); |         assertEquals(compositeSpanFactory, builder.getFactory(node)); | ||||||
|         assertEquals(Arrays.asList(first, second, third), compositeSpanFactory.factories); |         assertEquals(Arrays.asList(first, second, third), compositeSpanFactory.factories); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void builder_prepend_factory() { | ||||||
|  |         // here is what we should validate: | ||||||
|  |         // * if we call prependFactory 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.prependFactory(node, first); | ||||||
|  |         assertEquals(first, builder.getFactory(node)); | ||||||
|  | 
 | ||||||
|  |         // add second -> composite factory will be created | ||||||
|  |         builder.prependFactory(node, second); | ||||||
|  |         final MarkwonSpansFactoryImpl.CompositeSpanFactory compositeSpanFactory = | ||||||
|  |                 (MarkwonSpansFactoryImpl.CompositeSpanFactory) builder.getFactory(node); | ||||||
|  |         assertNotNull(compositeSpanFactory); | ||||||
|  |         assertEquals(Arrays.asList(first, second), compositeSpanFactory.factories); | ||||||
|  | 
 | ||||||
|  |         builder.prependFactory(node, third); | ||||||
|  |         assertEquals(compositeSpanFactory, builder.getFactory(node)); | ||||||
|  |         assertEquals(Arrays.asList(first, second, third), compositeSpanFactory.factories); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void builder_append_factory() { | ||||||
|  |         // here is what we should validate: | ||||||
|  |         // * if we call appendFactory 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.appendFactory(node, first); | ||||||
|  |         assertEquals(first, builder.getFactory(node)); | ||||||
|  | 
 | ||||||
|  |         // add second -> composite factory will be created | ||||||
|  |         builder.appendFactory(node, second); | ||||||
|  |         final MarkwonSpansFactoryImpl.CompositeSpanFactory compositeSpanFactory = | ||||||
|  |                 (MarkwonSpansFactoryImpl.CompositeSpanFactory) builder.getFactory(node); | ||||||
|  |         assertNotNull(compositeSpanFactory); | ||||||
|  |         assertEquals(Arrays.asList(second, first), compositeSpanFactory.factories); | ||||||
|  | 
 | ||||||
|  |         builder.appendFactory(node, third); | ||||||
|  |         assertEquals(compositeSpanFactory, builder.getFactory(node)); | ||||||
|  |         assertEquals(Arrays.asList(third, second, first), compositeSpanFactory.factories); | ||||||
|  |     } | ||||||
| } | } | ||||||
| @ -166,6 +166,18 @@ public class CorePluginTest { | |||||||
|                 throw new RuntimeException(); |                 throw new RuntimeException(); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |             @NonNull | ||||||
|  |             @Override | ||||||
|  |             public <N extends Node> MarkwonSpansFactory.Builder appendFactory(@NonNull Class<N> node, @NonNull SpanFactory factory) { | ||||||
|  |                 throw new RuntimeException(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             @NonNull | ||||||
|  |             @Override | ||||||
|  |             public <N extends Node> MarkwonSpansFactory.Builder prependFactory(@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) { | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ import android.graphics.Canvas; | |||||||
| import android.graphics.ColorFilter; | import android.graphics.ColorFilter; | ||||||
| import android.graphics.Rect; | import android.graphics.Rect; | ||||||
| import android.graphics.drawable.Drawable; | import android.graphics.drawable.Drawable; | ||||||
|  | 
 | ||||||
| import androidx.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import androidx.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| 
 | 
 | ||||||
| @ -18,7 +19,12 @@ import static org.junit.Assert.assertFalse; | |||||||
| 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.mockito.ArgumentMatchers.any; | ||||||
| import static org.mockito.Mockito.mock; | import static org.mockito.Mockito.mock; | ||||||
|  | import static org.mockito.Mockito.never; | ||||||
|  | import static org.mockito.Mockito.times; | ||||||
|  | import static org.mockito.Mockito.verify; | ||||||
|  | import static org.mockito.Mockito.when; | ||||||
| 
 | 
 | ||||||
| @RunWith(RobolectricTestRunner.class) | @RunWith(RobolectricTestRunner.class) | ||||||
| @Config(manifest = Config.NONE) | @Config(manifest = Config.NONE) | ||||||
| @ -78,6 +84,123 @@ public class AsyncDrawableTest { | |||||||
|         assertNotNull(result2.getCallback()); |         assertNotNull(result2.getCallback()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Test | ||||||
|  |     public void placeholder_no_bounds_no_intrinsic_bounds() { | ||||||
|  |         // when there is a placeholder and its | ||||||
|  |         // * bounds are empty | ||||||
|  |         // * intrinsic bounds are empty | ||||||
|  |         // AsyncDrawable.this must have any non-empty bounds (otherwise result won't be rendered, | ||||||
|  |         //  due to missing invalidation call) | ||||||
|  | 
 | ||||||
|  |         final Drawable placeholder = new AbstractDrawable() { | ||||||
|  |             @Override | ||||||
|  |             public int getIntrinsicWidth() { | ||||||
|  |                 return 0; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             @Override | ||||||
|  |             public int getIntrinsicHeight() { | ||||||
|  |                 return 0; | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         assertTrue(placeholder.getBounds().isEmpty()); | ||||||
|  | 
 | ||||||
|  |         final AsyncDrawableLoader loader = mock(AsyncDrawableLoader.class); | ||||||
|  |         when(loader.placeholder(any(AsyncDrawable.class))).thenReturn(placeholder); | ||||||
|  | 
 | ||||||
|  |         final AsyncDrawable drawable = new AsyncDrawable( | ||||||
|  |                 "", | ||||||
|  |                 loader, | ||||||
|  |                 mock(ImageSizeResolver.class), | ||||||
|  |                 null | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         final Rect bounds = drawable.getBounds(); | ||||||
|  |         assertFalse(bounds.toShortString(), bounds.isEmpty()); | ||||||
|  |         assertEquals(bounds.toShortString(), bounds, placeholder.getBounds()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void placeholder_no_bounds_has_intrinsic() { | ||||||
|  |         // placeholder has no bounds, but instead has intrinsic bounds | ||||||
|  | 
 | ||||||
|  |         final Drawable placeholder = new AbstractDrawable() { | ||||||
|  |             @Override | ||||||
|  |             public int getIntrinsicWidth() { | ||||||
|  |                 return 42; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             @Override | ||||||
|  |             public int getIntrinsicHeight() { | ||||||
|  |                 return 24; | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         assertTrue(placeholder.getBounds().isEmpty()); | ||||||
|  | 
 | ||||||
|  |         final AsyncDrawableLoader loader = mock(AsyncDrawableLoader.class); | ||||||
|  |         when(loader.placeholder(any(AsyncDrawable.class))).thenReturn(placeholder); | ||||||
|  | 
 | ||||||
|  |         final AsyncDrawable drawable = new AsyncDrawable( | ||||||
|  |                 "", | ||||||
|  |                 loader, | ||||||
|  |                 mock(ImageSizeResolver.class), | ||||||
|  |                 null | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         final Rect bounds = drawable.getBounds(); | ||||||
|  |         assertFalse(bounds.isEmpty()); | ||||||
|  |         assertEquals(0, bounds.left); | ||||||
|  |         assertEquals(42, bounds.right); | ||||||
|  |         assertEquals(0, bounds.top); | ||||||
|  |         assertEquals(24, bounds.bottom); | ||||||
|  | 
 | ||||||
|  |         assertEquals(bounds, placeholder.getBounds()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void placeholder_has_bounds() { | ||||||
|  | 
 | ||||||
|  |         final Rect rect = new Rect(0, 0, 12, 99); | ||||||
|  |         final Drawable placeholder = mock(Drawable.class); | ||||||
|  |         when(placeholder.getBounds()).thenReturn(rect); | ||||||
|  | 
 | ||||||
|  |         assertFalse(rect.isEmpty()); | ||||||
|  | 
 | ||||||
|  |         final AsyncDrawableLoader loader = mock(AsyncDrawableLoader.class); | ||||||
|  |         when(loader.placeholder(any(AsyncDrawable.class))).thenReturn(placeholder); | ||||||
|  | 
 | ||||||
|  |         final AsyncDrawable drawable = new AsyncDrawable( | ||||||
|  |                 "", | ||||||
|  |                 loader, | ||||||
|  |                 mock(ImageSizeResolver.class), | ||||||
|  |                 null | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         final Rect bounds = drawable.getBounds(); | ||||||
|  |         assertEquals(rect, bounds); | ||||||
|  | 
 | ||||||
|  |         verify(placeholder, times(1)).getBounds(); | ||||||
|  |         verify(placeholder, never()).getIntrinsicWidth(); | ||||||
|  |         verify(placeholder, never()).getIntrinsicHeight(); | ||||||
|  |         verify(placeholder, never()).setBounds(any(Rect.class)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     public void no_placeholder_empty_bounds() { | ||||||
|  |         // when AsyncDrawable has no placeholder, then its bounds must be empty at the start | ||||||
|  | 
 | ||||||
|  |         final AsyncDrawable drawable = new AsyncDrawable( | ||||||
|  |                 "", | ||||||
|  |                 mock(AsyncDrawableLoader.class), | ||||||
|  |                 mock(ImageSizeResolver.class), | ||||||
|  |                 null | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         assertTrue(drawable.getBounds().isEmpty()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private static class AbstractDrawable extends Drawable { |     private static class AbstractDrawable extends Drawable { | ||||||
| 
 | 
 | ||||||
|         @Override |         @Override | ||||||
|  | |||||||
| @ -41,6 +41,13 @@ public class Prism4jSyntaxHighlight implements SyntaxHighlight { | |||||||
|     @NonNull |     @NonNull | ||||||
|     @Override |     @Override | ||||||
|     public CharSequence highlight(@Nullable String info, @NonNull String code) { |     public CharSequence highlight(@Nullable String info, @NonNull String code) { | ||||||
|  | 
 | ||||||
|  |         // @since 4.2.2 | ||||||
|  |         // although not null, but still is empty | ||||||
|  |         if (code.isEmpty()) { | ||||||
|  |             return code; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         // if info is null, do not highlight -> LICENCE footer very commonly wrapped inside code |         // if info is null, do not highlight -> LICENCE footer very commonly wrapped inside code | ||||||
|         // block without syntax name specified (so, do not highlight) |         // block without syntax name specified (so, do not highlight) | ||||||
|         return info == null |         return info == null | ||||||
|  | |||||||
| @ -2,9 +2,13 @@ package io.noties.markwon.sample.recycler; | |||||||
| 
 | 
 | ||||||
| import android.app.Activity; | import android.app.Activity; | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
|  | import android.graphics.drawable.ColorDrawable; | ||||||
| import android.net.Uri; | import android.net.Uri; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
|  | import android.text.TextPaint; | ||||||
| import android.text.TextUtils; | import android.text.TextUtils; | ||||||
|  | import android.text.style.CharacterStyle; | ||||||
|  | import android.text.style.UpdateAppearance; | ||||||
| 
 | 
 | ||||||
| import androidx.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import androidx.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| @ -13,6 +17,7 @@ import androidx.recyclerview.widget.RecyclerView; | |||||||
| 
 | 
 | ||||||
| import org.commonmark.ext.gfm.tables.TableBlock; | import org.commonmark.ext.gfm.tables.TableBlock; | ||||||
| import org.commonmark.node.FencedCodeBlock; | import org.commonmark.node.FencedCodeBlock; | ||||||
|  | import org.commonmark.node.Link; | ||||||
| 
 | 
 | ||||||
| import java.io.BufferedReader; | import java.io.BufferedReader; | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| @ -24,10 +29,14 @@ import io.noties.debug.Debug; | |||||||
| import io.noties.markwon.AbstractMarkwonPlugin; | import io.noties.markwon.AbstractMarkwonPlugin; | ||||||
| import io.noties.markwon.Markwon; | import io.noties.markwon.Markwon; | ||||||
| import io.noties.markwon.MarkwonConfiguration; | import io.noties.markwon.MarkwonConfiguration; | ||||||
|  | import io.noties.markwon.MarkwonSpansFactory; | ||||||
| import io.noties.markwon.MarkwonVisitor; | import io.noties.markwon.MarkwonVisitor; | ||||||
| import io.noties.markwon.core.CorePlugin; | import io.noties.markwon.core.CorePlugin; | ||||||
| import io.noties.markwon.html.HtmlPlugin; | import io.noties.markwon.html.HtmlPlugin; | ||||||
| import io.noties.markwon.image.picasso.PicassoImagesPlugin; | import io.noties.markwon.image.ImagesPlugin; | ||||||
|  | import io.noties.markwon.image.file.FileSchemeHandler; | ||||||
|  | import io.noties.markwon.image.network.OkHttpNetworkSchemeHandler; | ||||||
|  | import io.noties.markwon.image.svg.SvgMediaDecoder; | ||||||
| import io.noties.markwon.recycler.MarkwonAdapter; | import io.noties.markwon.recycler.MarkwonAdapter; | ||||||
| import io.noties.markwon.recycler.SimpleEntry; | import io.noties.markwon.recycler.SimpleEntry; | ||||||
| import io.noties.markwon.recycler.table.TableEntry; | import io.noties.markwon.recycler.table.TableEntry; | ||||||
| @ -74,13 +83,14 @@ public class RecyclerActivity extends Activity { | |||||||
|     private static Markwon markwon(@NonNull Context context) { |     private static Markwon markwon(@NonNull Context context) { | ||||||
|         return Markwon.builder(context) |         return Markwon.builder(context) | ||||||
|                 .usePlugin(CorePlugin.create()) |                 .usePlugin(CorePlugin.create()) | ||||||
| //                .usePlugin(ImagesPlugin.create(plugin -> { |                 .usePlugin(ImagesPlugin.create(plugin -> { | ||||||
| //                    plugin |                     plugin | ||||||
| //                            .addSchemeHandler(FileSchemeHandler.createWithAssets(context)) |                             .addSchemeHandler(FileSchemeHandler.createWithAssets(context)) | ||||||
| //                            .addSchemeHandler(OkHttpNetworkSchemeHandler.create()) |                             .addSchemeHandler(OkHttpNetworkSchemeHandler.create()) | ||||||
| //                            .addMediaDecoder(SvgMediaDecoder.create()); |                             .addMediaDecoder(SvgMediaDecoder.create()) | ||||||
| //                })) |                             .placeholderProvider(drawable -> new ColorDrawable(0xFFff0000)); | ||||||
|                 .usePlugin(PicassoImagesPlugin.create(context)) |                 })) | ||||||
|  | //                .usePlugin(PicassoImagesPlugin.create(context)) | ||||||
| //                .usePlugin(GlideImagesPlugin.create(context)) | //                .usePlugin(GlideImagesPlugin.create(context)) | ||||||
| //                .usePlugin(CoilImagesPlugin.create(context)) | //                .usePlugin(CoilImagesPlugin.create(context)) | ||||||
|                 // important to use TableEntryPlugin instead of TablePlugin |                 // important to use TableEntryPlugin instead of TablePlugin | ||||||
| @ -106,10 +116,24 @@ public class RecyclerActivity extends Activity { | |||||||
|                             visitor.builder().append(code); |                             visitor.builder().append(code); | ||||||
|                         }); |                         }); | ||||||
|                     } |                     } | ||||||
|  | 
 | ||||||
|  |                     @Override | ||||||
|  |                     public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) { | ||||||
|  |                         // `RemoveUnderlineSpan` will be added AFTER original, thus it will remove underline applied by original | ||||||
|  |                         builder.appendFactory(Link.class, (configuration, props) -> new RemoveUnderlineSpan()); | ||||||
|  |                     } | ||||||
|                 }) |                 }) | ||||||
|                 .build(); |                 .build(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private static class RemoveUnderlineSpan extends CharacterStyle implements UpdateAppearance { | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public void updateDrawState(TextPaint tp) { | ||||||
|  |             tp.setUnderlineText(false); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @NonNull |     @NonNull | ||||||
|     private static String loadReadMe(@NonNull Context context) { |     private static String loadReadMe(@NonNull Context context) { | ||||||
|         InputStream stream = null; |         InputStream stream = null; | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Dimitry
						Dimitry