TablePlugin defer table-row invalidation
This commit is contained in:
		
							parent
							
								
									620da87694
								
							
						
					
					
						commit
						2a43797023
					
				| @ -4,6 +4,9 @@ | ||||
| * Add `Markwon.TextSetter` interface to be able to use PrecomputedText/PrecomputedTextCompat | ||||
| * Add `PrecomputedTextSetterCompat` and `compileOnly` dependency on `androidx.core:core`  | ||||
| (clients must have this dependency in the classpath) | ||||
| * Add `requirePlugin(Class)` and `getPlugins` for `Markwon` instance | ||||
| * TablePlugin -> defer table invalidation (via `View.post`), so only one invalidation  | ||||
| happens with each draw-call | ||||
| 
 | ||||
| # 4.0.2 | ||||
| * Fix `JLatexMathPlugin` formula placeholder (cannot have line breaks) ([#149]) | ||||
|  | ||||
| @ -5,6 +5,7 @@ import android.content.Intent; | ||||
| import android.net.Uri; | ||||
| import android.os.Bundle; | ||||
| import android.text.Spanned; | ||||
| import android.text.method.LinkMovementMethod; | ||||
| import android.view.View; | ||||
| import android.widget.TextView; | ||||
| 
 | ||||
| @ -15,6 +16,7 @@ import javax.inject.Inject; | ||||
| 
 | ||||
| import io.noties.debug.Debug; | ||||
| import io.noties.markwon.Markwon; | ||||
| import io.noties.markwon.utils.NoCopySpannableFactory; | ||||
| 
 | ||||
| public class MainActivity extends Activity { | ||||
| 
 | ||||
| @ -60,6 +62,9 @@ public class MainActivity extends Activity { | ||||
| 
 | ||||
|         appBarRenderer.render(appBarState()); | ||||
| 
 | ||||
|         textView.setMovementMethod(LinkMovementMethod.getInstance()); | ||||
|         textView.setSpannableFactory(NoCopySpannableFactory.getInstance()); | ||||
| 
 | ||||
|         markdownLoader.load(uri(), new MarkdownLoader.OnMarkdownTextLoaded() { | ||||
|             @Override | ||||
|             public void apply(final String text) { | ||||
|  | ||||
| @ -18,9 +18,12 @@ | ||||
|             android:id="@+id/text" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:breakStrategy="simple" | ||||
|             android:hyphenationFrequency="none" | ||||
|             android:lineSpacingExtra="2dip" | ||||
|             android:textSize="16sp" | ||||
|             tools:text="yo\nman" /> | ||||
|             tools:text="yo\nman" | ||||
|             tools:ignore="UnusedAttribute" /> | ||||
| 
 | ||||
|     </ScrollView> | ||||
| 
 | ||||
|  | ||||
| @ -9,6 +9,8 @@ import androidx.annotation.Nullable; | ||||
| 
 | ||||
| import org.commonmark.node.Node; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| import io.noties.markwon.core.CorePlugin; | ||||
| 
 | ||||
| /** | ||||
| @ -119,6 +121,19 @@ public abstract class Markwon { | ||||
|     @Nullable | ||||
|     public abstract <P extends MarkwonPlugin> P getPlugin(@NonNull Class<P> type); | ||||
| 
 | ||||
|     /** | ||||
|      * @since 4.1.0-SNAPSHOT | ||||
|      */ | ||||
|     @NonNull | ||||
|     public abstract <P extends MarkwonPlugin> P requirePlugin(@NonNull Class<P> type); | ||||
| 
 | ||||
|     /** | ||||
|      * @return a list of registered {@link MarkwonPlugin} | ||||
|      * @since 4.1.0-SNAPSHOT | ||||
|      */ | ||||
|     @NonNull | ||||
|     public abstract List<? extends MarkwonPlugin> getPlugins(); | ||||
| 
 | ||||
|     /** | ||||
|      * Interface to set text on a TextView. Primary goal is to give a way to use PrecomputedText | ||||
|      * functionality | ||||
|  | ||||
| @ -9,7 +9,9 @@ import androidx.annotation.Nullable; | ||||
| import org.commonmark.node.Node; | ||||
| import org.commonmark.parser.Parser; | ||||
| 
 | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.Locale; | ||||
| 
 | ||||
| /** | ||||
|  * @since 3.0.0 | ||||
| @ -129,4 +131,21 @@ class MarkwonImpl extends Markwon { | ||||
|         //noinspection unchecked | ||||
|         return (P) out; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public <P extends MarkwonPlugin> P requirePlugin(@NonNull Class<P> type) { | ||||
|         final P plugin = getPlugin(type); | ||||
|         if (plugin == null) { | ||||
|             throw new IllegalStateException(String.format(Locale.US, "Requested plugin `%s` is not " + | ||||
|                     "registered with this Markwon instance", type.getName())); | ||||
|         } | ||||
|         return plugin; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public List<? extends MarkwonPlugin> getPlugins() { | ||||
|         return Collections.unmodifiableList(plugins); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -14,6 +14,7 @@ import org.mockito.stubbing.Answer; | ||||
| import org.robolectric.RobolectricTestRunner; | ||||
| import org.robolectric.annotation.Config; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| @ -23,6 +24,7 @@ import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertFalse; | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| import static org.junit.Assert.fail; | ||||
| import static org.mockito.ArgumentMatchers.any; | ||||
| import static org.mockito.ArgumentMatchers.anyString; | ||||
| import static org.mockito.ArgumentMatchers.eq; | ||||
| @ -300,4 +302,64 @@ public class MarkwonImplTest { | ||||
|         assertEquals(TextView.BufferType.EDITABLE, bufferTypeArgumentCaptor.getValue()); | ||||
|         assertNotNull(runnableArgumentCaptor.getValue()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void require_plugin_throws() { | ||||
|         // if plugin is `required`, but it's not added -> an exception is thrown | ||||
| 
 | ||||
|         final class NotPresent extends AbstractMarkwonPlugin { | ||||
|         } | ||||
| 
 | ||||
|         final List<MarkwonPlugin> plugins = | ||||
|                 Arrays.asList(mock(MarkwonPlugin.class), mock(MarkwonPlugin.class)); | ||||
| 
 | ||||
|         final MarkwonImpl impl = new MarkwonImpl( | ||||
|                 TextView.BufferType.SPANNABLE, | ||||
|                 null, | ||||
|                 mock(Parser.class), | ||||
|                 mock(MarkwonVisitor.class), plugins); | ||||
| 
 | ||||
|         // should be returned | ||||
|         assertNotNull(impl.requirePlugin(MarkwonPlugin.class)); | ||||
| 
 | ||||
|         try { | ||||
|             impl.requirePlugin(NotPresent.class); | ||||
|             fail(); | ||||
|         } catch (Throwable t) { | ||||
|             assertTrue(t.getMessage(), t.getMessage().contains(NotPresent.class.getName())); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void plugins_unmodifiable() { | ||||
|         // returned plugins list must not be modifiable | ||||
| 
 | ||||
|         // modifiable list (created from Arrays.asList -> which returns non) | ||||
|         final List<MarkwonPlugin> plugins = new ArrayList<>( | ||||
|                 Arrays.asList(mock(MarkwonPlugin.class), mock(MarkwonPlugin.class))); | ||||
| 
 | ||||
|         // validate that list is modifiable | ||||
|         plugins.add(mock(MarkwonPlugin.class)); | ||||
|         assertEquals(3, plugins.size()); | ||||
| 
 | ||||
|         final MarkwonImpl impl = new MarkwonImpl( | ||||
|                 TextView.BufferType.SPANNABLE, | ||||
|                 null, | ||||
|                 mock(Parser.class), | ||||
|                 mock(MarkwonVisitor.class), | ||||
|                 plugins); | ||||
| 
 | ||||
|         final List<? extends MarkwonPlugin> list = impl.getPlugins(); | ||||
| 
 | ||||
|         // instance check (different list) | ||||
|         //noinspection SimplifiableJUnitAssertion | ||||
|         assertTrue(plugins != list); | ||||
| 
 | ||||
|         try { | ||||
|             list.add(null); | ||||
|             fail(); | ||||
|         } catch (UnsupportedOperationException e) { | ||||
|             assertTrue(e.getMessage(), true); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -34,9 +34,22 @@ abstract class TableRowsScheduler { | ||||
|             } | ||||
| 
 | ||||
|             final TableRowSpan.Invalidator invalidator = new TableRowSpan.Invalidator() { | ||||
| 
 | ||||
|                 // @since 4.1.0-SNAPSHOT | ||||
|                 // let's stack-up invalidation calls (so invalidation happens, | ||||
|                 // but not with each table-row-span draw call) | ||||
|                 final Runnable runnable = new Runnable() { | ||||
|                     @Override | ||||
|                     public void run() { | ||||
|                         view.setText(view.getText()); | ||||
|                     } | ||||
|                 }; | ||||
| 
 | ||||
|                 @Override | ||||
|                 public void invalidate() { | ||||
|                     view.setText(view.getText()); | ||||
|                     // @since 4.1.0-SNAPSHOT post invalidation (combine multiple calls) | ||||
|                     view.removeCallbacks(runnable); | ||||
|                     view.post(runnable); | ||||
|                 } | ||||
|             }; | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Dimitry Ivanov
						Dimitry Ivanov