Add TextSetter interface
This commit is contained in:
parent
822f16510e
commit
7e12552060
@ -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.0.2
|
VERSION_NAME=4.1.0-SNAPSHOT
|
||||||
|
|
||||||
GROUP=io.noties.markwon
|
GROUP=io.noties.markwon
|
||||||
POM_DESCRIPTION=Markwon markdown for Android
|
POM_DESCRIPTION=Markwon markdown for Android
|
||||||
|
@ -119,6 +119,29 @@ public abstract class Markwon {
|
|||||||
@Nullable
|
@Nullable
|
||||||
public abstract <P extends MarkwonPlugin> P getPlugin(@NonNull Class<P> type);
|
public abstract <P extends MarkwonPlugin> P getPlugin(@NonNull Class<P> 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}.
|
* Builder for {@link Markwon}.
|
||||||
* <p>
|
* <p>
|
||||||
@ -138,6 +161,13 @@ public abstract class Markwon {
|
|||||||
@NonNull
|
@NonNull
|
||||||
Builder bufferType(@NonNull TextView.BufferType bufferType);
|
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
|
@NonNull
|
||||||
Builder usePlugin(@NonNull MarkwonPlugin plugin);
|
Builder usePlugin(@NonNull MarkwonPlugin plugin);
|
||||||
|
|
||||||
|
@ -25,6 +25,8 @@ class MarkwonBuilderImpl implements Markwon.Builder {
|
|||||||
|
|
||||||
private TextView.BufferType bufferType = TextView.BufferType.SPANNABLE;
|
private TextView.BufferType bufferType = TextView.BufferType.SPANNABLE;
|
||||||
|
|
||||||
|
private Markwon.TextSetter textSetter;
|
||||||
|
|
||||||
MarkwonBuilderImpl(@NonNull Context context) {
|
MarkwonBuilderImpl(@NonNull Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
@ -36,6 +38,13 @@ class MarkwonBuilderImpl implements Markwon.Builder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Markwon.Builder textSetter(@NonNull Markwon.TextSetter textSetter) {
|
||||||
|
this.textSetter = textSetter;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Markwon.Builder usePlugin(@NonNull MarkwonPlugin plugin) {
|
public Markwon.Builder usePlugin(@NonNull MarkwonPlugin plugin) {
|
||||||
@ -97,6 +106,7 @@ class MarkwonBuilderImpl implements Markwon.Builder {
|
|||||||
|
|
||||||
return new MarkwonImpl(
|
return new MarkwonImpl(
|
||||||
bufferType,
|
bufferType,
|
||||||
|
textSetter,
|
||||||
parserBuilder.build(),
|
parserBuilder.build(),
|
||||||
visitorBuilder.build(configuration, renderProps),
|
visitorBuilder.build(configuration, renderProps),
|
||||||
Collections.unmodifiableList(plugins)
|
Collections.unmodifiableList(plugins)
|
||||||
|
@ -21,12 +21,18 @@ class MarkwonImpl extends Markwon {
|
|||||||
private final MarkwonVisitor visitor;
|
private final MarkwonVisitor visitor;
|
||||||
private final List<MarkwonPlugin> plugins;
|
private final List<MarkwonPlugin> plugins;
|
||||||
|
|
||||||
|
// @since 4.1.0-SNAPSHOT
|
||||||
|
@Nullable
|
||||||
|
private final TextSetter textSetter;
|
||||||
|
|
||||||
MarkwonImpl(
|
MarkwonImpl(
|
||||||
@NonNull TextView.BufferType bufferType,
|
@NonNull TextView.BufferType bufferType,
|
||||||
|
@Nullable TextSetter textSetter,
|
||||||
@NonNull Parser parser,
|
@NonNull Parser parser,
|
||||||
@NonNull MarkwonVisitor visitor,
|
@NonNull MarkwonVisitor visitor,
|
||||||
@NonNull List<MarkwonPlugin> plugins) {
|
@NonNull List<MarkwonPlugin> plugins) {
|
||||||
this.bufferType = bufferType;
|
this.bufferType = bufferType;
|
||||||
|
this.textSetter = textSetter;
|
||||||
this.parser = parser;
|
this.parser = parser;
|
||||||
this.visitor = visitor;
|
this.visitor = visitor;
|
||||||
this.plugins = plugins;
|
this.plugins = plugins;
|
||||||
@ -78,18 +84,33 @@ class MarkwonImpl extends Markwon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setParsedMarkdown(@NonNull TextView textView, @NonNull Spanned markdown) {
|
public void setParsedMarkdown(@NonNull final TextView textView, @NonNull Spanned markdown) {
|
||||||
|
|
||||||
for (MarkwonPlugin plugin : plugins) {
|
for (MarkwonPlugin plugin : plugins) {
|
||||||
plugin.beforeSetText(textView, markdown);
|
plugin.beforeSetText(textView, markdown);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @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 {
|
||||||
|
|
||||||
|
// if no text-setter is specified -> just a regular sync operation
|
||||||
textView.setText(markdown, bufferType);
|
textView.setText(markdown, bufferType);
|
||||||
|
|
||||||
for (MarkwonPlugin plugin : plugins) {
|
for (MarkwonPlugin plugin : plugins) {
|
||||||
plugin.afterSetText(textView);
|
plugin.afterSetText(textView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPlugin(@NonNull Class<? extends MarkwonPlugin> type) {
|
public boolean hasPlugin(@NonNull Class<? extends MarkwonPlugin> type) {
|
||||||
|
@ -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<TextView> 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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
@ -42,6 +43,7 @@ public class MarkwonImplTest {
|
|||||||
final MarkwonPlugin plugin = mock(MarkwonPlugin.class);
|
final MarkwonPlugin plugin = mock(MarkwonPlugin.class);
|
||||||
final MarkwonImpl impl = new MarkwonImpl(
|
final MarkwonImpl impl = new MarkwonImpl(
|
||||||
TextView.BufferType.SPANNABLE,
|
TextView.BufferType.SPANNABLE,
|
||||||
|
null,
|
||||||
mock(Parser.class),
|
mock(Parser.class),
|
||||||
mock(MarkwonVisitor.class),
|
mock(MarkwonVisitor.class),
|
||||||
Collections.singletonList(plugin));
|
Collections.singletonList(plugin));
|
||||||
@ -64,6 +66,7 @@ public class MarkwonImplTest {
|
|||||||
|
|
||||||
final MarkwonImpl impl = new MarkwonImpl(
|
final MarkwonImpl impl = new MarkwonImpl(
|
||||||
TextView.BufferType.SPANNABLE,
|
TextView.BufferType.SPANNABLE,
|
||||||
|
null,
|
||||||
parser,
|
parser,
|
||||||
mock(MarkwonVisitor.class),
|
mock(MarkwonVisitor.class),
|
||||||
Arrays.asList(first, second));
|
Arrays.asList(first, second));
|
||||||
@ -89,6 +92,7 @@ public class MarkwonImplTest {
|
|||||||
|
|
||||||
final MarkwonImpl impl = new MarkwonImpl(
|
final MarkwonImpl impl = new MarkwonImpl(
|
||||||
TextView.BufferType.SPANNABLE,
|
TextView.BufferType.SPANNABLE,
|
||||||
|
null,
|
||||||
mock(Parser.class),
|
mock(Parser.class),
|
||||||
visitor,
|
visitor,
|
||||||
Collections.singletonList(plugin));
|
Collections.singletonList(plugin));
|
||||||
@ -130,6 +134,7 @@ public class MarkwonImplTest {
|
|||||||
|
|
||||||
final MarkwonImpl impl = new MarkwonImpl(
|
final MarkwonImpl impl = new MarkwonImpl(
|
||||||
TextView.BufferType.SPANNABLE,
|
TextView.BufferType.SPANNABLE,
|
||||||
|
null,
|
||||||
mock(Parser.class),
|
mock(Parser.class),
|
||||||
visitor,
|
visitor,
|
||||||
Collections.<MarkwonPlugin>emptyList());
|
Collections.<MarkwonPlugin>emptyList());
|
||||||
@ -160,6 +165,7 @@ public class MarkwonImplTest {
|
|||||||
|
|
||||||
final MarkwonImpl impl = new MarkwonImpl(
|
final MarkwonImpl impl = new MarkwonImpl(
|
||||||
TextView.BufferType.SPANNABLE,
|
TextView.BufferType.SPANNABLE,
|
||||||
|
null,
|
||||||
mock(Parser.class),
|
mock(Parser.class),
|
||||||
visitor,
|
visitor,
|
||||||
Collections.singletonList(plugin));
|
Collections.singletonList(plugin));
|
||||||
@ -195,6 +201,7 @@ public class MarkwonImplTest {
|
|||||||
final MarkwonPlugin plugin = mock(MarkwonPlugin.class);
|
final MarkwonPlugin plugin = mock(MarkwonPlugin.class);
|
||||||
final MarkwonImpl impl = new MarkwonImpl(
|
final MarkwonImpl impl = new MarkwonImpl(
|
||||||
TextView.BufferType.EDITABLE,
|
TextView.BufferType.EDITABLE,
|
||||||
|
null,
|
||||||
mock(Parser.class),
|
mock(Parser.class),
|
||||||
mock(MarkwonVisitor.class, RETURNS_MOCKS),
|
mock(MarkwonVisitor.class, RETURNS_MOCKS),
|
||||||
Collections.singletonList(plugin));
|
Collections.singletonList(plugin));
|
||||||
@ -241,6 +248,7 @@ public class MarkwonImplTest {
|
|||||||
|
|
||||||
final MarkwonImpl impl = new MarkwonImpl(
|
final MarkwonImpl impl = new MarkwonImpl(
|
||||||
TextView.BufferType.SPANNABLE,
|
TextView.BufferType.SPANNABLE,
|
||||||
|
null,
|
||||||
mock(Parser.class),
|
mock(Parser.class),
|
||||||
mock(MarkwonVisitor.class),
|
mock(MarkwonVisitor.class),
|
||||||
plugins);
|
plugins);
|
||||||
@ -253,4 +261,43 @@ public class MarkwonImplTest {
|
|||||||
assertTrue("AbstractMarkwonPlugin", impl.hasPlugin(AbstractMarkwonPlugin.class));
|
assertTrue("AbstractMarkwonPlugin", impl.hasPlugin(AbstractMarkwonPlugin.class));
|
||||||
assertTrue("MarkwonPlugin", impl.hasPlugin(MarkwonPlugin.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<TextView> textViewArgumentCaptor =
|
||||||
|
ArgumentCaptor.forClass(TextView.class);
|
||||||
|
final ArgumentCaptor<Spanned> spannedArgumentCaptor =
|
||||||
|
ArgumentCaptor.forClass(Spanned.class);
|
||||||
|
final ArgumentCaptor<TextView.BufferType> bufferTypeArgumentCaptor =
|
||||||
|
ArgumentCaptor.forClass(TextView.BufferType.class);
|
||||||
|
final ArgumentCaptor<Runnable> 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());
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user