cache;
+
+ SampleItemView(@NonNull Markwon markwon) {
+ this.markwon = markwon;
+ this.factory = new NoCopySpannableFactory();
+ this.cache = new EnumMap<>(SampleItem.class);
+ }
+
+ @NonNull
+ @Override
+ public SampleHolder createHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
+
+ final SampleHolder holder = new SampleHolder(inflater.inflate(
+ R.layout.adapt_sample_item,
+ parent,
+ false));
+
+ // set Spannable.Factory so when TextView will receive a new content
+ // it won't create new Spannable and copy all the spans but instead
+ // re-use existing Spannable thus improving performance
+ holder.textView.setSpannableFactory(factory);
+
+ return holder;
+ }
+
+ @Override
+ public void bindHolder(@NonNull SampleHolder holder, @NonNull SampleItem item) {
+
+ // retrieve an item from cache or create new one
+ // simple lazy loading pattern (cache on first call then re-use)
+ Spanned spanned = cache.get(item);
+ if (spanned == null) {
+ spanned = markwon.toMarkdown(context(holder).getString(item.textResId()));
+ cache.put(item, spanned);
+ }
+
+ holder.textView.setText(spanned);
+ }
+
+ static class SampleHolder extends Holder {
+
+ final TextView textView;
+
+ SampleHolder(@NonNull View view) {
+ super(view);
+
+ this.textView = requireView(R.id.text);
+ }
+ }
+
+ private static class NoCopySpannableFactory extends Spannable.Factory {
+ @Override
+ public Spannable newSpannable(CharSequence source) {
+ return source instanceof Spannable
+ ? (Spannable) source
+ : new SpannableString(source);
+ }
+ }
+}
diff --git a/sample/src/main/java/ru/noties/markwon/sample/core/CoreActivity.java b/sample/src/main/java/ru/noties/markwon/sample/core/CoreActivity.java
new file mode 100644
index 00000000..79687390
--- /dev/null
+++ b/sample/src/main/java/ru/noties/markwon/sample/core/CoreActivity.java
@@ -0,0 +1,208 @@
+package ru.noties.markwon.sample.core;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.Spanned;
+import android.text.style.ForegroundColorSpan;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import org.commonmark.node.Heading;
+import org.commonmark.node.Node;
+import org.commonmark.node.Paragraph;
+
+import ru.noties.markwon.AbstractMarkwonPlugin;
+import ru.noties.markwon.Markwon;
+import ru.noties.markwon.MarkwonPlugin;
+import ru.noties.markwon.MarkwonSpansFactory;
+import ru.noties.markwon.MarkwonVisitor;
+import ru.noties.markwon.core.CorePlugin;
+import ru.noties.markwon.core.MarkwonTheme;
+
+public class CoreActivity extends Activity {
+
+ private TextView textView;
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ textView = new TextView(this);
+ setContentView(textView);
+
+ step_1();
+
+ step_2();
+
+ step_3();
+
+ step_4();
+
+ step_5();
+
+ step_6();
+
+ step_7();
+ }
+
+ /**
+ * Create a simple instance of Markwon with only Core plugin registered
+ * this will handle all _natively_ supported by commonmark-java nodes:
+ *
+ * - StrongEmphasis
+ * - Emphasis
+ * - BlockQuote
+ * - Code
+ * - FencedCodeBlock
+ * - IndentedCodeBlock
+ * - ListItem (bullet-list and ordered list
+ * - Heading
+ * - Link
+ * - ThematicBreak
+ * - Paragraph (please note that there is no default span for a paragraph registered)
+ *
+ *
+ * and basic core functionality:
+ *
+ * - Append text
+ * - Insert new lines (soft and hard breaks)
+ *
+ */
+ private void step_1() {
+
+ // short call
+ final Markwon markwon = Markwon.create(this);
+
+ // this is the same as calling
+ final Markwon markwon2 = Markwon.builder(this)
+ .usePlugin(CorePlugin.create())
+ .build();
+ }
+
+ /**
+ * To simply apply raw (non-parsed) markdown call {@link Markwon#setMarkdown(TextView, String)}
+ */
+ private void step_2() {
+
+ // this is raw markdown
+ final String markdown = "Hello **markdown**!";
+
+ final Markwon markwon = Markwon.create(this);
+
+ // this will parse raw markdown and set parsed content to specified TextView
+ markwon.setMarkdown(textView, markdown);
+ }
+
+ /**
+ * To apply markdown in a different context (other than textView) use {@link Markwon#toMarkdown(String)}
+ *
+ * Please note that some features won't work unless they are used in a TextView context. For example
+ * there might be misplaced ordered lists (ordered list must have TextPaint in order to properly measure
+ * its number). But also images and tables (they belong to independent modules now). Images and tables
+ * are using some work-arounds in order to be displayed in relatively limited context without proper way
+ * of invalidation. But if a Toast for example is created with a custom view
+ * ({@code new Toast(this).setView(...) }) and has access to a TextView everything should work.
+ */
+ private void step_3() {
+
+ final String markdown = "*Toast* __here__!\n\n> And a quote!";
+
+ final Markwon markwon = Markwon.create(this);
+
+ final Spanned spanned = markwon.toMarkdown(markdown);
+
+ Toast.makeText(this, spanned, Toast.LENGTH_LONG).show();
+ }
+
+ /**
+ * To apply already parsed markdown use {@link Markwon#setParsedMarkdown(TextView, Spanned)}
+ */
+ private void step_4() {
+
+ final String markdown = "This **is** pre-parsed [markdown](#)";
+
+ final Markwon markwon = Markwon.create(this);
+
+ // parse markdown to obtain a Node
+ final Node node = markwon.parse(markdown);
+
+ // create a spanned content from parsed node
+ final Spanned spanned = markwon.render(node);
+
+ // apply parsed markdown
+ markwon.setParsedMarkdown(textView, spanned);
+ }
+
+ /**
+ * In order to apply paragraph spans a custom plugin should be created (CorePlugin will take care
+ * of everything else).
+ *
+ * Please note that when a plugin is registered and it depends on CorePlugin, there is no
+ * need to explicitly specify it. By default all plugins that extend AbstractMarkwonPlugin do declare
+ * it\'s dependency on CorePlugin ({@link MarkwonPlugin#priority()}).
+ *
+ * Order in which plugins are specified to the builder is of little importance as long as each
+ * plugin clearly states what dependencies it has
+ */
+ private void step_5() {
+
+ final String markdown = "# Hello!\n\nA paragraph?\n\nIt should be!";
+
+ final Markwon markwon = Markwon.builder(this)
+ .usePlugin(new AbstractMarkwonPlugin() {
+ @Override
+ public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) {
+ builder.setFactory(Paragraph.class, (configuration, props) ->
+ new ForegroundColorSpan(Color.GREEN));
+ }
+ })
+ .build();
+
+ markwon.setMarkdown(textView, markdown);
+ }
+
+ /**
+ * To disable some nodes from rendering another custom plugin can be used
+ */
+ private void step_6() {
+
+ final String markdown = "# Heading 1\n\n## Heading 2\n\n**other** content [here](#)";
+
+ final Markwon markwon = Markwon.builder(this)
+ .usePlugin(new AbstractMarkwonPlugin() {
+ @Override
+ public void configureVisitor(@NonNull MarkwonVisitor.Builder builder) {
+ // for example to disable rendering of heading:
+ // try commenting this out to see that otherwise headings will be rendered
+ builder.on(Heading.class, null);
+ }
+ })
+ .build();
+
+ markwon.setMarkdown(textView, markdown);
+ }
+
+ /**
+ * To customize core theme plugins can be used again
+ */
+ private void step_7() {
+
+ final String markdown = "`A code` that is rendered differently\n\n```\nHello!\n```";
+
+ final Markwon markwon = Markwon.builder(this)
+ .usePlugin(new AbstractMarkwonPlugin() {
+ @Override
+ public void configureTheme(@NonNull MarkwonTheme.Builder builder) {
+ builder
+ .codeBackgroundColor(Color.BLACK)
+ .codeTextColor(Color.RED);
+ }
+ })
+ .build();
+
+ markwon.setMarkdown(textView, markdown);
+ }
+}
diff --git a/sample/src/main/java/ru/noties/markwon/sample/customextension/CustomExtensionActivity.java b/sample/src/main/java/ru/noties/markwon/sample/customextension/CustomExtensionActivity.java
new file mode 100644
index 00000000..1b35c0c7
--- /dev/null
+++ b/sample/src/main/java/ru/noties/markwon/sample/customextension/CustomExtensionActivity.java
@@ -0,0 +1,6 @@
+package ru.noties.markwon.sample.customextension;
+
+import android.app.Activity;
+
+public class CustomExtensionActivity extends Activity {
+}
diff --git a/sample/src/main/java/ru/noties/markwon/sample/latex/LatexActivity.java b/sample/src/main/java/ru/noties/markwon/sample/latex/LatexActivity.java
new file mode 100644
index 00000000..179113ea
--- /dev/null
+++ b/sample/src/main/java/ru/noties/markwon/sample/latex/LatexActivity.java
@@ -0,0 +1,15 @@
+package ru.noties.markwon.sample.latex;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+
+public class LatexActivity extends Activity {
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+
+ }
+}
diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml
new file mode 100644
index 00000000..26bc9906
--- /dev/null
+++ b/sample/src/main/res/layout/activity_main.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/layout/adapt_sample_item.xml b/sample/src/main/res/layout/adapt_sample_item.xml
new file mode 100644
index 00000000..3dfe4e21
--- /dev/null
+++ b/sample/src/main/res/layout/adapt_sample_item.xml
@@ -0,0 +1,13 @@
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/values-v21/styles.xml b/sample/src/main/res/values-v21/styles.xml
new file mode 100644
index 00000000..191be162
--- /dev/null
+++ b/sample/src/main/res/values-v21/styles.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/sample/src/main/res/values/strings-samples.xml b/sample/src/main/res/values/strings-samples.xml
new file mode 100644
index 00000000..230c3557
--- /dev/null
+++ b/sample/src/main/res/values/strings-samples.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+ # \# Core\n\nSimple usage example
+ # \# LaTeX\n\nShows how to display a **LaTeX** formula in a Markwon powered application
+ # \# Custom extension\n\nShows how to create a custom extension to display an icon referenced in markdown as `@ic-android-black-24`
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml
index 9785e0c9..76d400a3 100644
--- a/sample/src/main/res/values/styles.xml
+++ b/sample/src/main/res/values/styles.xml
@@ -1,8 +1,7 @@
-
-
+
+
+
diff --git a/sample/src/test/java/ru/noties/markwon/sample/MainActivityTest.java b/sample/src/test/java/ru/noties/markwon/sample/MainActivityTest.java
new file mode 100644
index 00000000..dec6b07e
--- /dev/null
+++ b/sample/src/test/java/ru/noties/markwon/sample/MainActivityTest.java
@@ -0,0 +1,25 @@
+package ru.noties.markwon.sample;
+
+import android.content.Context;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import static org.junit.Assert.assertNotNull;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class MainActivityTest {
+
+ @Test
+ public void all_sample_items_have_activity_associated() {
+ final Context context = RuntimeEnvironment.application;
+ for (SampleItem item : SampleItem.values()) {
+ // we assert as not null, but in case of an error this method should throw
+ assertNotNull(MainActivity.sampleItemIntent(context, item));
+ }
+ }
+}
\ No newline at end of file