From bf5a8142f5a12cda90fd6e0179466aa74da4b068 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Mon, 23 Oct 2017 15:35:05 +0300 Subject: [PATCH] Task lists implementation --- README.md | 8 +- docs/SpannableTheme.md | 10 ++ library-task-list/build.gradle | 10 -- library/build.gradle | 2 - .../main/java/ru/noties/markwon/Markwon.java | 2 +- .../renderer/SpannableMarkdownVisitor.java | 16 +++- .../markwon/renderer/SpannableRenderer.java | 9 +- .../noties/markwon/spans/SpannableTheme.java | 24 ++--- .../markwon/spans/TaskListDrawable.java | 2 +- .../ru/noties/markwon/spans/TaskListSpan.java | 9 +- .../markwon/tasklist/TaskListBlock.java | 0 .../markwon/tasklist/TaskListBlockParser.java | 96 +++++++++---------- .../markwon/tasklist/TaskListExtension.java | 0 .../noties/markwon/tasklist/TaskListItem.java | 11 +++ settings.gradle | 2 +- 15 files changed, 112 insertions(+), 89 deletions(-) delete mode 100644 library-task-list/build.gradle rename {library-task-list => library}/src/main/java/ru/noties/markwon/tasklist/TaskListBlock.java (100%) rename {library-task-list => library}/src/main/java/ru/noties/markwon/tasklist/TaskListBlockParser.java (61%) rename {library-task-list => library}/src/main/java/ru/noties/markwon/tasklist/TaskListExtension.java (100%) rename {library-task-list => library}/src/main/java/ru/noties/markwon/tasklist/TaskListItem.java (58%) diff --git a/README.md b/README.md index 3e0d5383..1e33a9f4 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,6 @@ [![markwon-image-loader](https://img.shields.io/maven-central/v/ru.noties/markwon-image-loader.svg?label=markwon-image-loader)](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%markwon-image-loader%22) [![markwon-view](https://img.shields.io/maven-central/v/ru.noties/markwon-view.svg?label=markwon-view)](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%markwon-view%22) -- [ ] one **one** _one_ -- [X] ~~two~~ - **Markwon** is a library for Android that renders markdown as system-native Spannables. It gives ability to display markdown in all TextView widgets (**TextView**, **Button**, **Switch**, **CheckBox**, etc), **Notifications**, **Toasts**, etc. **No WebView is required**. Library provides reasonable defaults for display style of markdown but also gives all the means to tweak the appearance if desired. All markdown features are supported (including limited support for inlined HTML code, markdown tables and images). **This file is displayed by default in the [sample-apk] application. Which is a generic markdown viewer with support to display markdown via `http`, `https` & `file` schemes and 2 themes included: Light & Dark* @@ -41,6 +38,11 @@ compile 'ru.noties:markwon-view:1.0.0' // optional * * Underline (``) * * Strike-through (``, ``, ``) * other inline html is rendered via (`Html.fromHtml(...)`) +* Task lists: + +- [ ] Not _done_ + - [X] **Done** with `X` + - [x] ~~and~~ **or** small `x` --- diff --git a/docs/SpannableTheme.md b/docs/SpannableTheme.md index 4096ce34..b4fd7dee 100644 --- a/docs/SpannableTheme.md +++ b/docs/SpannableTheme.md @@ -105,6 +105,16 @@ public Builder tableBorderWidth(@Dimension int tableBorderWidth); public Builder tableOddRowBackgroundColor(@ColorInt int tableOddRowBackgroundColor); ``` +#### Task lists + +Task lists are supported but with some limitations. First of all, task list cannot be nested +(in a list, quote, etc). By default (if used factory method `builderWithDefaults`) TaskListDrawable +will be used with `linkColor` as the primary color and `windowBackground` as the checkMarkColor. + +```java +public Builder taskListDrawable(@NonNull Drawable taskListDrawable); +``` + ### Contents diff --git a/library-task-list/build.gradle b/library-task-list/build.gradle deleted file mode 100644 index 094d9248..00000000 --- a/library-task-list/build.gradle +++ /dev/null @@ -1,10 +0,0 @@ -apply plugin: 'java' - -sourceCompatibility = "1.7" -targetCompatibility = "1.7" - -dependencies { - compile SUPPORT_ANNOTATIONS - compile COMMON_MARK - compile 'ru.noties:debug:3.0.0@jar' -} diff --git a/library/build.gradle b/library/build.gradle index aba4fc62..c384c1b6 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -18,8 +18,6 @@ dependencies { compile COMMON_MARK compile COMMON_MARK_STRIKETHROUGHT compile COMMON_MARK_TABLE - - compile project(':library-task-list') } if (project.hasProperty('release')) { diff --git a/library/src/main/java/ru/noties/markwon/Markwon.java b/library/src/main/java/ru/noties/markwon/Markwon.java index 9f74ebbd..f2daf964 100644 --- a/library/src/main/java/ru/noties/markwon/Markwon.java +++ b/library/src/main/java/ru/noties/markwon/Markwon.java @@ -17,7 +17,7 @@ import java.util.Arrays; import ru.noties.markwon.renderer.SpannableRenderer; import ru.noties.markwon.tasklist.TaskListExtension; -@SuppressWarnings("WeakerAccess") +@SuppressWarnings({"WeakerAccess", "unused"}) public abstract class Markwon { /** diff --git a/library/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java b/library/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java index a9c00690..146769bc 100644 --- a/library/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java +++ b/library/src/main/java/ru/noties/markwon/renderer/SpannableMarkdownVisitor.java @@ -299,11 +299,25 @@ public class SpannableMarkdownVisitor extends AbstractVisitor { if (customNode instanceof TaskListItem) { + final TaskListItem listItem = (TaskListItem) customNode; + final int length = builder.length(); + + blockQuoteIndent += listItem.indent(); + visitChildren(customNode); - setSpan(length, new TaskListSpan(configuration.theme(), blockQuoteIndent, length, ((TaskListItem) customNode).done())); + + setSpan(length, new TaskListSpan( + configuration.theme(), + blockQuoteIndent, + length, + listItem.done() + )); + newLine(); + blockQuoteIndent -= listItem.indent(); + } else { super.visit(customNode); } diff --git a/library/src/main/java/ru/noties/markwon/renderer/SpannableRenderer.java b/library/src/main/java/ru/noties/markwon/renderer/SpannableRenderer.java index e20d0b63..25073964 100644 --- a/library/src/main/java/ru/noties/markwon/renderer/SpannableRenderer.java +++ b/library/src/main/java/ru/noties/markwon/renderer/SpannableRenderer.java @@ -8,16 +8,9 @@ import org.commonmark.node.Node; import ru.noties.markwon.SpannableConfiguration; -// please note that this class does not implement Renderer in order to return CharSequence (instead of String) public class SpannableRenderer { - // todo - // * LinkDrawableSpan, that draws link whilst image is still loading (it must be clickable...) - // * Common interface for images (in markdown & inline-html) - // * util method to properly copy markdown (with lists/links, etc) - // * util to apply empty line height - // * transform relative urls to absolute ones... - + @Nullable public CharSequence render(@NonNull SpannableConfiguration configuration, @Nullable Node node) { final CharSequence out; if (node == null) { diff --git a/library/src/main/java/ru/noties/markwon/spans/SpannableTheme.java b/library/src/main/java/ru/noties/markwon/spans/SpannableTheme.java index 63d8b69b..8801ce8f 100644 --- a/library/src/main/java/ru/noties/markwon/spans/SpannableTheme.java +++ b/library/src/main/java/ru/noties/markwon/spans/SpannableTheme.java @@ -11,22 +11,13 @@ import android.support.annotation.Dimension; import android.support.annotation.FloatRange; import android.support.annotation.IntRange; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.text.TextPaint; import android.util.TypedValue; @SuppressWarnings("WeakerAccess") public class SpannableTheme { -// -// // this method should be used if TextView is known beforehand -// // it will correctly measure the `space` char and set it as `codeMultilineMargin` -// // otherwise this value must be set explicitly -// public static SpannableTheme create(@NonNull TextView textView) { -// return builderWithDefaults(textView.getContext()) -// .codeMultilineMargin((int) (textView.getPaint().measureText("\u00a0") + .5F)) -// .build(); -// } - // this create default theme (except for `codeMultilineMargin` property) public static SpannableTheme create(@NonNull Context context) { return builderWithDefaults(context).build(); } @@ -41,6 +32,8 @@ public class SpannableTheme { public static Builder builderWithDefaults(@NonNull Context context) { + // by default we will be using link color for the checkbox color + // & window background as a checkMark color final int linkColor = resolve(context, android.R.attr.textColorLink); final int backgroundColor = resolve(context, android.R.attr.colorBackground); @@ -153,6 +146,8 @@ public class SpannableTheme { // by default paint.color * TABLE_ODD_ROW_DEF_ALPHA protected final int tableOddRowBackgroundColor; + // drawable that will be used to render checkbox (should be stateful) + // TaskListDrawable can be used protected final Drawable taskListDrawable; protected SpannableTheme(@NonNull Builder builder) { @@ -252,10 +247,16 @@ public class SpannableTheme { // custom typeface was set if (codeTypeface != null) { + paint.setTypeface(codeTypeface); + + // please note that we won't be calculating textSize + // (like we do when no Typeface is provided), if it's some specific typeface + // we would confuse users about textSize if (codeTextSize != 0) { paint.setTextSize(codeTextSize); } + } else { paint.setTypeface(Typeface.MONOSPACE); final float textSize; @@ -372,7 +373,7 @@ public class SpannableTheme { paint.setStyle(Paint.Style.FILL); } - @NonNull + @Nullable public Drawable getTaskListDrawable() { return taskListDrawable; } @@ -546,6 +547,7 @@ public class SpannableTheme { } private static class Dip { + private final float density; Dip(@NonNull Context context) { diff --git a/library/src/main/java/ru/noties/markwon/spans/TaskListDrawable.java b/library/src/main/java/ru/noties/markwon/spans/TaskListDrawable.java index f53931a1..78834aac 100644 --- a/library/src/main/java/ru/noties/markwon/spans/TaskListDrawable.java +++ b/library/src/main/java/ru/noties/markwon/spans/TaskListDrawable.java @@ -46,7 +46,7 @@ public class TaskListDrawable extends Drawable { protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); - // we should exclude stroke with from final bounds (half of the strokeWidth from both sides) + // we should exclude stroke with from final bounds (half of the strokeWidth from all sides) // we should have square shape final float min = Math.min(bounds.width(), bounds.height()); diff --git a/library/src/main/java/ru/noties/markwon/spans/TaskListSpan.java b/library/src/main/java/ru/noties/markwon/spans/TaskListSpan.java index af647558..3af7e297 100644 --- a/library/src/main/java/ru/noties/markwon/spans/TaskListSpan.java +++ b/library/src/main/java/ru/noties/markwon/spans/TaskListSpan.java @@ -23,7 +23,7 @@ public class TaskListSpan implements LeadingMarginSpan { @Override public int getLeadingMargin(boolean first) { - return theme.getBlockMargin(); + return theme.getBlockMargin() * blockIndent; } @Override @@ -33,14 +33,17 @@ public class TaskListSpan implements LeadingMarginSpan { return; } + final Drawable drawable = theme.getTaskListDrawable(); + if (drawable == null) { + return; + } + final int save = c.save(); try { final int width = theme.getBlockMargin(); final int height = bottom - top; - final Drawable drawable = theme.getTaskListDrawable(); - final int w = (int) (width * .75F + .5F); final int h = (int) (height * .75F + .5F); diff --git a/library-task-list/src/main/java/ru/noties/markwon/tasklist/TaskListBlock.java b/library/src/main/java/ru/noties/markwon/tasklist/TaskListBlock.java similarity index 100% rename from library-task-list/src/main/java/ru/noties/markwon/tasklist/TaskListBlock.java rename to library/src/main/java/ru/noties/markwon/tasklist/TaskListBlock.java diff --git a/library-task-list/src/main/java/ru/noties/markwon/tasklist/TaskListBlockParser.java b/library/src/main/java/ru/noties/markwon/tasklist/TaskListBlockParser.java similarity index 61% rename from library-task-list/src/main/java/ru/noties/markwon/tasklist/TaskListBlockParser.java rename to library/src/main/java/ru/noties/markwon/tasklist/TaskListBlockParser.java index f6942fe5..27752168 100644 --- a/library-task-list/src/main/java/ru/noties/markwon/tasklist/TaskListBlockParser.java +++ b/library/src/main/java/ru/noties/markwon/tasklist/TaskListBlockParser.java @@ -17,20 +17,20 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import ru.noties.debug.Debug; - +@SuppressWarnings("WeakerAccess") class TaskListBlockParser extends AbstractBlockParser { private static final Pattern PATTERN = Pattern.compile("\\s*-\\s+\\[(x|X|\\s)\\]\\s+(.*)"); -// private static final Pattern PATTERN_2 = Pattern.compile("^\\s*-\\s+\\[(x|X|\\s)\\]\\s+(.*)"); private final TaskListBlock block = new TaskListBlock(); - private final List lines; + private final List items = new ArrayList<>(3); - TaskListBlockParser(@NonNull String startLine) { - this.lines = new ArrayList<>(3); - this.lines.add(startLine); + private int indent = 0; + + TaskListBlockParser(@NonNull String startLine, int startIndent) { + items.add(new Item(startLine, startIndent)); + indent = startIndent; } @Override @@ -45,16 +45,18 @@ class TaskListBlockParser extends AbstractBlockParser { final String line = line(parserState); -// Debug.i("line: %s, find: %s", line, PATTERN.matcher(line).find()); - Debug.i("isBlank: %s, line: `%s`", parserState.isBlank(), line); + final int currentIndent = parserState.getIndent(); + if (currentIndent > indent) { + indent += 2; + } else if (currentIndent < indent && indent > 1) { + indent -= 2; + } if (line != null && line.length() > 0 && PATTERN.matcher(line).matches()) { - Debug.e(); blockContinue = BlockContinue.atIndex(parserState.getIndex()); } else { - Debug.e(); blockContinue = BlockContinue.finished(); } @@ -63,54 +65,29 @@ class TaskListBlockParser extends AbstractBlockParser { @Override public void addLine(CharSequence line) { - Debug.i("line: %s", line); - if (line != null - && line.length() > 0) { - lines.add(line.toString()); + if (length(line) > 0) { + items.add(new Item(line.toString(), indent)); } } @Override public void parseInlines(InlineParser inlineParser) { - Debug.i(lines); - Matcher matcher; - TaskListItem item; - - for (String line : lines) { - - matcher = PATTERN.matcher(line); + TaskListItem listItem; + for (Item item : items) { + matcher = PATTERN.matcher(item.line); if (!matcher.matches()) { continue; } - - item = new TaskListItem().done(isDone(matcher.group(1))); - - inlineParser.parse(matcher.group(2), item); - - block.appendChild(item); + listItem = new TaskListItem() + .done(isDone(matcher.group(1))) + .indent(item.indent / 2); + inlineParser.parse(matcher.group(2), listItem); + block.appendChild(listItem); } - - } - - @Override - public boolean isContainer() { - return false; - } - - @Override - public boolean canContain(Block block) { - Debug.i("block: %s", block); - return false; - } - - @Override - public void closeBlock() { - Debug.e(block); - Debug.trace(); } static class Factory extends AbstractBlockParserFactory { @@ -124,8 +101,14 @@ class TaskListBlockParser extends AbstractBlockParser { && line.length() > 0 && PATTERN.matcher(line).matches()) { - return BlockStart.of(new TaskListBlockParser(line)) - .atIndex(state.getIndex() + line.length()); + final int length = line.length(); + final int index = state.getIndex(); + final int atIndex = index != 0 + ? index + (length - index) + : length; + + return BlockStart.of(new TaskListBlockParser(line, state.getIndent())) + .atIndex(atIndex); } return BlockStart.none(); @@ -140,8 +123,25 @@ class TaskListBlockParser extends AbstractBlockParser { : null; } + private static int length(@Nullable CharSequence text) { + return text != null + ? text.length() + : 0; + } + private static boolean isDone(@NonNull String value) { return "X".equals(value) || "x".equals(value); } + + private static class Item { + + final String line; + final int indent; + + Item(@NonNull String line, int indent) { + this.line = line; + this.indent = indent; + } + } } diff --git a/library-task-list/src/main/java/ru/noties/markwon/tasklist/TaskListExtension.java b/library/src/main/java/ru/noties/markwon/tasklist/TaskListExtension.java similarity index 100% rename from library-task-list/src/main/java/ru/noties/markwon/tasklist/TaskListExtension.java rename to library/src/main/java/ru/noties/markwon/tasklist/TaskListExtension.java diff --git a/library-task-list/src/main/java/ru/noties/markwon/tasklist/TaskListItem.java b/library/src/main/java/ru/noties/markwon/tasklist/TaskListItem.java similarity index 58% rename from library-task-list/src/main/java/ru/noties/markwon/tasklist/TaskListItem.java rename to library/src/main/java/ru/noties/markwon/tasklist/TaskListItem.java index cef9fcff..9904ee76 100644 --- a/library-task-list/src/main/java/ru/noties/markwon/tasklist/TaskListItem.java +++ b/library/src/main/java/ru/noties/markwon/tasklist/TaskListItem.java @@ -2,9 +2,11 @@ package ru.noties.markwon.tasklist; import org.commonmark.node.CustomNode; +@SuppressWarnings("WeakerAccess") public class TaskListItem extends CustomNode { private boolean done; + private int indent; public boolean done() { return done; @@ -14,4 +16,13 @@ public class TaskListItem extends CustomNode { this.done = done; return this; } + + public int indent() { + return indent; + } + + public TaskListItem indent(int indent) { + this.indent = indent; + return this; + } } diff --git a/settings.gradle b/settings.gradle index 4baa558b..59f2e834 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -include ':app', ':library', ':library-image-loader', ':library-view', ':library-task-list' +include ':app', ':library', ':library-image-loader', ':library-view'