diff --git a/app/build.gradle b/app/build.gradle
index bc2bca96..286e6bb9 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -27,13 +27,13 @@ android {
dependencies {
- compile project(':library')
- compile project(':library-image-loader')
+ implementation project(':library')
+ implementation project(':library-image-loader')
- compile 'ru.noties:debug:3.0.0@jar'
+ implementation 'ru.noties:debug:3.0.0@jar'
- compile OK_HTTP
+ implementation OK_HTTP
- compile 'com.google.dagger:dagger:2.10'
+ implementation 'com.google.dagger:dagger:2.10'
annotationProcessor 'com.google.dagger:dagger-compiler:2.10'
}
diff --git a/build.gradle b/build.gradle
index 036e147b..ef441eaf 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,7 +4,7 @@ buildscript {
google()
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.3.3'
+ classpath 'com.android.tools.build:gradle:3.0.1'
}
}
@@ -22,7 +22,7 @@ task clean(type: Delete) {
}
task wrapper(type: Wrapper) {
- gradleVersion '4.3'
+ gradleVersion '4.5'
distributionType 'all'
}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 7a3265ee..27768f1b 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 590f0e81..610ad4c5 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.5-all.zip
diff --git a/library-image-loader/build.gradle b/library-image-loader/build.gradle
index 3a909101..5c8f993b 100644
--- a/library-image-loader/build.gradle
+++ b/library-image-loader/build.gradle
@@ -19,10 +19,10 @@ android {
}
dependencies {
- compile project(':library')
- compile ANDROID_SVG
- compile ANDROID_GIF
- compile OK_HTTP
+ api project(':library')
+ api ANDROID_SVG
+ api ANDROID_GIF
+ api OK_HTTP
}
if (project.hasProperty('release')) {
diff --git a/library-view/build.gradle b/library-view/build.gradle
index c0675588..bb6073b7 100644
--- a/library-view/build.gradle
+++ b/library-view/build.gradle
@@ -14,8 +14,8 @@ android {
}
dependencies {
- compile project(':library')
- provided SUPPORT_APP_COMPAT
+ api project(':library')
+ compileOnly SUPPORT_APP_COMPAT
}
if (project.hasProperty('release')) {
diff --git a/library/build.gradle b/library/build.gradle
index c384c1b6..702b609b 100644
--- a/library/build.gradle
+++ b/library/build.gradle
@@ -14,10 +14,10 @@ android {
}
dependencies {
- compile SUPPORT_ANNOTATIONS
- compile COMMON_MARK
- compile COMMON_MARK_STRIKETHROUGHT
- compile COMMON_MARK_TABLE
+ api SUPPORT_ANNOTATIONS
+ api COMMON_MARK
+ api COMMON_MARK_STRIKETHROUGHT
+ api COMMON_MARK_TABLE
}
if (project.hasProperty('release')) {
diff --git a/sample-custom-extension/README.md b/sample-custom-extension/README.md
new file mode 100644
index 00000000..194557ab
--- /dev/null
+++ b/sample-custom-extension/README.md
@@ -0,0 +1,26 @@
+# Custom extension
+
+This module provides a simple implementation for icons that are bundled in your application resources using custom `DelimiterProcessor`. It can be used as a reference when dealing with new functionality based on _delimiters_.
+
+```markdown
+# Hello @ic-android-black-24
+
+**Please** click @ic-home-green-24 (home icon) if you want to go home.
+```
+
+Here we will substitute elements starting with `@ic-` for icons that we have in our resources:
+
+* `@ic-android-black-24` -> `R.drawable.ic_android_black_24dp`
+* `@ic-home-green-24` -> `R.drawable.ic_home_green_24dp`
+
+In order to provide reliable parsing we need to have delimiters _around_ desired content. So, `@ic-home-green-24` would become `@ic-home-green-24@`. This is current limitation of [commonmark-java](https://github.com/atlassian/commonmark-java) library that Markwon uses underneath. There is an ongoing [issue](https://github.com/atlassian/commonmark-java/issues/113) that might change this in future thought.
+
+But as we known the pattern beforehand it's pretty easy to pre-process raw markdown and make it the way we want it. Please refer to `IconProcessor#process` method for the reference.
+
+So, the our steps would be:
+
+* prepare raw markdown (wrap icons with `@` if it's not already)
+* construct a Parser with our registered delimiter processor
+* parse markdown and obtain a `Node`
+* create a node visitor that will additionally visit custom node (`IconNode`)
+* use markdown
diff --git a/sample-custom-extension/build.gradle b/sample-custom-extension/build.gradle
new file mode 100644
index 00000000..baa37484
--- /dev/null
+++ b/sample-custom-extension/build.gradle
@@ -0,0 +1,22 @@
+apply plugin: 'com.android.application'
+
+android {
+
+ compileSdkVersion TARGET_SDK
+ buildToolsVersion BUILD_TOOLS
+
+ defaultConfig {
+
+ applicationId "ru.noties.markwon.sample.extension"
+
+ // using 21 as minimum only to be able to vector assets
+ minSdkVersion 21
+ targetSdkVersion TARGET_SDK
+ versionCode 1
+ versionName version
+ }
+}
+
+dependencies {
+ implementation project(':library')
+}
diff --git a/sample-custom-extension/src/main/AndroidManifest.xml b/sample-custom-extension/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..1553a0a6
--- /dev/null
+++ b/sample-custom-extension/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconGroupNode.java b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconGroupNode.java
new file mode 100644
index 00000000..193b33b8
--- /dev/null
+++ b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconGroupNode.java
@@ -0,0 +1,8 @@
+package ru.noties.markwon.sample.extension;
+
+import org.commonmark.node.CustomNode;
+
+@SuppressWarnings("WeakerAccess")
+public class IconGroupNode extends CustomNode {
+
+}
diff --git a/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconNode.java b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconNode.java
new file mode 100644
index 00000000..3d40ec2f
--- /dev/null
+++ b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconNode.java
@@ -0,0 +1,61 @@
+package ru.noties.markwon.sample.extension;
+
+import android.support.annotation.NonNull;
+
+import org.commonmark.node.CustomNode;
+import org.commonmark.node.Delimited;
+
+@SuppressWarnings("WeakerAccess")
+public class IconNode extends CustomNode implements Delimited {
+
+ public static final char DELIMITER = '@';
+
+ public static final String DELIMITER_STRING = "" + DELIMITER;
+
+
+ private final String name;
+
+ private final String color;
+
+ private final String size;
+
+ public IconNode(@NonNull String name, @NonNull String color, @NonNull String size) {
+ this.name = name;
+ this.color = color;
+ this.size = size;
+ }
+
+ @NonNull
+ public String name() {
+ return name;
+ }
+
+ @NonNull
+ public String color() {
+ return color;
+ }
+
+ @NonNull
+ public String size() {
+ return size;
+ }
+
+ @Override
+ public String getOpeningDelimiter() {
+ return DELIMITER_STRING;
+ }
+
+ @Override
+ public String getClosingDelimiter() {
+ return DELIMITER_STRING;
+ }
+
+ @Override
+ public String toString() {
+ return "IconNode{" +
+ "name='" + name + '\'' +
+ ", color='" + color + '\'' +
+ ", size='" + size + '\'' +
+ '}';
+ }
+}
diff --git a/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconProcessor.java b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconProcessor.java
new file mode 100644
index 00000000..500726e0
--- /dev/null
+++ b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconProcessor.java
@@ -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();
+ }
+}
diff --git a/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconSpan.java b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconSpan.java
new file mode 100644
index 00000000..690ba5b2
--- /dev/null
+++ b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconSpan.java
@@ -0,0 +1,77 @@
+package ru.noties.markwon.sample.extension;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.style.ReplacementSpan;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@SuppressWarnings("WeakerAccess")
+public class IconSpan extends ReplacementSpan {
+
+ @IntDef({ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER})
+ @Retention(RetentionPolicy.CLASS)
+ @interface Alignment {
+ }
+
+ public static final int ALIGN_BOTTOM = 0;
+ public static final int ALIGN_BASELINE = 1;
+ public static final int ALIGN_CENTER = 2; // will only center if drawable height is less than text line height
+
+
+ private final Drawable drawable;
+
+ private final int alignment;
+
+ public IconSpan(@NonNull Drawable drawable, @Alignment int alignment) {
+ this.drawable = drawable;
+ this.alignment = alignment;
+ if (drawable.getBounds().isEmpty()) {
+ drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
+ }
+ }
+
+ @Override
+ public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm) {
+
+ final Rect rect = drawable.getBounds();
+
+ if (fm != null) {
+ fm.ascent = -rect.bottom;
+ fm.descent = 0;
+
+ fm.top = fm.ascent;
+ fm.bottom = 0;
+ }
+
+ return rect.right;
+ }
+
+ @Override
+ public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
+
+ final int b = bottom - drawable.getBounds().bottom;
+
+ final int save = canvas.save();
+ try {
+ final int translationY;
+ if (ALIGN_CENTER == alignment) {
+ translationY = b - ((bottom - top - drawable.getBounds().height()) / 2);
+ } else if (ALIGN_BASELINE == alignment) {
+ translationY = b - paint.getFontMetricsInt().descent;
+ } else {
+ translationY = b;
+ }
+ canvas.translate(x, translationY);
+ drawable.draw(canvas);
+ } finally {
+ canvas.restoreToCount(save);
+ }
+ }
+}
diff --git a/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconSpanProvider.java b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconSpanProvider.java
new file mode 100644
index 00000000..cd6e3bf3
--- /dev/null
+++ b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconSpanProvider.java
@@ -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;
+ }
+ }
+}
diff --git a/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconVisitor.java b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconVisitor.java
new file mode 100644
index 00000000..d373ff75
--- /dev/null
+++ b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/IconVisitor.java
@@ -0,0 +1,63 @@
+package ru.noties.markwon.sample.extension;
+
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
+import android.widget.TextView;
+
+import org.commonmark.node.CustomNode;
+
+import ru.noties.markwon.SpannableBuilder;
+import ru.noties.markwon.SpannableConfiguration;
+import ru.noties.markwon.renderer.SpannableMarkdownVisitor;
+
+@SuppressWarnings("WeakerAccess")
+public class IconVisitor extends SpannableMarkdownVisitor {
+
+ private final SpannableBuilder builder;
+
+ private final IconSpanProvider iconSpanProvider;
+
+ public IconVisitor(
+ @NonNull SpannableConfiguration configuration,
+ @NonNull SpannableBuilder builder,
+ @NonNull IconSpanProvider iconSpanProvider
+ ) {
+ super(configuration, builder);
+ this.builder = builder;
+ this.iconSpanProvider = iconSpanProvider;
+ }
+
+ @Override
+ public void visit(CustomNode customNode) {
+ if (!visitIconNode(customNode)) {
+ super.visit(customNode);
+ }
+ }
+
+ private boolean visitIconNode(@NonNull CustomNode customNode) {
+
+ if (customNode instanceof IconNode) {
+
+ final IconNode node = (IconNode) customNode;
+
+ final String name = node.name();
+ final String color = node.color();
+ final String size = node.size();
+
+ if (!TextUtils.isEmpty(name)
+ && !TextUtils.isEmpty(color)
+ && !TextUtils.isEmpty(size)) {
+
+ final int length = builder.length();
+
+ builder.append(name);
+ builder.setSpan(iconSpanProvider.provide(name, color, size), length);
+ builder.append(' ');
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/MainActivity.java b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/MainActivity.java
new file mode 100644
index 00000000..a81f8eb0
--- /dev/null
+++ b/sample-custom-extension/src/main/java/ru/noties/markwon/sample/extension/MainActivity.java
@@ -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());
+ }
+}
diff --git a/sample-custom-extension/src/main/res/drawable-v24/ic_launcher_foreground.xml b/sample-custom-extension/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 00000000..c7bd21db
--- /dev/null
+++ b/sample-custom-extension/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample-custom-extension/src/main/res/drawable/ic_android_black_24dp.xml b/sample-custom-extension/src/main/res/drawable/ic_android_black_24dp.xml
new file mode 100644
index 00000000..401cbf63
--- /dev/null
+++ b/sample-custom-extension/src/main/res/drawable/ic_android_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/sample-custom-extension/src/main/res/drawable/ic_home_black_36dp.xml b/sample-custom-extension/src/main/res/drawable/ic_home_black_36dp.xml
new file mode 100644
index 00000000..c3b7e150
--- /dev/null
+++ b/sample-custom-extension/src/main/res/drawable/ic_home_black_36dp.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/sample-custom-extension/src/main/res/drawable/ic_launcher_background.xml b/sample-custom-extension/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 00000000..d5fccc53
--- /dev/null
+++ b/sample-custom-extension/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample-custom-extension/src/main/res/drawable/ic_memory_black_48dp.xml b/sample-custom-extension/src/main/res/drawable/ic_memory_black_48dp.xml
new file mode 100644
index 00000000..88ac2954
--- /dev/null
+++ b/sample-custom-extension/src/main/res/drawable/ic_memory_black_48dp.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/sample-custom-extension/src/main/res/drawable/ic_sentiment_satisfied_red_64dp.xml b/sample-custom-extension/src/main/res/drawable/ic_sentiment_satisfied_red_64dp.xml
new file mode 100644
index 00000000..f9c60705
--- /dev/null
+++ b/sample-custom-extension/src/main/res/drawable/ic_sentiment_satisfied_red_64dp.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/sample-custom-extension/src/main/res/layout/activity_main.xml b/sample-custom-extension/src/main/res/layout/activity_main.xml
new file mode 100644
index 00000000..439dac71
--- /dev/null
+++ b/sample-custom-extension/src/main/res/layout/activity_main.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample-custom-extension/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/sample-custom-extension/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 00000000..eca70cfe
--- /dev/null
+++ b/sample-custom-extension/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/sample-custom-extension/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/sample-custom-extension/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 00000000..eca70cfe
--- /dev/null
+++ b/sample-custom-extension/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/sample-custom-extension/src/main/res/mipmap-hdpi/ic_launcher.png b/sample-custom-extension/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..a2f59082
Binary files /dev/null and b/sample-custom-extension/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/sample-custom-extension/src/main/res/mipmap-hdpi/ic_launcher_round.png b/sample-custom-extension/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 00000000..1b523998
Binary files /dev/null and b/sample-custom-extension/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/sample-custom-extension/src/main/res/mipmap-mdpi/ic_launcher.png b/sample-custom-extension/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..ff10afd6
Binary files /dev/null and b/sample-custom-extension/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/sample-custom-extension/src/main/res/mipmap-mdpi/ic_launcher_round.png b/sample-custom-extension/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 00000000..115a4c76
Binary files /dev/null and b/sample-custom-extension/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/sample-custom-extension/src/main/res/mipmap-xhdpi/ic_launcher.png b/sample-custom-extension/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..dcd3cd80
Binary files /dev/null and b/sample-custom-extension/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/sample-custom-extension/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/sample-custom-extension/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..459ca609
Binary files /dev/null and b/sample-custom-extension/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/sample-custom-extension/src/main/res/mipmap-xxhdpi/ic_launcher.png b/sample-custom-extension/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..8ca12fe0
Binary files /dev/null and b/sample-custom-extension/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/sample-custom-extension/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/sample-custom-extension/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..8e19b410
Binary files /dev/null and b/sample-custom-extension/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/sample-custom-extension/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/sample-custom-extension/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..b824ebdd
Binary files /dev/null and b/sample-custom-extension/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/sample-custom-extension/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/sample-custom-extension/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..4c19a13c
Binary files /dev/null and b/sample-custom-extension/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/sample-custom-extension/src/main/res/values/strings.xml b/sample-custom-extension/src/main/res/values/strings.xml
new file mode 100644
index 00000000..7dc177e7
--- /dev/null
+++ b/sample-custom-extension/src/main/res/values/strings.xml
@@ -0,0 +1,13 @@
+
+
+ Markwon-SampleCustomExtension
+
+
+
+
+
diff --git a/sample-custom-extension/src/main/res/values/styles.xml b/sample-custom-extension/src/main/res/values/styles.xml
new file mode 100644
index 00000000..49c8cb25
--- /dev/null
+++ b/sample-custom-extension/src/main/res/values/styles.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/settings.gradle b/settings.gradle
index 59f2e834..fbd8c3c4 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1 @@
-include ':app', ':library', ':library-image-loader', ':library-view'
+include ':app', ':library', ':library-image-loader', ':library-view', ':sample-custom-extension'