diff --git a/gradle.properties b/gradle.properties
index 5797d1d0..e9c33f08 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -8,7 +8,7 @@ android.enableJetifier=true
android.enableBuildCache=true
android.buildCacheDir=build/pre-dex-cache
-VERSION_NAME=4.0.2
+VERSION_NAME=4.1.0-SNAPSHOT
GROUP=io.noties.markwon
POM_DESCRIPTION=Markwon markdown for Android
diff --git a/markwon-core/src/main/java/io/noties/markwon/Markwon.java b/markwon-core/src/main/java/io/noties/markwon/Markwon.java
index ac702599..431790c5 100644
--- a/markwon-core/src/main/java/io/noties/markwon/Markwon.java
+++ b/markwon-core/src/main/java/io/noties/markwon/Markwon.java
@@ -119,6 +119,29 @@ public abstract class Markwon {
@Nullable
public abstract
P getPlugin(@NonNull Class
type);
+ /**
+ * Interface to set text on a TextView. Primary goal is to give a way to use PrecomputedText
+ * functionality
+ *
+ * @see PrecomputedTextSetter
+ * @since 4.1.0-SNAPSHOT
+ */
+ public interface TextSetter {
+ /**
+ * @param textView TextView
+ * @param markdown prepared markdown
+ * @param bufferType BufferType specified when building {@link Markwon} instance
+ * via {@link Builder#bufferType(TextView.BufferType)}
+ * @param onComplete action to run when set-text is finished (required to call in order
+ * to execute {@link MarkwonPlugin#afterSetText(TextView)})
+ */
+ void setText(
+ @NonNull TextView textView,
+ @NonNull Spanned markdown,
+ @NonNull TextView.BufferType bufferType,
+ @NonNull Runnable onComplete);
+ }
+
/**
* Builder for {@link Markwon}.
*
@@ -138,6 +161,13 @@ public abstract class Markwon {
@NonNull
Builder bufferType(@NonNull TextView.BufferType bufferType);
+ /**
+ * @param textSetter {@link TextSetter} to apply text to a TextView
+ * @since 4.1.0-SNAPSHOT
+ */
+ @NonNull
+ Builder textSetter(@NonNull TextSetter textSetter);
+
@NonNull
Builder usePlugin(@NonNull MarkwonPlugin plugin);
diff --git a/markwon-core/src/main/java/io/noties/markwon/MarkwonBuilderImpl.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonBuilderImpl.java
index 38ad7573..77de961f 100644
--- a/markwon-core/src/main/java/io/noties/markwon/MarkwonBuilderImpl.java
+++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonBuilderImpl.java
@@ -25,6 +25,8 @@ class MarkwonBuilderImpl implements Markwon.Builder {
private TextView.BufferType bufferType = TextView.BufferType.SPANNABLE;
+ private Markwon.TextSetter textSetter;
+
MarkwonBuilderImpl(@NonNull Context context) {
this.context = context;
}
@@ -36,6 +38,13 @@ class MarkwonBuilderImpl implements Markwon.Builder {
return this;
}
+ @NonNull
+ @Override
+ public Markwon.Builder textSetter(@NonNull Markwon.TextSetter textSetter) {
+ this.textSetter = textSetter;
+ return this;
+ }
+
@NonNull
@Override
public Markwon.Builder usePlugin(@NonNull MarkwonPlugin plugin) {
@@ -97,6 +106,7 @@ class MarkwonBuilderImpl implements Markwon.Builder {
return new MarkwonImpl(
bufferType,
+ textSetter,
parserBuilder.build(),
visitorBuilder.build(configuration, renderProps),
Collections.unmodifiableList(plugins)
diff --git a/markwon-core/src/main/java/io/noties/markwon/MarkwonImpl.java b/markwon-core/src/main/java/io/noties/markwon/MarkwonImpl.java
index 199beb0b..3a8d6d1f 100644
--- a/markwon-core/src/main/java/io/noties/markwon/MarkwonImpl.java
+++ b/markwon-core/src/main/java/io/noties/markwon/MarkwonImpl.java
@@ -21,12 +21,18 @@ class MarkwonImpl extends Markwon {
private final MarkwonVisitor visitor;
private final List plugins;
+ // @since 4.1.0-SNAPSHOT
+ @Nullable
+ private final TextSetter textSetter;
+
MarkwonImpl(
@NonNull TextView.BufferType bufferType,
+ @Nullable TextSetter textSetter,
@NonNull Parser parser,
@NonNull MarkwonVisitor visitor,
@NonNull List plugins) {
this.bufferType = bufferType;
+ this.textSetter = textSetter;
this.parser = parser;
this.visitor = visitor;
this.plugins = plugins;
@@ -78,16 +84,31 @@ class MarkwonImpl extends Markwon {
}
@Override
- public void setParsedMarkdown(@NonNull TextView textView, @NonNull Spanned markdown) {
+ public void setParsedMarkdown(@NonNull final TextView textView, @NonNull Spanned markdown) {
for (MarkwonPlugin plugin : plugins) {
plugin.beforeSetText(textView, markdown);
}
- textView.setText(markdown, bufferType);
+ // @since 4.1.0-SNAPSHOT
+ if (textSetter != null) {
+ textSetter.setText(textView, markdown, bufferType, new Runnable() {
+ @Override
+ public void run() {
+ // on-complete we just must call `afterSetText` on all plugins
+ for (MarkwonPlugin plugin : plugins) {
+ plugin.afterSetText(textView);
+ }
+ }
+ });
+ } else {
- for (MarkwonPlugin plugin : plugins) {
- plugin.afterSetText(textView);
+ // if no text-setter is specified -> just a regular sync operation
+ textView.setText(markdown, bufferType);
+
+ for (MarkwonPlugin plugin : plugins) {
+ plugin.afterSetText(textView);
+ }
}
}
diff --git a/markwon-core/src/main/java/io/noties/markwon/PrecomputedTextSetter.java b/markwon-core/src/main/java/io/noties/markwon/PrecomputedTextSetter.java
new file mode 100644
index 00000000..75452e21
--- /dev/null
+++ b/markwon-core/src/main/java/io/noties/markwon/PrecomputedTextSetter.java
@@ -0,0 +1,80 @@
+package io.noties.markwon;
+
+import android.os.AsyncTask;
+import android.os.Build;
+import android.text.PrecomputedText;
+import android.text.Spanned;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.Executor;
+
+/**
+ * @see io.noties.markwon.Markwon.TextSetter
+ * @since 4.1.0-SNAPSHOT
+ */
+@RequiresApi(Build.VERSION_CODES.P)
+public class PrecomputedTextSetter implements Markwon.TextSetter {
+
+ @NonNull
+ public static PrecomputedTextSetter create() {
+ return create(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ @NonNull
+ public static PrecomputedTextSetter create(@NonNull Executor executor) {
+ return new PrecomputedTextSetter(executor);
+ }
+
+ private final Executor executor;
+
+ @SuppressWarnings("WeakerAccess")
+ PrecomputedTextSetter(@NonNull Executor executor) {
+ this.executor = executor;
+ }
+
+ @Override
+ public void setText(
+ @NonNull TextView textView,
+ @NonNull final Spanned markdown,
+ @NonNull final TextView.BufferType bufferType,
+ @NonNull final Runnable onComplete) {
+ final WeakReference reference = new WeakReference<>(textView);
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ final PrecomputedText precomputedText = precomputedText(reference.get(), markdown);
+ if (precomputedText != null) {
+ apply(reference.get(), precomputedText, bufferType, onComplete);
+ }
+ }
+ });
+ }
+
+ @Nullable
+ private static PrecomputedText precomputedText(@Nullable TextView textView, @NonNull Spanned spanned) {
+ return textView == null
+ ? null
+ : PrecomputedText.create(spanned, textView.getTextMetricsParams());
+ }
+
+ private static void apply(
+ @Nullable final TextView textView,
+ @NonNull final PrecomputedText precomputedText,
+ @NonNull final TextView.BufferType bufferType,
+ @NonNull final Runnable onComplete) {
+ if (textView != null) {
+ textView.post(new Runnable() {
+ @Override
+ public void run() {
+ textView.setText(precomputedText, bufferType);
+ onComplete.run();
+ }
+ });
+ }
+ }
+}
diff --git a/markwon-core/src/test/java/io/noties/markwon/MarkwonImplTest.java b/markwon-core/src/test/java/io/noties/markwon/MarkwonImplTest.java
index 60f022ef..739470b7 100644
--- a/markwon-core/src/test/java/io/noties/markwon/MarkwonImplTest.java
+++ b/markwon-core/src/test/java/io/noties/markwon/MarkwonImplTest.java
@@ -21,6 +21,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
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.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
@@ -42,6 +43,7 @@ public class MarkwonImplTest {
final MarkwonPlugin plugin = mock(MarkwonPlugin.class);
final MarkwonImpl impl = new MarkwonImpl(
TextView.BufferType.SPANNABLE,
+ null,
mock(Parser.class),
mock(MarkwonVisitor.class),
Collections.singletonList(plugin));
@@ -64,6 +66,7 @@ public class MarkwonImplTest {
final MarkwonImpl impl = new MarkwonImpl(
TextView.BufferType.SPANNABLE,
+ null,
parser,
mock(MarkwonVisitor.class),
Arrays.asList(first, second));
@@ -89,6 +92,7 @@ public class MarkwonImplTest {
final MarkwonImpl impl = new MarkwonImpl(
TextView.BufferType.SPANNABLE,
+ null,
mock(Parser.class),
visitor,
Collections.singletonList(plugin));
@@ -130,6 +134,7 @@ public class MarkwonImplTest {
final MarkwonImpl impl = new MarkwonImpl(
TextView.BufferType.SPANNABLE,
+ null,
mock(Parser.class),
visitor,
Collections.emptyList());
@@ -160,6 +165,7 @@ public class MarkwonImplTest {
final MarkwonImpl impl = new MarkwonImpl(
TextView.BufferType.SPANNABLE,
+ null,
mock(Parser.class),
visitor,
Collections.singletonList(plugin));
@@ -195,6 +201,7 @@ public class MarkwonImplTest {
final MarkwonPlugin plugin = mock(MarkwonPlugin.class);
final MarkwonImpl impl = new MarkwonImpl(
TextView.BufferType.EDITABLE,
+ null,
mock(Parser.class),
mock(MarkwonVisitor.class, RETURNS_MOCKS),
Collections.singletonList(plugin));
@@ -241,6 +248,7 @@ public class MarkwonImplTest {
final MarkwonImpl impl = new MarkwonImpl(
TextView.BufferType.SPANNABLE,
+ null,
mock(Parser.class),
mock(MarkwonVisitor.class),
plugins);
@@ -253,4 +261,43 @@ public class MarkwonImplTest {
assertTrue("AbstractMarkwonPlugin", impl.hasPlugin(AbstractMarkwonPlugin.class));
assertTrue("MarkwonPlugin", impl.hasPlugin(MarkwonPlugin.class));
}
+
+ @Test
+ public void text_setter() {
+
+ final Markwon.TextSetter textSetter = mock(Markwon.TextSetter.class);
+ final MarkwonPlugin plugin = mock(MarkwonPlugin.class);
+
+ final MarkwonImpl impl = new MarkwonImpl(
+ TextView.BufferType.EDITABLE,
+ textSetter,
+ mock(Parser.class),
+ mock(MarkwonVisitor.class),
+ Collections.singletonList(plugin));
+
+ final TextView textView = mock(TextView.class);
+ final Spanned spanned = mock(Spanned.class);
+
+ impl.setParsedMarkdown(textView, spanned);
+
+ final ArgumentCaptor textViewArgumentCaptor =
+ ArgumentCaptor.forClass(TextView.class);
+ final ArgumentCaptor spannedArgumentCaptor =
+ ArgumentCaptor.forClass(Spanned.class);
+ final ArgumentCaptor bufferTypeArgumentCaptor =
+ ArgumentCaptor.forClass(TextView.BufferType.class);
+ final ArgumentCaptor runnableArgumentCaptor =
+ ArgumentCaptor.forClass(Runnable.class);
+
+ verify(textSetter, times(1)).setText(
+ textViewArgumentCaptor.capture(),
+ spannedArgumentCaptor.capture(),
+ bufferTypeArgumentCaptor.capture(),
+ runnableArgumentCaptor.capture());
+
+ assertEquals(textView, textViewArgumentCaptor.getValue());
+ assertEquals(spanned, spannedArgumentCaptor.getValue());
+ assertEquals(TextView.BufferType.EDITABLE, bufferTypeArgumentCaptor.getValue());
+ assertNotNull(runnableArgumentCaptor.getValue());
+ }
}
\ No newline at end of file