Shaping up the custom extension module
@ -6,8 +6,11 @@ android {
|
|||||||
buildToolsVersion BUILD_TOOLS
|
buildToolsVersion BUILD_TOOLS
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "noties.ru.markwon_samplecustomextension"
|
|
||||||
minSdkVersion MIN_SDK
|
applicationId "ru.noties.markwon.sample.extension"
|
||||||
|
|
||||||
|
// using 21 as minimum only to be able to vector assets
|
||||||
|
minSdkVersion 21
|
||||||
targetSdkVersion TARGET_SDK
|
targetSdkVersion TARGET_SDK
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName version
|
versionName version
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="noties.ru.markwon_samplecustomextension">
|
package="ru.noties.markwon.sample.extension">
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
@ -9,10 +9,10 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
|
|
||||||
<activity android:name=".MainActivity">
|
<activity android:name=".MainActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
@ -1,86 +0,0 @@
|
|||||||
package noties.ru.markwon_samplecustomextension;
|
|
||||||
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import org.commonmark.node.Node;
|
|
||||||
import org.commonmark.node.Text;
|
|
||||||
import org.commonmark.parser.delimiter.DelimiterProcessor;
|
|
||||||
import org.commonmark.parser.delimiter.DelimiterRun;
|
|
||||||
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public class IconProcessor implements DelimiterProcessor {
|
|
||||||
|
|
||||||
private static final Pattern PATTERN = Pattern.compile("material-icon-(\\w+)-(\\w+)-(\\w+)");
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public char getOpeningCharacter() {
|
|
||||||
return IconNode.DELIMITER;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public char getClosingCharacter() {
|
|
||||||
return IconNode.DELIMITER;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMinLength() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) {
|
|
||||||
return opener.length() >= 1 && closer.length() >= 1 ? 1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void process(Text opener, Text closer, int delimiterUse) {
|
|
||||||
|
|
||||||
final IconGroupNode iconGroupNode = new IconGroupNode();
|
|
||||||
|
|
||||||
final Node next = opener.getNext();
|
|
||||||
|
|
||||||
boolean handled = false;
|
|
||||||
|
|
||||||
// process only if we have exactly one Text node
|
|
||||||
if (next instanceof Text && next.getNext() == closer) {
|
|
||||||
|
|
||||||
final String text = ((Text) next).getLiteral();
|
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(text)) {
|
|
||||||
|
|
||||||
// attempt to match
|
|
||||||
final Matcher matcher = PATTERN.matcher(text);
|
|
||||||
if (matcher.matches()) {
|
|
||||||
final IconNode iconNode = new IconNode(
|
|
||||||
matcher.group(1),
|
|
||||||
matcher.group(2),
|
|
||||||
matcher.group(3)
|
|
||||||
);
|
|
||||||
iconGroupNode.appendChild(iconNode);
|
|
||||||
next.unlink();
|
|
||||||
handled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!handled) {
|
|
||||||
|
|
||||||
// restore delimiters if we didn't match
|
|
||||||
|
|
||||||
iconGroupNode.appendChild(new Text(IconNode.DELIMITER_STRING));
|
|
||||||
|
|
||||||
Node node;
|
|
||||||
for (Node tmp = opener.getNext(); tmp != null && tmp != closer; tmp = node) {
|
|
||||||
node = tmp.getNext();
|
|
||||||
// append a child anyway
|
|
||||||
iconGroupNode.appendChild(tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
iconGroupNode.appendChild(new Text(IconNode.DELIMITER_STRING));
|
|
||||||
}
|
|
||||||
|
|
||||||
opener.insertBefore(iconGroupNode);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package noties.ru.markwon_samplecustomextension;
|
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
public interface IconSpanProvider {
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
IconSpan provide(@NonNull String name, @NonNull String color, @NonNull String size);
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
package noties.ru.markwon_samplecustomextension;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.support.annotation.DrawableRes;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
public class IconSpanProviderImpl implements IconSpanProvider {
|
|
||||||
|
|
||||||
private static final boolean IS_L = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
|
|
||||||
|
|
||||||
private final Context context;
|
|
||||||
private final Resources resources;
|
|
||||||
private final int fallBack;
|
|
||||||
|
|
||||||
public IconSpanProviderImpl(@NonNull Context context, @DrawableRes int fallBack) {
|
|
||||||
this.context = context;
|
|
||||||
this.resources = context.getResources();
|
|
||||||
this.fallBack = fallBack;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public IconSpan provide(@NonNull String name, @NonNull String color, @NonNull String size) {
|
|
||||||
final String resName = materialIconName(name, color, size);
|
|
||||||
int resId = resources.getIdentifier(resName, "drawable", context.getPackageName());
|
|
||||||
if (resId == 0) {
|
|
||||||
resId = fallBack;
|
|
||||||
}
|
|
||||||
return new IconSpan(getDrawable(resId), IconSpan.ALIGN_CENTER);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private static String materialIconName(@NonNull String name, @NonNull String color, @NonNull String size) {
|
|
||||||
return "ic_" + name + "_" + color + "_" + size + "dp";
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private Drawable getDrawable(int resId) {
|
|
||||||
final Drawable drawable;
|
|
||||||
if (IS_L) {
|
|
||||||
drawable = context.getDrawable(resId);
|
|
||||||
} else {
|
|
||||||
drawable = resources.getDrawable(resId);
|
|
||||||
}
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
return drawable;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
package noties.ru.markwon_samplecustomextension;
|
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
public abstract class IconUtils {
|
|
||||||
|
|
||||||
private static final String TO_FIND = "@material-icon-";
|
|
||||||
|
|
||||||
public static void prepare(@NonNull StringBuilder builder) {
|
|
||||||
|
|
||||||
int start = builder.indexOf(TO_FIND);
|
|
||||||
int end;
|
|
||||||
|
|
||||||
while (start > -1) {
|
|
||||||
end = iconDefinitionEnd(start + TO_FIND.length(), builder);
|
|
||||||
builder.insert(end, '@');
|
|
||||||
start = builder.indexOf(TO_FIND, end);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int iconDefinitionEnd(int index, @NonNull StringBuilder builder) {
|
|
||||||
|
|
||||||
// all spaces, new lines, non-words or digits,
|
|
||||||
|
|
||||||
char c;
|
|
||||||
|
|
||||||
int end = -1;
|
|
||||||
for (int i = index; i < builder.length(); i++) {
|
|
||||||
c = builder.charAt(i);
|
|
||||||
if (Character.isWhitespace(c)
|
|
||||||
|| !(Character.isLetterOrDigit(c) || c == '-' || c == '_')) {
|
|
||||||
end = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (end == -1) {
|
|
||||||
end = builder.length();
|
|
||||||
}
|
|
||||||
|
|
||||||
return end;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IconUtils() {
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
package noties.ru.markwon_samplecustomextension;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension;
|
|
||||||
import org.commonmark.ext.gfm.tables.TablesExtension;
|
|
||||||
import org.commonmark.node.Node;
|
|
||||||
import org.commonmark.parser.Parser;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import ru.noties.markwon.SpannableBuilder;
|
|
||||||
import ru.noties.markwon.SpannableConfiguration;
|
|
||||||
import ru.noties.markwon.tasklist.TaskListExtension;
|
|
||||||
|
|
||||||
public class MainActivity extends Activity {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(0);
|
|
||||||
|
|
||||||
final TextView textView = findViewById(0);
|
|
||||||
|
|
||||||
final Parser parser = new Parser.Builder()
|
|
||||||
// we will register all known to Markwon extensions
|
|
||||||
.extensions(Arrays.asList(
|
|
||||||
StrikethroughExtension.create(),
|
|
||||||
TablesExtension.create(),
|
|
||||||
TaskListExtension.create()
|
|
||||||
))
|
|
||||||
// this is the handler for custom icons
|
|
||||||
.customDelimiterProcessor(new IconProcessor())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
final Node node = parser.parse("# Hello icons! @material-icon-home-black-24@\n\n Your account @material-icon-account_balance-white-26@ is 0.00003");
|
|
||||||
final SpannableBuilder builder = new SpannableBuilder();
|
|
||||||
final IconVisitor visitor = new IconVisitor(SpannableConfiguration.create(this), builder, new IconSpanProviderImpl(this, R.drawable.ic_home_black_24dp));
|
|
||||||
node.accept(visitor);
|
|
||||||
textView.setText(builder.text());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,8 @@
|
|||||||
package noties.ru.markwon_samplecustomextension;
|
package ru.noties.markwon.sample.extension;
|
||||||
|
|
||||||
import org.commonmark.node.CustomNode;
|
import org.commonmark.node.CustomNode;
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
public class IconGroupNode extends CustomNode {
|
public class IconGroupNode extends CustomNode {
|
||||||
|
|
||||||
}
|
}
|
@ -1,13 +1,13 @@
|
|||||||
package noties.ru.markwon_samplecustomextension;
|
package ru.noties.markwon.sample.extension;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
import org.commonmark.node.CustomNode;
|
import org.commonmark.node.CustomNode;
|
||||||
import org.commonmark.node.Delimited;
|
import org.commonmark.node.Delimited;
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
public class IconNode extends CustomNode implements Delimited {
|
public class IconNode extends CustomNode implements Delimited {
|
||||||
|
|
||||||
|
|
||||||
public static final char DELIMITER = '@';
|
public static final char DELIMITER = '@';
|
||||||
|
|
||||||
public static final String DELIMITER_STRING = "" + DELIMITER;
|
public static final String DELIMITER_STRING = "" + DELIMITER;
|
@ -0,0 +1,160 @@
|
|||||||
|
package ru.noties.markwon.sample.extension;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import org.commonmark.node.Node;
|
||||||
|
import org.commonmark.node.Text;
|
||||||
|
import org.commonmark.parser.delimiter.DelimiterProcessor;
|
||||||
|
import org.commonmark.parser.delimiter.DelimiterRun;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
public class IconProcessor implements DelimiterProcessor {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static IconProcessor create() {
|
||||||
|
return new IconProcessor();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ic-home-black-24
|
||||||
|
private static final Pattern PATTERN = Pattern.compile("ic-(\\w+)-(\\w+)-(\\d+)");
|
||||||
|
|
||||||
|
private static final String TO_FIND = IconNode.DELIMITER_STRING + "ic-";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should be used when input string does not wrap icon definition with `@` from both ends.
|
||||||
|
* So, `@ic-home-white-24` would become `@ic-home-white-24@`. This way parsing is easier
|
||||||
|
* and more predictable (cannot specify multiple ending delimiters, as we would require them:
|
||||||
|
* space, newline, end of a document, and a lot of more)
|
||||||
|
*
|
||||||
|
* @param input to process
|
||||||
|
* @return processed string
|
||||||
|
* @see #prepare(StringBuilder)
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static String prepare(@NonNull String input) {
|
||||||
|
final StringBuilder builder = new StringBuilder(input);
|
||||||
|
prepare(builder);
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void prepare(@NonNull StringBuilder builder) {
|
||||||
|
|
||||||
|
int start = builder.indexOf(TO_FIND);
|
||||||
|
int end;
|
||||||
|
|
||||||
|
while (start > -1) {
|
||||||
|
|
||||||
|
end = iconDefinitionEnd(start + TO_FIND.length(), builder);
|
||||||
|
|
||||||
|
// if we match our pattern, append `@` else ignore
|
||||||
|
if (iconDefinitionValid(builder.subSequence(start + 1, end))) {
|
||||||
|
builder.insert(end, '@');
|
||||||
|
}
|
||||||
|
|
||||||
|
// move to next
|
||||||
|
start = builder.indexOf(TO_FIND, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char getOpeningCharacter() {
|
||||||
|
return IconNode.DELIMITER;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char getClosingCharacter() {
|
||||||
|
return IconNode.DELIMITER;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMinLength() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDelimiterUse(DelimiterRun opener, DelimiterRun closer) {
|
||||||
|
return opener.length() >= 1 && closer.length() >= 1 ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process(Text opener, Text closer, int delimiterUse) {
|
||||||
|
|
||||||
|
final IconGroupNode iconGroupNode = new IconGroupNode();
|
||||||
|
|
||||||
|
final Node next = opener.getNext();
|
||||||
|
|
||||||
|
boolean handled = false;
|
||||||
|
|
||||||
|
// process only if we have exactly one Text node
|
||||||
|
if (next instanceof Text && next.getNext() == closer) {
|
||||||
|
|
||||||
|
final String text = ((Text) next).getLiteral();
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(text)) {
|
||||||
|
|
||||||
|
// attempt to match
|
||||||
|
final Matcher matcher = PATTERN.matcher(text);
|
||||||
|
if (matcher.matches()) {
|
||||||
|
final IconNode iconNode = new IconNode(
|
||||||
|
matcher.group(1),
|
||||||
|
matcher.group(2),
|
||||||
|
matcher.group(3)
|
||||||
|
);
|
||||||
|
iconGroupNode.appendChild(iconNode);
|
||||||
|
next.unlink();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!handled) {
|
||||||
|
|
||||||
|
// restore delimiters if we didn't match
|
||||||
|
|
||||||
|
iconGroupNode.appendChild(new Text(IconNode.DELIMITER_STRING));
|
||||||
|
|
||||||
|
Node node;
|
||||||
|
for (Node tmp = opener.getNext(); tmp != null && tmp != closer; tmp = node) {
|
||||||
|
node = tmp.getNext();
|
||||||
|
// append a child anyway
|
||||||
|
iconGroupNode.appendChild(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
iconGroupNode.appendChild(new Text(IconNode.DELIMITER_STRING));
|
||||||
|
}
|
||||||
|
|
||||||
|
opener.insertBefore(iconGroupNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int iconDefinitionEnd(int index, @NonNull StringBuilder builder) {
|
||||||
|
|
||||||
|
// all spaces, new lines, non-words or digits,
|
||||||
|
|
||||||
|
char c;
|
||||||
|
|
||||||
|
int end = -1;
|
||||||
|
for (int i = index; i < builder.length(); i++) {
|
||||||
|
c = builder.charAt(i);
|
||||||
|
if (Character.isWhitespace(c)
|
||||||
|
|| !(Character.isLetterOrDigit(c) || c == '-' || c == '_')) {
|
||||||
|
end = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end == -1) {
|
||||||
|
end = builder.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean iconDefinitionValid(@NonNull CharSequence cs) {
|
||||||
|
final Matcher matcher = PATTERN.matcher(cs);
|
||||||
|
return matcher.matches();
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package noties.ru.markwon_samplecustomextension;
|
package ru.noties.markwon.sample.extension;
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
@ -12,10 +12,11 @@ import android.text.style.ReplacementSpan;
|
|||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
public class IconSpan extends ReplacementSpan {
|
public class IconSpan extends ReplacementSpan {
|
||||||
|
|
||||||
@IntDef({ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER})
|
@IntDef({ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER})
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.CLASS)
|
||||||
@interface Alignment {
|
@interface Alignment {
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
|||||||
|
package ru.noties.markwon.sample.extension;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.support.annotation.DrawableRes;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
public abstract class IconSpanProvider {
|
||||||
|
|
||||||
|
@SuppressWarnings("SameParameterValue")
|
||||||
|
@NonNull
|
||||||
|
public static IconSpanProvider create(@NonNull Context context, @DrawableRes int fallBack) {
|
||||||
|
return new Impl(context, fallBack);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public abstract IconSpan provide(@NonNull String name, @NonNull String color, @NonNull String size);
|
||||||
|
|
||||||
|
|
||||||
|
private static class Impl extends IconSpanProvider {
|
||||||
|
|
||||||
|
private static final boolean IS_L = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final Resources resources;
|
||||||
|
private final int fallBack;
|
||||||
|
|
||||||
|
Impl(@NonNull Context context, @DrawableRes int fallBack) {
|
||||||
|
this.context = context;
|
||||||
|
this.resources = context.getResources();
|
||||||
|
this.fallBack = fallBack;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public IconSpan provide(@NonNull String name, @NonNull String color, @NonNull String size) {
|
||||||
|
final String resName = iconName(name, color, size);
|
||||||
|
int resId = resources.getIdentifier(resName, "drawable", context.getPackageName());
|
||||||
|
if (resId == 0) {
|
||||||
|
resId = fallBack;
|
||||||
|
}
|
||||||
|
return new IconSpan(getDrawable(resId), IconSpan.ALIGN_CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static String iconName(@NonNull String name, @NonNull String color, @NonNull String size) {
|
||||||
|
return "ic_" + name + "_" + color + "_" + size + "dp";
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private Drawable getDrawable(int resId) {
|
||||||
|
final Drawable drawable;
|
||||||
|
if (IS_L) {
|
||||||
|
drawable = context.getDrawable(resId);
|
||||||
|
} else {
|
||||||
|
drawable = resources.getDrawable(resId);
|
||||||
|
}
|
||||||
|
//noinspection ConstantConditions
|
||||||
|
return drawable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package noties.ru.markwon_samplecustomextension;
|
package ru.noties.markwon.sample.extension;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@ -9,6 +9,7 @@ import ru.noties.markwon.SpannableBuilder;
|
|||||||
import ru.noties.markwon.SpannableConfiguration;
|
import ru.noties.markwon.SpannableConfiguration;
|
||||||
import ru.noties.markwon.renderer.SpannableMarkdownVisitor;
|
import ru.noties.markwon.renderer.SpannableMarkdownVisitor;
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
public class IconVisitor extends SpannableMarkdownVisitor {
|
public class IconVisitor extends SpannableMarkdownVisitor {
|
||||||
|
|
||||||
private final SpannableBuilder builder;
|
private final SpannableBuilder builder;
|
@ -0,0 +1,66 @@
|
|||||||
|
package ru.noties.markwon.sample.extension;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension;
|
||||||
|
import org.commonmark.ext.gfm.tables.TablesExtension;
|
||||||
|
import org.commonmark.node.Node;
|
||||||
|
import org.commonmark.parser.Parser;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import ru.noties.markwon.SpannableBuilder;
|
||||||
|
import ru.noties.markwon.SpannableConfiguration;
|
||||||
|
import ru.noties.markwon.tasklist.TaskListExtension;
|
||||||
|
|
||||||
|
public class MainActivity extends Activity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
|
final TextView textView = findViewById(R.id.text_view);
|
||||||
|
|
||||||
|
// obtain an instance of parser
|
||||||
|
final Parser parser = new Parser.Builder()
|
||||||
|
// we will register all known to Markwon extensions
|
||||||
|
.extensions(Arrays.asList(
|
||||||
|
StrikethroughExtension.create(),
|
||||||
|
TablesExtension.create(),
|
||||||
|
TaskListExtension.create()
|
||||||
|
))
|
||||||
|
// this is the handler for custom icons
|
||||||
|
.customDelimiterProcessor(IconProcessor.create())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// we process input to wrap icon definitions with `@` on both ends
|
||||||
|
// if your input already does it, there is not need for `IconProcessor.prepare()` call.
|
||||||
|
final String markdown = IconProcessor.prepare(getString(R.string.input));
|
||||||
|
|
||||||
|
final Node node = parser.parse(markdown);
|
||||||
|
|
||||||
|
final SpannableBuilder builder = new SpannableBuilder();
|
||||||
|
|
||||||
|
// please note that here I am passing `0` as fallback it means that if markdown references
|
||||||
|
// unknown icon, it will try to load fallback one and will fail with ResourceNotFound. It's
|
||||||
|
// better to provide a valid fallback option
|
||||||
|
final IconSpanProvider spanProvider = IconSpanProvider.create(this, 0);
|
||||||
|
|
||||||
|
// create an instance of visitor to process parsed markdown
|
||||||
|
final IconVisitor visitor = new IconVisitor(
|
||||||
|
SpannableConfiguration.create(this),
|
||||||
|
builder,
|
||||||
|
spanProvider
|
||||||
|
);
|
||||||
|
|
||||||
|
// trigger visit
|
||||||
|
node.accept(visitor);
|
||||||
|
|
||||||
|
// apply
|
||||||
|
textView.setText(builder.text());
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 631 B |
Before Width: | Height: | Size: 336 B |
Before Width: | Height: | Size: 231 B |
Before Width: | Height: | Size: 399 B |
Before Width: | Height: | Size: 216 B |
Before Width: | Height: | Size: 184 B |
Before Width: | Height: | Size: 835 B |
Before Width: | Height: | Size: 297 B |
Before Width: | Height: | Size: 261 B |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 432 B |
Before Width: | Height: | Size: 327 B |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 401 B |
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M6,18c0,0.55 0.45,1 1,1h1v3.5c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5L11,19h2v3.5c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5L16,19h1c0.55,0 1,-0.45 1,-1L18,8L6,8v10zM3.5,8C2.67,8 2,8.67 2,9.5v7c0,0.83 0.67,1.5 1.5,1.5S5,17.33 5,16.5v-7C5,8.67 4.33,8 3.5,8zM20.5,8c-0.83,0 -1.5,0.67 -1.5,1.5v7c0,0.83 0.67,1.5 1.5,1.5s1.5,-0.67 1.5,-1.5v-7c0,-0.83 -0.67,-1.5 -1.5,-1.5zM15.53,2.16l1.3,-1.3c0.2,-0.2 0.2,-0.51 0,-0.71 -0.2,-0.2 -0.51,-0.2 -0.71,0l-1.48,1.48C13.85,1.23 12.95,1 12,1c-0.96,0 -1.86,0.23 -2.66,0.63L7.85,0.15c-0.2,-0.2 -0.51,-0.2 -0.71,0 -0.2,0.2 -0.2,0.51 0,0.71l1.31,1.31C6.97,3.26 6,5.01 6,7h12c0,-1.99 -0.97,-3.75 -2.47,-4.84zM10,5L9,5L9,4h1v1zM15,5h-1L14,4h1v1z"/>
|
||||||
|
</vector>
|
@ -0,0 +1,4 @@
|
|||||||
|
<vector android:height="36dp" android:viewportHeight="24.0"
|
||||||
|
android:viewportWidth="24.0" android:width="36dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/>
|
||||||
|
</vector>
|
@ -0,0 +1,4 @@
|
|||||||
|
<vector android:height="48dp" android:viewportHeight="24.0"
|
||||||
|
android:viewportWidth="24.0" android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M15,9L9,9v6h6L15,9zM13,13h-2v-2h2v2zM21,11L21,9h-2L19,7c0,-1.1 -0.9,-2 -2,-2h-2L15,3h-2v2h-2L11,3L9,3v2L7,5c-1.1,0 -2,0.9 -2,2v2L3,9v2h2v2L3,13v2h2v2c0,1.1 0.9,2 2,2h2v2h2v-2h2v2h2v-2h2c1.1,0 2,-0.9 2,-2v-2h2v-2h-2v-2h2zM17,17L7,17L7,7h10v10z"/>
|
||||||
|
</vector>
|
@ -0,0 +1,6 @@
|
|||||||
|
<vector android:height="64dp" android:viewportHeight="24.0"
|
||||||
|
android:viewportWidth="24.0" android:width="64dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FFFF0000" android:pathData="M15.5,9.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/>
|
||||||
|
<path android:fillColor="#FFFF0000" android:pathData="M8.5,9.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/>
|
||||||
|
<path android:fillColor="#FFFF0000" android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12,16c-1.48,0 -2.75,-0.81 -3.45,-2L6.88,14c0.8,2.05 2.79,3.5 5.12,3.5s4.32,-1.45 5.12,-3.5h-1.67c-0.7,1.19 -1.97,2 -3.45,2z"/>
|
||||||
|
</vector>
|
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dip"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
tools:text="@string/input"/>
|
||||||
|
|
||||||
|
</ScrollView>
|
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<color name="colorPrimary">#3F51B5</color>
|
|
||||||
<color name="colorPrimaryDark">#303F9F</color>
|
|
||||||
<color name="colorAccent">#FF4081</color>
|
|
||||||
</resources>
|
|
@ -1,3 +1,13 @@
|
|||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<string name="app_name">Markwon-SampleCustomExtension</string>
|
<string name="app_name">Markwon-SampleCustomExtension</string>
|
||||||
|
|
||||||
|
<string name="input"><![CDATA[
|
||||||
|
# Hello! @ic-android-black-24\n\n
|
||||||
|
Home 36 black: @ic-home-black-36\n\n
|
||||||
|
Memory 48 black: @ic-memory-black-48\n\n
|
||||||
|
Sentiment Satisfied 64 red: @ic-sentiment_satisfied-red-64
|
||||||
|
]]>
|
||||||
|
</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<!-- Base application theme. -->
|
<style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar"/>
|
||||||
<style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
|
|
||||||
<!-- Customize your theme here. -->
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|