Add TextSetter interface
This commit is contained in:
parent
822f16510e
commit
7e12552060
@ -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
|
||||
|
@ -119,6 +119,29 @@ public abstract class Markwon {
|
||||
@Nullable
|
||||
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}.
|
||||
* <p>
|
||||
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
|
@ -21,12 +21,18 @@ class MarkwonImpl extends Markwon {
|
||||
private final MarkwonVisitor visitor;
|
||||
private final List<MarkwonPlugin> 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<MarkwonPlugin> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.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.<MarkwonPlugin>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<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