Skip to content

Commit

Permalink
Merge pull request #154 from noties/develop
Browse files Browse the repository at this point in the history
4.1.0
  • Loading branch information
noties authored Aug 6, 2019
2 parents 822f165 + a2d35a1 commit 3fe514a
Show file tree
Hide file tree
Showing 19 changed files with 457 additions and 13 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

# 4.1.0
* 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
* AsyncDrawableSpan -> defer invalidation

# 4.0.2
* Fix `JLatexMathPlugin` formula placeholder (cannot have line breaks) ([#149])
* Fix `JLatexMathPlugin` to update resulting formula bounds when `fitCanvas=true` and
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/io/noties/markwon/app/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
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;

Expand All @@ -15,6 +16,7 @@

import io.noties.debug.Debug;
import io.noties.markwon.Markwon;
import io.noties.markwon.utils.NoCopySpannableFactory;

public class MainActivity extends Activity {

Expand Down Expand Up @@ -60,6 +62,9 @@ public void onClick(View v) {

appBarRenderer.render(appBarState());

textView.setMovementMethod(LinkMovementMethod.getInstance());
textView.setSpannableFactory(NoCopySpannableFactory.getInstance());

markdownLoader.load(uri(), new MarkdownLoader.OnMarkdownTextLoaded() {
@Override
public void apply(final String text) {
Expand Down
5 changes: 4 additions & 1 deletion app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ ext {
deps = [
'x-annotations' : 'androidx.annotation:annotation:1.1.0',
'x-recycler-view' : 'androidx.recyclerview:recyclerview:1.0.0',
'x-core' : 'androidx.core:core:1.0.2',
'commonmark' : "com.atlassian.commonmark:commonmark:$commonMarkVersion",
'commonmark-strikethrough': "com.atlassian.commonmark:commonmark-ext-gfm-strikethrough:$commonMarkVersion",
'commonmark-table' : "com.atlassian.commonmark:commonmark-ext-gfm-tables:$commonMarkVersion",
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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

GROUP=io.noties.markwon
POM_DESCRIPTION=Markwon markdown for Android
Expand Down
4 changes: 4 additions & 0 deletions markwon-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ dependencies {
deps.with {
api it['x-annotations']
api it['commonmark']

// @since 4.1.0 to allow PrecomputedTextSetterCompat
// note that this dependency must be added on a client side explicitly
compileOnly it['x-core']
}

deps['test'].with {
Expand Down
45 changes: 45 additions & 0 deletions markwon-core/src/main/java/io/noties/markwon/Markwon.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

import org.commonmark.node.Node;

import java.util.List;

import io.noties.markwon.core.CorePlugin;

/**
Expand Down Expand Up @@ -119,6 +121,42 @@ public static Builder builderNoCore(@NonNull Context context) {
@Nullable
public abstract <P extends MarkwonPlugin> P getPlugin(@NonNull Class<P> type);

/**
* @since 4.1.0
*/
@NonNull
public abstract <P extends MarkwonPlugin> P requirePlugin(@NonNull Class<P> type);

/**
* @return a list of registered {@link MarkwonPlugin}
* @since 4.1.0
*/
@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
*
* @see PrecomputedTextSetterCompat
* @since 4.1.0
*/
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>
Expand All @@ -138,6 +176,13 @@ public interface Builder {
@NonNull
Builder bufferType(@NonNull TextView.BufferType bufferType);

/**
* @param textSetter {@link TextSetter} to apply text to a TextView
* @since 4.1.0
*/
@NonNull
Builder textSetter(@NonNull TextSetter textSetter);

@NonNull
Builder usePlugin(@NonNull MarkwonPlugin plugin);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -36,6 +38,13 @@ public Markwon.Builder bufferType(@NonNull TextView.BufferType bufferType) {
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) {
Expand Down Expand Up @@ -97,6 +106,7 @@ public Markwon build() {

return new MarkwonImpl(
bufferType,
textSetter,
parserBuilder.build(),
visitorBuilder.build(configuration, renderProps),
Collections.unmodifiableList(plugins)
Expand Down
50 changes: 45 additions & 5 deletions markwon-core/src/main/java/io/noties/markwon/MarkwonImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
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
Expand All @@ -21,12 +23,18 @@ class MarkwonImpl extends Markwon {
private final MarkwonVisitor visitor;
private final List<MarkwonPlugin> plugins;

// @since 4.1.0
@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;
Expand Down Expand Up @@ -78,16 +86,31 @@ public void setMarkdown(@NonNull TextView textView, @NonNull String markdown) {
}

@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);

for (MarkwonPlugin plugin : plugins) {
plugin.afterSetText(textView);
// @since 4.1.0
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);

for (MarkwonPlugin plugin : plugins) {
plugin.afterSetText(textView);
}
}
}

Expand All @@ -108,4 +131,21 @@ public <P extends MarkwonPlugin> P getPlugin(@NonNull Class<P> type) {
//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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package io.noties.markwon;

import android.os.Build;
import android.text.Spanned;
import android.util.Log;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.text.PrecomputedTextCompat;

import java.lang.ref.WeakReference;
import java.util.concurrent.Executor;

/**
* Please note this class requires `androidx.core:core` artifact being explicitly added to your dependencies.
* Please do not use with `markwon-recycler` as it will lead to bad item rendering (due to async nature)
*
* @see io.noties.markwon.Markwon.TextSetter
* @since 4.1.0
*/
public class PrecomputedTextSetterCompat implements Markwon.TextSetter {

/**
* @param executor for background execution of text pre-computation
*/
@NonNull
public static PrecomputedTextSetterCompat create(@NonNull Executor executor) {
return new PrecomputedTextSetterCompat(executor);
}

private final Executor executor;

@SuppressWarnings("WeakerAccess")
PrecomputedTextSetterCompat(@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) {

// insert version check and do not execute on a device < 21
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// it's still no-op, so there is no need to start background execution
applyText(textView, markdown, bufferType, onComplete);
return;
}

final WeakReference<TextView> reference = new WeakReference<>(textView);
executor.execute(new Runnable() {
@Override
public void run() {
try {
final PrecomputedTextCompat precomputedTextCompat = precomputedText(reference.get(), markdown);
if (precomputedTextCompat != null) {
applyText(reference.get(), precomputedTextCompat, bufferType, onComplete);
}
} catch (Throwable t) {
Log.e("PrecomputdTxtSetterCmpt", "Exception during pre-computing text", t);
// apply initial markdown
applyText(reference.get(), markdown, bufferType, onComplete);
}
}
});
}

@Nullable
private static PrecomputedTextCompat precomputedText(@Nullable TextView textView, @NonNull Spanned spanned) {

if (textView == null) {
return null;
}

final PrecomputedTextCompat.Params params;

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// use native parameters on P
params = new PrecomputedTextCompat.Params(textView.getTextMetricsParams());
} else {

final PrecomputedTextCompat.Params.Builder builder =
new PrecomputedTextCompat.Params.Builder(textView.getPaint());

// please note that text-direction initialization is omitted
// by default it will be determined by the first locale-specific character

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// another miss on API surface, this can easily be done by the compat class itself
builder
.setBreakStrategy(textView.getBreakStrategy())
.setHyphenationFrequency(textView.getHyphenationFrequency());
}

params = builder.build();
}

return PrecomputedTextCompat.create(spanned, params);
}

private static void applyText(
@Nullable final TextView textView,
@NonNull final Spanned text,
@NonNull final TextView.BufferType bufferType,
@NonNull final Runnable onComplete) {
if (textView != null) {
textView.post(new Runnable() {
@Override
public void run() {
textView.setText(text, bufferType);
onComplete.run();
}
});
}
}
}
Loading

0 comments on commit 3fe514a

Please sign in to comment.