diff --git a/markdown/src/main/java/it/niedermann/android/markdown/MarkdownUtil.java b/markdown/src/main/java/it/niedermann/android/markdown/MarkdownUtil.java index 6231308..7b6bdca 100644 --- a/markdown/src/main/java/it/niedermann/android/markdown/MarkdownUtil.java +++ b/markdown/src/main/java/it/niedermann/android/markdown/MarkdownUtil.java @@ -24,6 +24,7 @@ import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; +import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.Locale; @@ -35,6 +36,7 @@ import io.noties.markwon.Markwon; import it.niedermann.android.markdown.model.EListType; import it.niedermann.android.markdown.model.SearchSpan; +import it.niedermann.android.markdown.remoteviews.RemoteViewElement; @SuppressWarnings("OptionalUsedAsFieldOrParameterType") public class MarkdownUtil { @@ -58,6 +60,71 @@ private MarkdownUtil() { // Util class } + + /** + * {@link RemoteView}s have a limited subset of supported classes to maintain compatibility with many different launchers. + *

+ * This function seperates render-able markdown into elements that can be displayed by a widget natively. + * Currently supported are: Markdowntext, Checkbox + * + * @return ArrayList List of Elements from the note. + */ + public static ArrayList getRenderedElementsForRemoteView(@NonNull Context context, @NonNull String content) { + + ArrayList remoteViews = new ArrayList<>(); + StringBuilder currentLineBlock = new StringBuilder(); + int startLine = 0; + + final String[] lines = content.split("\n"); + boolean isInFencedCodeBlock = false; + int fencedCodeBlockSigns = 0; + for (int i = 0; i < lines.length; i++) { + final var matcher = PATTERN_CODE_FENCE.matcher(lines[i]); + if (matcher.find()) { + final String fence = matcher.group(1); + if (fence != null) { + int currentFencedCodeBlockSigns = fence.length(); + if (isInFencedCodeBlock) { + if (currentFencedCodeBlockSigns == fencedCodeBlockSigns) { + isInFencedCodeBlock = false; + fencedCodeBlockSigns = 0; + } + } else { + isInFencedCodeBlock = true; + fencedCodeBlockSigns = currentFencedCodeBlockSigns; + } + } + } + if (!isInFencedCodeBlock) { + if (isCheckboxLine(lines[i])) { + // if the first line is a checkbox, this will be an empty markdown block. It will also end in line -1. + var endline = i-1; + if(endline<0) { + endline = 0; + } + remoteViews.add(new RemoteViewElement(RemoteViewElement.TYPE_TEXT, currentLineBlock.toString(), startLine, endline)); + currentLineBlock = new StringBuilder(); + startLine = i+1; + + boolean isChecked = false; + for (EListType listType : EListType.values()) { + if(lineStartsWithCheckedCheckbox(lines[i], listType)) { + isChecked = true; + } + } + if(isChecked) { + remoteViews.add(new RemoteViewElement(RemoteViewElement.TYPE_CHECKBOX_CHECKED, lines[i], i, i)); + } else { + remoteViews.add(new RemoteViewElement(RemoteViewElement.TYPE_CHECKBOX_UNCHECKED, lines[i], i, i)); + } + continue; + } + } + currentLineBlock.append(lines[i]).append("\n"); + } + return remoteViews; + } + /** * {@link RemoteView}s have a limited subset of supported classes to maintain compatibility with many different launchers. *

@@ -161,7 +228,7 @@ private static String runForEachCheckbox(@NonNull String markdownString, @NonNul } } if (!isInFencedCodeBlock) { - if (lineStartsWithCheckbox(lines[i]) && lines[i].trim().length() > EListType.DASH.checkboxChecked.length()) { + if (isCheckboxLine(lines[i])) { lines[i] = map.apply(lines[i]); } } @@ -169,6 +236,13 @@ private static String runForEachCheckbox(@NonNull String markdownString, @NonNul return TextUtils.join("\n", lines); } + public static boolean isCheckboxLine(String line) { + if (lineStartsWithCheckbox(line) && line.trim().length() > EListType.DASH.checkboxChecked.length()) { + return true; + } + return false; + } + public static int getStartOfLine(@NonNull CharSequence s, int cursorPosition) { int startOfLine = cursorPosition; while (startOfLine > 0 && s.charAt(startOfLine - 1) != '\n') { @@ -256,10 +330,21 @@ public static boolean lineStartsWithCheckbox(@NonNull String line) { } public static boolean lineStartsWithCheckbox(@NonNull String line, @NonNull EListType listType) { + return lineStartsWithCheckedCheckbox(line, listType) | lineStartsWithUncheckedCheckbox(line, listType); + } + + public static boolean lineStartsWithCheckedCheckbox(@NonNull String line, @NonNull EListType listType) { + final String trimmedLine = line.trim(); + return (trimmedLine.startsWith(listType.checkboxChecked) || trimmedLine.startsWith(listType.checkboxCheckedUpperCase)); + } + + public static boolean lineStartsWithUncheckedCheckbox(@NonNull String line, @NonNull EListType listType) { final String trimmedLine = line.trim(); - return (trimmedLine.startsWith(listType.checkboxUnchecked) || trimmedLine.startsWith(listType.checkboxChecked) || trimmedLine.startsWith(listType.checkboxCheckedUpperCase)); + return (trimmedLine.startsWith(listType.checkboxUnchecked)); } + + /** * @return the number of the ordered list item if the line is an ordered list, otherwise -1. */ diff --git a/markdown/src/main/java/it/niedermann/android/markdown/remoteviews/RemoteViewElement.kt b/markdown/src/main/java/it/niedermann/android/markdown/remoteviews/RemoteViewElement.kt new file mode 100644 index 0000000..6af2d1b --- /dev/null +++ b/markdown/src/main/java/it/niedermann/android/markdown/remoteviews/RemoteViewElement.kt @@ -0,0 +1,36 @@ +package it.niedermann.android.markdown.remoteviews + +import java.util.ArrayList + +class RemoteViewElement( + val type: Int, + val currentLineBlock: String, + val blockStartsInLine: Int, + val blockEndsInLine: Int +) { + + companion object { + const val TYPE_TEXT = 0 + const val TYPE_CHECKBOX_CHECKED = 1 + const val TYPE_CHECKBOX_UNCHECKED = 2 + } + + override fun toString(): String { + var elements = StringBuilder() + + if(type == TYPE_TEXT) { + elements.append("TYPE_TEXT").append("\n") + } + if(type == TYPE_CHECKBOX_CHECKED) { + elements.append("TYPE_CHECKBOX_CHECKED").append("\n") + } + if(type == TYPE_CHECKBOX_UNCHECKED) { + elements.append("TYPE_CHECKBOX_UNCHECKED").append("\n") + } + + elements.append("Content:").append("\n") + elements.append(currentLineBlock).append("\n") + elements.append("Started from $blockStartsInLine to $blockEndsInLine").append("\n") + return elements.toString() + } +} \ No newline at end of file