diff --git a/build.gradle b/build.gradle
index f06fea38..d45c2466 100644
--- a/build.gradle
+++ b/build.gradle
@@ -29,6 +29,7 @@ ext {
// Dependencies
final def supportVersion = '25.3.1'
SUPPORT_ANNOTATIONS = "com.android.support:support-annotations:$supportVersion"
+ SUPPORT_APP_COMPAT = "com.android.support:appcompat-v7:$supportVersion"
final def commonMarkVersion = '0.9.0'
COMMON_MARK = "com.atlassian.commonmark:commonmark:$commonMarkVersion"
diff --git a/library-view/build.gradle b/library-view/build.gradle
new file mode 100644
index 00000000..c0675588
--- /dev/null
+++ b/library-view/build.gradle
@@ -0,0 +1,23 @@
+apply plugin: 'com.android.library'
+
+android {
+
+ compileSdkVersion TARGET_SDK
+ buildToolsVersion BUILD_TOOLS
+
+ defaultConfig {
+ minSdkVersion MIN_SDK
+ targetSdkVersion TARGET_SDK
+ versionCode 1
+ versionName version
+ }
+}
+
+dependencies {
+ compile project(':library')
+ provided SUPPORT_APP_COMPAT
+}
+
+if (project.hasProperty('release')) {
+ apply from: 'https://raw.githubusercontent.com/chrisbanes/gradle-mvn-push/master/gradle-mvn-push.gradle'
+}
diff --git a/library-view/src/debug/java/ru/noties/markwon/view/debug/DebugConfigurationProvider.java b/library-view/src/debug/java/ru/noties/markwon/view/debug/DebugConfigurationProvider.java
new file mode 100644
index 00000000..e530bab5
--- /dev/null
+++ b/library-view/src/debug/java/ru/noties/markwon/view/debug/DebugConfigurationProvider.java
@@ -0,0 +1,31 @@
+package ru.noties.markwon.view.debug;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+
+import ru.noties.markwon.SpannableConfiguration;
+import ru.noties.markwon.spans.SpannableTheme;
+import ru.noties.markwon.view.IMarkwonView;
+
+public class DebugConfigurationProvider implements IMarkwonView.ConfigurationProvider {
+
+ private SpannableConfiguration cached;
+
+ @NonNull
+ @Override
+ public SpannableConfiguration provide(@NonNull Context context) {
+ if (cached == null) {
+ cached = SpannableConfiguration.builder(context)
+ .theme(debugTheme(context))
+ .build();
+ }
+ return cached;
+ }
+
+ private static SpannableTheme debugTheme(@NonNull Context context) {
+ return SpannableTheme.builderWithDefaults(context)
+ .blockQuoteColor(0xFFff0000)
+ .codeBackgroundColor(0x40FF0000)
+ .build();
+ }
+}
diff --git a/library-view/src/debug/res/layout/debug_markwon_preview.xml b/library-view/src/debug/res/layout/debug_markwon_preview.xml
new file mode 100644
index 00000000..ecc7bd3d
--- /dev/null
+++ b/library-view/src/debug/res/layout/debug_markwon_preview.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library-view/src/debug/res/layout/debug_markwon_preview_compat.xml b/library-view/src/debug/res/layout/debug_markwon_preview_compat.xml
new file mode 100644
index 00000000..092dbf00
--- /dev/null
+++ b/library-view/src/debug/res/layout/debug_markwon_preview_compat.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library-view/src/debug/res/values/debug_strings.xml b/library-view/src/debug/res/values/debug_strings.xml
new file mode 100644
index 00000000..e7b87ab2
--- /dev/null
+++ b/library-view/src/debug/res/values/debug_strings.xml
@@ -0,0 +1,39 @@
+
+
+
+
+ Quote
+ \n
+ >> Quote #2
+ \n
+ >>> Quote `#3`
+ \n
+ ---
+ \n
+ ```
+ \n
+ // this is some amazing code block
+ \n
+ ```
+ \n
+ * First
+ \n
+ * Second
+ \n
+ * * Second-First
+ \n
+ * * Second-Second
+ \n
+ * * * Second-Second-First
+ \n
+ * And out of blue - Third
+ ]]>
+
+
+
\ No newline at end of file
diff --git a/library-view/src/main/AndroidManifest.xml b/library-view/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..6340e29d
--- /dev/null
+++ b/library-view/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/library-view/src/main/java/ru/noties/markwon/view/IMarkwonView.java b/library-view/src/main/java/ru/noties/markwon/view/IMarkwonView.java
new file mode 100644
index 00000000..4db6d2c7
--- /dev/null
+++ b/library-view/src/main/java/ru/noties/markwon/view/IMarkwonView.java
@@ -0,0 +1,23 @@
+package ru.noties.markwon.view;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import ru.noties.markwon.SpannableConfiguration;
+
+public interface IMarkwonView {
+
+ interface ConfigurationProvider {
+ @NonNull
+ SpannableConfiguration provide(@NonNull Context context);
+ }
+
+ void setConfigurationProvider(@NonNull ConfigurationProvider provider);
+
+ void setMarkdown(@Nullable String markdown);
+ void setMarkdown(@Nullable SpannableConfiguration configuration, @Nullable String markdown);
+
+ @Nullable
+ String getMarkdown();
+}
diff --git a/library-view/src/main/java/ru/noties/markwon/view/MarkwonView.java b/library-view/src/main/java/ru/noties/markwon/view/MarkwonView.java
new file mode 100644
index 00000000..19ab09b0
--- /dev/null
+++ b/library-view/src/main/java/ru/noties/markwon/view/MarkwonView.java
@@ -0,0 +1,50 @@
+package ru.noties.markwon.view;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import ru.noties.markwon.SpannableConfiguration;
+
+@SuppressLint("AppCompatCustomView")
+public class MarkwonView extends TextView implements IMarkwonView {
+
+ private MarkwonViewHelper helper;
+
+ public MarkwonView(Context context) {
+ super(context);
+ init(context, null);
+ }
+
+ public MarkwonView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context, attrs);
+ }
+
+ private void init(Context context, AttributeSet attributeSet) {
+ helper = MarkwonViewHelper.create(this);
+ helper.init(context, attributeSet);
+ }
+
+ @Override
+ public void setConfigurationProvider(@NonNull ConfigurationProvider provider) {
+ helper.setConfigurationProvider(provider);
+ }
+
+ public void setMarkdown(@Nullable String markdown) {
+ helper.setMarkdown(markdown);
+ }
+
+ public void setMarkdown(@Nullable SpannableConfiguration configuration, @Nullable String markdown) {
+ helper.setMarkdown(configuration, markdown);
+ }
+
+ @Nullable
+ @Override
+ public String getMarkdown() {
+ return helper.getMarkdown();
+ }
+}
diff --git a/library-view/src/main/java/ru/noties/markwon/view/MarkwonViewCompat.java b/library-view/src/main/java/ru/noties/markwon/view/MarkwonViewCompat.java
new file mode 100644
index 00000000..da5c1934
--- /dev/null
+++ b/library-view/src/main/java/ru/noties/markwon/view/MarkwonViewCompat.java
@@ -0,0 +1,50 @@
+package ru.noties.markwon.view;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.AppCompatTextView;
+import android.util.AttributeSet;
+
+import ru.noties.markwon.SpannableConfiguration;
+
+public class MarkwonViewCompat extends AppCompatTextView implements IMarkwonView {
+
+ private MarkwonViewHelper helper;
+
+ public MarkwonViewCompat(Context context) {
+ super(context);
+ init(context, null);
+ }
+
+ public MarkwonViewCompat(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context, attrs);
+ }
+
+ private void init(Context context, AttributeSet attributeSet) {
+ helper = MarkwonViewHelper.create(this);
+ helper.init(context, attributeSet);
+ }
+
+ @Override
+ public void setConfigurationProvider(@NonNull ConfigurationProvider provider) {
+ helper.setConfigurationProvider(provider);
+ }
+
+ @Override
+ public void setMarkdown(@Nullable String markdown) {
+ helper.setMarkdown(markdown);
+ }
+
+ @Override
+ public void setMarkdown(@Nullable SpannableConfiguration configuration, @Nullable String markdown) {
+ helper.setMarkdown(configuration, markdown);
+ }
+
+ @Nullable
+ @Override
+ public String getMarkdown() {
+ return helper.getMarkdown();
+ }
+}
diff --git a/library-view/src/main/java/ru/noties/markwon/view/MarkwonViewHelper.java b/library-view/src/main/java/ru/noties/markwon/view/MarkwonViewHelper.java
new file mode 100644
index 00000000..c8c813f6
--- /dev/null
+++ b/library-view/src/main/java/ru/noties/markwon/view/MarkwonViewHelper.java
@@ -0,0 +1,105 @@
+package ru.noties.markwon.view;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import ru.noties.markwon.Markwon;
+import ru.noties.markwon.SpannableConfiguration;
+
+public class MarkwonViewHelper implements IMarkwonView {
+
+ public static MarkwonViewHelper create(@NonNull V view) {
+ return new MarkwonViewHelper(view);
+ }
+
+ private final TextView textView;
+
+ private ConfigurationProvider provider;
+
+ private SpannableConfiguration configuration;
+ private String markdown;
+
+ private MarkwonViewHelper(@NonNull TextView textView) {
+ this.textView = textView;
+ }
+
+ public void init(Context context, AttributeSet attributeSet) {
+
+ if (attributeSet != null) {
+ final TypedArray array = context.obtainStyledAttributes(attributeSet, R.styleable.MarkwonView);
+ try {
+
+ final String configurationProvider = array.getString(R.styleable.MarkwonView_mv_configurationProvider);
+ final ConfigurationProvider provider;
+ if (!TextUtils.isEmpty(configurationProvider)) {
+ provider = MarkwonViewHelper.obtainProvider(configurationProvider);
+ } else {
+ provider = null;
+ }
+ if (provider != null) {
+ setConfigurationProvider(provider);
+ }
+
+ final String markdown = array.getString(R.styleable.MarkwonView_mv_markdown);
+ if (!TextUtils.isEmpty(markdown)) {
+ setMarkdown(markdown);
+ }
+ } finally {
+ array.recycle();
+ }
+ }
+ }
+
+ @Override
+ public void setConfigurationProvider(@NonNull ConfigurationProvider provider) {
+ this.provider = provider;
+ this.configuration = provider.provide(textView.getContext());
+ if (!TextUtils.isEmpty(markdown)) {
+ // invalidate rendered markdown
+ setMarkdown(markdown);
+ }
+ }
+
+ @Override
+ public void setMarkdown(@Nullable String markdown) {
+ setMarkdown(null, markdown);
+ }
+
+ @Override
+ public void setMarkdown(@Nullable SpannableConfiguration configuration, @Nullable String markdown) {
+ this.markdown = markdown;
+ if (configuration == null) {
+ if (this.configuration == null) {
+ if (provider != null) {
+ this.configuration = provider.provide(textView.getContext());
+ } else {
+ this.configuration = SpannableConfiguration.create(textView.getContext());
+ }
+ }
+ configuration = this.configuration;
+ }
+ Markwon.setMarkdown(textView, configuration, markdown);
+ }
+
+ @Nullable
+ @Override
+ public String getMarkdown() {
+ return markdown;
+ }
+
+ @Nullable
+ public static IMarkwonView.ConfigurationProvider obtainProvider(@NonNull String className) {
+ try {
+ final Class> cl = Class.forName(className);
+ return (IMarkwonView.ConfigurationProvider) cl.newInstance();
+ } catch (Throwable t) {
+ t.printStackTrace();
+ return null;
+ }
+ }
+}
diff --git a/library-view/src/main/res/values/attrs.xml b/library-view/src/main/res/values/attrs.xml
new file mode 100644
index 00000000..33a532f3
--- /dev/null
+++ b/library-view/src/main/res/values/attrs.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index f47f4744..59f2e834 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1 @@
-include ':app', ':library', ':library-image-loader'
+include ':app', ':library', ':library-image-loader', ':library-view'