diff options
author | Stefan Niedermann <info@niedermann.it> | 2020-12-15 17:43:44 +0300 |
---|---|---|
committer | Niedermann IT-Dienstleistungen <stefan-niedermann@users.noreply.github.com> | 2020-12-17 18:14:29 +0300 |
commit | 6b4e57da9cc62c06511fc7a01b1c914b2a3144a0 (patch) | |
tree | 1d91323ababce89cdfb77cc31ecb67320df219df | |
parent | c3a3ca561987f3eefbc8de8165192c2715c59714 (diff) |
Enhance context based formatting
Signed-off-by: Stefan Niedermann <info@niedermann.it>
17 files changed, 525 insertions, 74 deletions
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/util/CommentsUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/util/CommentsUtil.java index 5251291c8..8fa24988d 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/util/CommentsUtil.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/util/CommentsUtil.java @@ -1,12 +1,14 @@ package it.niedermann.nextcloud.deck.ui.card.comments.util; +import androidx.annotation.Nullable; import androidx.core.util.Pair; public class CommentsUtil { + @Nullable public static Pair<String, Integer> getUserNameForMentionProposal(String text, int cursorPosition) { - Pair result = null; + Pair<String, Integer> result = null; if (text != null) { // find start of relevant substring diff --git a/app/src/test/java/it/niedermann/nextcloud/deck/CommentsUtilTest.java b/app/src/test/java/it/niedermann/nextcloud/deck/CommentsUtilTest.java index 47a23392e..bc76207e0 100644 --- a/app/src/test/java/it/niedermann/nextcloud/deck/CommentsUtilTest.java +++ b/app/src/test/java/it/niedermann/nextcloud/deck/CommentsUtilTest.java @@ -7,6 +7,7 @@ import it.niedermann.nextcloud.deck.ui.card.comments.util.CommentsUtil; public class CommentsUtilTest { + @SuppressWarnings("ConstantConditions") @Test public void testMentionDiscovery() { Assert.assertNull(CommentsUtil.getUserNameForMentionProposal("", 0)); diff --git a/markdown/build.gradle b/markdown/build.gradle index c30c18a5a..d4ba32ef6 100644 --- a/markdown/build.gradle +++ b/markdown/build.gradle @@ -32,6 +32,7 @@ android { dependencies { implementation 'com.github.nextcloud:Android-SingleSignOn:0.5.4' + implementation 'com.github.stefan-niedermann:android-commons:0.1.1' implementation 'androidx.appcompat:appcompat:1.2.0' implementation "androidx.lifecycle:lifecycle-livedata:2.2.0" diff --git a/markdown/src/androidTest/java/it/niedermann/android/markdown/ExampleInstrumentedTest.java b/markdown/src/androidTest/java/it/niedermann/android/markdown/ExampleInstrumentedTest.java deleted file mode 100644 index e765e0663..000000000 --- a/markdown/src/androidTest/java/it/niedermann/android/markdown/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package it.niedermann.android.markdown; - -import android.content.Context; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.platform.app.InstrumentationRegistry; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.assertEquals; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - assertEquals("it.niedermann.android.markdown.test", appContext.getPackageName()); - } -}
\ No newline at end of file diff --git a/markdown/src/androidTest/java/it/niedermann/android/markdown/MarkwonMarkdownUtilTest.java b/markdown/src/androidTest/java/it/niedermann/android/markdown/MarkwonMarkdownUtilTest.java new file mode 100644 index 000000000..b20f7fd36 --- /dev/null +++ b/markdown/src/androidTest/java/it/niedermann/android/markdown/MarkwonMarkdownUtilTest.java @@ -0,0 +1,112 @@ +package it.niedermann.android.markdown; + +import junit.framework.TestCase; + +import java.util.HashMap; +import java.util.Map; + +import it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil; + +/** + * Tests the NoteUtil + * Created by stefan on 06.10.15. + */ +public class MarkwonMarkdownUtilTest extends TestCase { + + public void testGetStartOfLine() { + //language=md + StringBuilder test = new StringBuilder( + "# Test-Note\n" + // line start 0 + "\n" + // line start 12 + "- [ ] this is a test note\n" + // line start 13 + "- [x] test\n" + // line start 39 + "[test](https://example.com)\n" + // line start 50 + "\n" + // line start 77 + "\n" // line start 78 + ); + + for (int i = 0; i < test.length(); i++) { + int startOfLine = MarkwonMarkdownUtil.getStartOfLine(test, i); + if (i <= 11) { + assertEquals(0, startOfLine); + } else if (i <= 12) { + assertEquals(12, startOfLine); + } else if (i <= 38) { + assertEquals(13, startOfLine); + } else if (i <= 49) { + assertEquals(39, startOfLine); + } else if (i <= 77) { + assertEquals(50, startOfLine); + } else if (i <= 78) { + assertEquals(78, startOfLine); + } else if (i <= 79) { + assertEquals(79, startOfLine); + } + } + } + + public void testGetEndOfLine() { + //language=md + StringBuilder test = new StringBuilder( + "# Test-Note\n" + // line 0 - 11 + "\n" + // line 12 - 12 + "- [ ] this is a test note\n" + // line 13 - 38 + "- [x] test\n" + // line start 39 - 49 + "[test](https://example.com)\n" + // line 50 - 77 + "\n" + // line 77 - 78 + "\n" // line 78 - 79 + ); + + for (int i = 0; i < test.length(); i++) { + int endOfLine = MarkwonMarkdownUtil.getEndOfLine(test, i); + if (i <= 11) { + assertEquals(11, endOfLine); + } else if (i <= 12) { + assertEquals(12, endOfLine); + } else if (i <= 38) { + assertEquals(38, endOfLine); + } else if (i <= 49) { + assertEquals(49, endOfLine); + } else if (i <= 77) { + assertEquals(77, endOfLine); + } else if (i <= 78) { + assertEquals(78, endOfLine); + } else if (i <= 79) { + assertEquals(79, endOfLine); + } + } + } + + public void testLineStartsWithCheckbox() { + Map<String, Boolean> lines = new HashMap<>(); + lines.put("- [ ] ", true); + lines.put("- [x] ", true); + lines.put("* [ ] ", true); + lines.put("* [x] ", true); + lines.put("- [ ]", true); + lines.put("- [x]", true); + lines.put("* [ ]", true); + lines.put("* [x]", true); + + lines.put("-[ ] ", false); + lines.put("-[x] ", false); + lines.put("*[ ] ", false); + lines.put("*[x] ", false); + lines.put("-[ ]", false); + lines.put("-[x]", false); + lines.put("*[ ]", false); + lines.put("*[x]", false); + + lines.put("- [] ", false); + lines.put("* [] ", false); + lines.put("- []", false); + lines.put("* []", false); + + lines.put("-[] ", false); + lines.put("*[] ", false); + lines.put("-[]", false); + lines.put("*[]", false); + + lines.forEach((key, value) -> assertEquals(value, (Boolean) MarkwonMarkdownUtil.lineStartsWithCheckbox(key))); + } +}
\ No newline at end of file diff --git a/markdown/src/main/java/it/niedermann/android/markdown/markwon/MarkwonMarkdownEditor.java b/markdown/src/main/java/it/niedermann/android/markdown/markwon/MarkwonMarkdownEditor.java index 7dd73a94b..345a75347 100644 --- a/markdown/src/main/java/it/niedermann/android/markdown/markwon/MarkwonMarkdownEditor.java +++ b/markdown/src/main/java/it/niedermann/android/markdown/markwon/MarkwonMarkdownEditor.java @@ -1,6 +1,7 @@ package it.niedermann.android.markdown.markwon; import android.content.Context; +import android.os.Build; import android.util.AttributeSet; import android.widget.EditText; @@ -15,6 +16,8 @@ import io.noties.markwon.editor.MarkwonEditor; import io.noties.markwon.editor.handler.EmphasisEditHandler; import io.noties.markwon.editor.handler.StrongEmphasisEditHandler; import it.niedermann.android.markdown.MarkdownEditor; +import it.niedermann.android.markdown.markwon.format.ContextBasedFormattingCallback; +import it.niedermann.android.markdown.markwon.format.ContextBasedRangeFormattingCallback; import it.niedermann.android.markdown.markwon.handler.BlockQuoteEditHandler; import it.niedermann.android.markdown.markwon.handler.CodeBlockEditHandler; import it.niedermann.android.markdown.markwon.handler.CodeEditHandler; @@ -47,6 +50,10 @@ public class MarkwonMarkdownEditor extends AppCompatEditText implements Markdown .useEditHandler(new HeadingEditHandler()) .build(); addTextChangedListener(new AutoContinuationTextWatcher(editor, this)); + setCustomSelectionActionModeCallback(new ContextBasedRangeFormattingCallback(this)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + setCustomInsertionActionModeCallback(new ContextBasedFormattingCallback(this)); + } } @Override diff --git a/markdown/src/main/java/it/niedermann/android/markdown/markwon/MarkwonMarkdownUtil.java b/markdown/src/main/java/it/niedermann/android/markdown/markwon/MarkwonMarkdownUtil.java index d33401b47..fdaf405da 100644 --- a/markdown/src/main/java/it/niedermann/android/markdown/markwon/MarkwonMarkdownUtil.java +++ b/markdown/src/main/java/it/niedermann/android/markdown/markwon/MarkwonMarkdownUtil.java @@ -21,6 +21,7 @@ import io.noties.markwon.syntax.Prism4jThemeDefault; import io.noties.markwon.syntax.SyntaxHighlightPlugin; import io.noties.prism4j.Prism4j; import io.noties.prism4j.annotations.PrismBundle; +import it.niedermann.android.markdown.markwon.model.EListType; import it.niedermann.android.markdown.markwon.plugins.NextcloudMentionsPlugin; import it.niedermann.android.markdown.markwon.plugins.ThemePlugin; @@ -62,4 +63,48 @@ public class MarkwonMarkdownUtil { return initMarkwonViewer(context) .usePlugin(NextcloudMentionsPlugin.create(context, mentions)); } + + public static int getStartOfLine(@NonNull CharSequence s, int cursorPosition) { + int startOfLine = cursorPosition; + while (startOfLine > 0 && s.charAt(startOfLine - 1) != '\n') { + startOfLine--; + } + return startOfLine; + } + + public static int getEndOfLine(@NonNull CharSequence s, int cursorPosition) { + int nextLinebreak = s.toString().indexOf('\n', cursorPosition); + if (nextLinebreak > -1) { + return nextLinebreak; + } + return cursorPosition; + } + + public static String getListItemIfIsEmpty(@NonNull String line) { + for (EListType listType : EListType.values()) { + if (line.equals(listType.checkboxUncheckedWithTrailingSpace)) { + return listType.checkboxUncheckedWithTrailingSpace; + } else if (line.equals(listType.listSymbolWithTrailingSpace)) { + return listType.listSymbolWithTrailingSpace; + } + } + return null; + } + + public static boolean lineStartsWithCheckbox(@NonNull String line) { + for (EListType listType : EListType.values()) { + if (lineStartsWithCheckbox(line, listType)) { + return true; + } + } + return false; + } + + public static boolean lineStartsWithCheckbox(@NonNull String line, @NonNull EListType listType) { + return line.startsWith(listType.checkboxUnchecked) || line.startsWith(listType.checkboxChecked); + } + + public static boolean lineStartsWithList(@NonNull String line, @NonNull EListType listType) { + return line.startsWith(listType.listSymbol); + } } diff --git a/markdown/src/main/java/it/niedermann/android/markdown/markwon/format/ContextBasedFormattingCallback.java b/markdown/src/main/java/it/niedermann/android/markdown/markwon/format/ContextBasedFormattingCallback.java new file mode 100644 index 000000000..6bed4b1db --- /dev/null +++ b/markdown/src/main/java/it/niedermann/android/markdown/markwon/format/ContextBasedFormattingCallback.java @@ -0,0 +1,127 @@ +package it.niedermann.android.markdown.markwon.format; + +import android.text.SpannableStringBuilder; +import android.text.TextUtils; +import android.util.Log; +import android.view.ActionMode; +import android.view.Menu; +import android.view.MenuItem; + +import it.niedermann.android.markdown.R; +import it.niedermann.android.markdown.markwon.MarkwonMarkdownEditor; +import it.niedermann.android.markdown.markwon.model.EListType; +import it.niedermann.android.util.ClipboardUtil; + +import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.getEndOfLine; +import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.getStartOfLine; +import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.lineStartsWithCheckbox; + +public class ContextBasedFormattingCallback implements ActionMode.Callback { + + private static final String TAG = ContextBasedFormattingCallback.class.getSimpleName(); + + private final MarkwonMarkdownEditor editText; + + public ContextBasedFormattingCallback(MarkwonMarkdownEditor editText) { + this.editText = editText; + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + mode.getMenuInflater().inflate(R.menu.context_based_formatting, menu); + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + final CharSequence text = editText.getText(); + if (text != null) { + final int cursorPosition = editText.getSelectionStart(); + if (cursorPosition >= 0 && cursorPosition <= text.length()) { + final int startOfLine = getStartOfLine(text, cursorPosition); + final int endOfLine = getEndOfLine(text, startOfLine); + final String line = text.subSequence(startOfLine, endOfLine).toString(); + if (lineStartsWithCheckbox(line)) { + menu.findItem(R.id.checkbox).setVisible(false); + Log.i(TAG, "Hide checkbox menu item because line starts already with checkbox"); + } + } else { + Log.e(TAG, "SelectionStart is " + cursorPosition + ". Expected to be between 0 and " + text.length()); + } + } + return false; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + int itemId = item.getItemId(); + if (itemId == R.id.checkbox) { + insertCheckbox(); + return true; + } else if (itemId == R.id.link) { + insertLink(); + return true; + } + return false; + } + + private void insertCheckbox() { + final CharSequence text = editText.getText(); + if (text == null) { + editText.setMarkdownString(EListType.DASH.checkboxUncheckedWithTrailingSpace); + editText.setSelection(EListType.DASH.checkboxUncheckedWithTrailingSpace.length()); + } else { + final int originalCursorPosition = editText.getSelectionStart(); + final int startOfLine = getStartOfLine(text, originalCursorPosition); + Log.i(TAG, "Inserting checkbox at position " + startOfLine); + final CharSequence part1 = text.subSequence(0, startOfLine); + final CharSequence part2 = text.subSequence(startOfLine, text.length()); + editText.setMarkdownString(TextUtils.concat(part1, EListType.DASH.checkboxUncheckedWithTrailingSpace, part2)); + editText.setSelection(originalCursorPosition + EListType.DASH.checkboxUncheckedWithTrailingSpace.length()); + } + } + + private void insertLink() { + final CharSequence text = editText.getText(); + final SpannableStringBuilder ssb = new SpannableStringBuilder(text); + final boolean textToFormatIsLink; + final int start; + int end; + if (text == null) { + start = end = 0; + textToFormatIsLink = false; + } else { + start = text.length(); + end = start; + textToFormatIsLink = TextUtils.indexOf(text.subSequence(start, end), "http") == 0; + } + + if (textToFormatIsLink) { + Log.i(TAG, "Inserting link description for position " + start + " to " + end); + ssb.insert(end, ")"); + ssb.insert(start, "[]("); + } else { + String clipboardURL = ClipboardUtil.INSTANCE.getClipboardURLorNull(editText.getContext()); + if (clipboardURL != null) { + Log.i(TAG, "Inserting link from clipboard at position " + start + " to " + end + ": " + clipboardURL); + ssb.insert(end, "](" + clipboardURL + ")"); + end += clipboardURL.length(); + } else { + Log.i(TAG, "Inserting empty link for position " + start + " to " + end); + ssb.insert(end, "]()"); + } + ssb.insert(start, "["); + } + editText.setMarkdownString(ssb); + if (textToFormatIsLink) { + editText.setSelection(start + 1); + } else { + editText.setSelection(end + 3); // after <end>]( + } + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + // Nothing to do here... + } +} diff --git a/markdown/src/main/java/it/niedermann/android/markdown/markwon/format/ContextBasedRangeFormattingCallback.java b/markdown/src/main/java/it/niedermann/android/markdown/markwon/format/ContextBasedRangeFormattingCallback.java new file mode 100644 index 000000000..20b26674d --- /dev/null +++ b/markdown/src/main/java/it/niedermann/android/markdown/markwon/format/ContextBasedRangeFormattingCallback.java @@ -0,0 +1,130 @@ +package it.niedermann.android.markdown.markwon.format; + +import android.graphics.Typeface; +import android.text.SpannableStringBuilder; +import android.text.TextUtils; +import android.text.style.StyleSpan; +import android.util.SparseIntArray; +import android.view.ActionMode; +import android.view.Menu; +import android.view.MenuItem; + +import androidx.annotation.Nullable; + +import it.niedermann.android.markdown.R; +import it.niedermann.android.markdown.markwon.MarkwonMarkdownEditor; +import it.niedermann.android.util.ClipboardUtil; + +public class ContextBasedRangeFormattingCallback implements ActionMode.Callback { + + private static final String TAG = ContextBasedRangeFormattingCallback.class.getSimpleName(); + + private final MarkwonMarkdownEditor editText; + + public ContextBasedRangeFormattingCallback(MarkwonMarkdownEditor editText) { + this.editText = editText; + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + mode.getMenuInflater().inflate(R.menu.context_based_range_formatting, menu); + + final SparseIntArray styleFormatMap = new SparseIntArray(); + styleFormatMap.append(R.id.bold, Typeface.BOLD); + styleFormatMap.append(R.id.italic, Typeface.ITALIC); + + MenuItem item; + CharSequence title; + SpannableStringBuilder ssb; + + for (int i = 0; i < styleFormatMap.size(); i++) { + item = menu.findItem(styleFormatMap.keyAt(i)); + title = item.getTitle(); + ssb = new SpannableStringBuilder(title); + ssb.setSpan(new StyleSpan(styleFormatMap.valueAt(i)), 0, title.length(), 0); + item.setTitle(ssb); + } + + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + // TODO hide actions if not available? + return false; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + final SpannableStringBuilder ssb = new SpannableStringBuilder(editText.getText()); + final int itemId = item.getItemId(); + final int start = editText.getSelectionStart(); + int end = editText.getSelectionEnd(); + + if (itemId == R.id.bold) { + final String punctuation = "**"; + final boolean hasAlreadyMarkdown = hasAlreadyMarkdown(editText.getText(), start, end, punctuation); + if (hasAlreadyMarkdown) { + removeMarkdown(ssb, start, end, punctuation); + } else { + ssb.insert(end, punctuation); + ssb.insert(start, punctuation); + } + editText.setMarkdownString(ssb); + editText.setSelection(hasAlreadyMarkdown ? end - punctuation.length() : end + punctuation.length() * 2); + return true; + } else if (itemId == R.id.italic) { + final String punctuation = "*"; + final boolean hasAlreadyMarkdown = hasAlreadyMarkdown(editText.getText(), start, end, punctuation); + if (hasAlreadyMarkdown) { + removeMarkdown(ssb, start, end, punctuation); + } else { + ssb.insert(end, punctuation); + ssb.insert(start, punctuation); + } + editText.setMarkdownString(ssb); + editText.setSelection(hasAlreadyMarkdown ? end - punctuation.length() : end + punctuation.length() * 2); + return true; + } else if (itemId == R.id.link) { + final CharSequence text = editText.getText(); + final boolean textToFormatIsLink = text != null && TextUtils.indexOf(text.subSequence(start, end), "http") == 0; + if (textToFormatIsLink) { + ssb.insert(end, ")"); + ssb.insert(start, "[]("); + } else { + String clipboardURL = ClipboardUtil.INSTANCE.getClipboardURLorNull(editText.getContext()); + if (clipboardURL != null) { + ssb.insert(end, "](" + clipboardURL + ")"); + end += clipboardURL.length(); + } else { + ssb.insert(end, "]()"); + } + ssb.insert(start, "["); + } + editText.setMarkdownString(ssb); + if (textToFormatIsLink) { + editText.setSelection(start + 1); + } else { + editText.setSelection(end + 3); // after <end>]( + } + return true; + } + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + // Nothing to do here... + } + + private static boolean hasAlreadyMarkdown(@Nullable CharSequence text, int start, int end, String punctuation) { + return text != null && (start > punctuation.length() && punctuation.contentEquals(text.subSequence(start - punctuation.length(), start)) && + text.length() > end + punctuation.length() && punctuation.contentEquals(text.subSequence(end, end + punctuation.length()))); + } + + private static void removeMarkdown(SpannableStringBuilder ssb, int start, int end, String punctuation) { + // FIXME disabled, because it does not work properly and might cause data loss + ssb.delete(start - punctuation.length(), start); + ssb.delete(end - punctuation.length(), end); + } +} diff --git a/markdown/src/main/java/it/niedermann/android/markdown/markwon/model/EListType.java b/markdown/src/main/java/it/niedermann/android/markdown/markwon/model/EListType.java new file mode 100644 index 000000000..a03987fda --- /dev/null +++ b/markdown/src/main/java/it/niedermann/android/markdown/markwon/model/EListType.java @@ -0,0 +1,21 @@ +package it.niedermann.android.markdown.markwon.model; + +public enum EListType { + STAR('*'), + DASH('-'), + PLUS('+'); + + public final String listSymbol; + public final String listSymbolWithTrailingSpace; + public final String checkboxChecked; + public final String checkboxUnchecked; + public final String checkboxUncheckedWithTrailingSpace; + + EListType(char listSymbol) { + this.listSymbol = String.valueOf(listSymbol); + this.listSymbolWithTrailingSpace = listSymbol + " "; + this.checkboxChecked = listSymbolWithTrailingSpace + "[x]"; + this.checkboxUnchecked = listSymbolWithTrailingSpace + "[ ]"; + this.checkboxUncheckedWithTrailingSpace = checkboxUnchecked + " "; + } +}
\ No newline at end of file diff --git a/markdown/src/main/java/it/niedermann/android/markdown/markwon/textwatcher/AutoContinuationTextWatcher.java b/markdown/src/main/java/it/niedermann/android/markdown/markwon/textwatcher/AutoContinuationTextWatcher.java index 384dc78c5..795812f4c 100644 --- a/markdown/src/main/java/it/niedermann/android/markdown/markwon/textwatcher/AutoContinuationTextWatcher.java +++ b/markdown/src/main/java/it/niedermann/android/markdown/markwon/textwatcher/AutoContinuationTextWatcher.java @@ -10,6 +10,12 @@ import java.util.concurrent.Executors; import io.noties.markwon.editor.MarkwonEditor; import io.noties.markwon.editor.MarkwonEditorTextWatcher; import it.niedermann.android.markdown.markwon.MarkwonMarkdownEditor; +import it.niedermann.android.markdown.markwon.model.EListType; + +import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.getListItemIfIsEmpty; +import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.getStartOfLine; +import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.lineStartsWithCheckbox; +import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.lineStartsWithList; /** * Automatically continues lists and checkbox lists when pressing enter @@ -88,51 +94,4 @@ public class AutoContinuationTextWatcher implements TextWatcher { } } } - - private static int getStartOfLine(@NonNull CharSequence s, int cursorPosition) { - int startOfLine = cursorPosition; - while (startOfLine > 0 && s.charAt(startOfLine - 1) != '\n') { - startOfLine--; - } - return startOfLine; - } - - private static String getListItemIfIsEmpty(@NonNull String line) { - for (EListType listType : EListType.values()) { - if (line.equals(listType.checkboxUncheckedWithTrailingSpace)) { - return listType.checkboxUncheckedWithTrailingSpace; - } else if (line.equals(listType.listSymbolWithTrailingSpace)) { - return listType.listSymbolWithTrailingSpace; - } - } - return null; - } - - private static boolean lineStartsWithCheckbox(@NonNull String line, @NonNull EListType listType) { - return line.startsWith(listType.checkboxUnchecked) || line.startsWith(listType.checkboxChecked); - } - - private static boolean lineStartsWithList(@NonNull String line, @NonNull EListType listType) { - return line.startsWith(listType.listSymbol); - } - - enum EListType { - STAR('*'), - DASH('-'), - PLUS('+'); - - final String listSymbol; - final String listSymbolWithTrailingSpace; - final String checkboxChecked; - final String checkboxUnchecked; - final String checkboxUncheckedWithTrailingSpace; - - EListType(char listSymbol) { - this.listSymbol = String.valueOf(listSymbol); - this.listSymbolWithTrailingSpace = listSymbol + " "; - this.checkboxChecked = listSymbolWithTrailingSpace + "[x]"; - this.checkboxUnchecked = listSymbolWithTrailingSpace + "[ ]"; - this.checkboxUncheckedWithTrailingSpace = checkboxUnchecked + " "; - } - } } diff --git a/markdown/src/main/res/drawable/ic_format_bold_black_24dp.xml b/markdown/src/main/res/drawable/ic_format_bold_black_24dp.xml new file mode 100644 index 000000000..0b06db879 --- /dev/null +++ b/markdown/src/main/res/drawable/ic_format_bold_black_24dp.xml @@ -0,0 +1,11 @@ +<vector android:autoMirrored="true" + android:height="24dp" + android:tint="#757575" + android:viewportHeight="24.0" + android:viewportWidth="24.0" + android:width="24dp" + xmlns:android="http://schemas.android.com/apk/res/android"> + <path + android:fillColor="#FF000000" + android:pathData="M15.6,10.79c0.97,-0.67 1.65,-1.77 1.65,-2.79 0,-2.26 -1.75,-4 -4,-4L7,4v14h7.04c2.09,0 3.71,-1.7 3.71,-3.79 0,-1.52 -0.86,-2.82 -2.15,-3.42zM10,6.5h3c0.83,0 1.5,0.67 1.5,1.5s-0.67,1.5 -1.5,1.5h-3v-3zM13.5,15.5L10,15.5v-3h3.5c0.83,0 1.5,0.67 1.5,1.5s-0.67,1.5 -1.5,1.5z" /> +</vector> diff --git a/markdown/src/main/res/drawable/ic_format_italic_black_24dp.xml b/markdown/src/main/res/drawable/ic_format_italic_black_24dp.xml new file mode 100644 index 000000000..20680318b --- /dev/null +++ b/markdown/src/main/res/drawable/ic_format_italic_black_24dp.xml @@ -0,0 +1,11 @@ +<vector android:autoMirrored="true" + android:height="24dp" + android:tint="#757575" + android:viewportHeight="24.0" + android:viewportWidth="24.0" + android:width="24dp" + xmlns:android="http://schemas.android.com/apk/res/android"> + <path + android:fillColor="#FF000000" + android:pathData="M10,4v3h2.21l-3.42,8H6v3h8v-3h-2.21l3.42,-8H18V4z" /> +</vector> diff --git a/markdown/src/main/res/drawable/ic_insert_link_black_24dp.xml b/markdown/src/main/res/drawable/ic_insert_link_black_24dp.xml new file mode 100644 index 000000000..91b42f7fb --- /dev/null +++ b/markdown/src/main/res/drawable/ic_insert_link_black_24dp.xml @@ -0,0 +1,11 @@ +<vector android:autoMirrored="true" + android:height="24dp" + android:tint="#757575" + android:viewportHeight="24.0" + android:viewportWidth="24.0" + android:width="24dp" + xmlns:android="http://schemas.android.com/apk/res/android"> + <path + android:fillColor="#FF000000" + android:pathData="M3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4L11,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9L7,15.1c-1.71,0 -3.1,-1.39 -3.1,-3.1zM8,13h8v-2L8,11v2zM17,7h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1s-1.39,3.1 -3.1,3.1h-4L13,17h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5z" /> +</vector> diff --git a/markdown/src/main/res/menu/context_based_formatting.xml b/markdown/src/main/res/menu/context_based_formatting.xml new file mode 100644 index 000000000..9bf2d779f --- /dev/null +++ b/markdown/src/main/res/menu/context_based_formatting.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:id="@+id/checkbox" + android:title="@string/simple_checkbox" + app:showAsAction="ifRoom" /> + <item + android:id="@+id/link" + android:icon="@drawable/ic_insert_link_black_24dp" + android:title="@string/simple_link" + app:showAsAction="ifRoom" /> +</menu>
\ No newline at end of file diff --git a/markdown/src/main/res/menu/context_based_range_formatting.xml b/markdown/src/main/res/menu/context_based_range_formatting.xml new file mode 100644 index 000000000..68d701f83 --- /dev/null +++ b/markdown/src/main/res/menu/context_based_range_formatting.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:id="@+id/italic" + android:icon="@drawable/ic_format_italic_black_24dp" + android:title="@string/simple_italic" + app:showAsAction="ifRoom" /> + <item + android:id="@+id/bold" + android:icon="@drawable/ic_format_bold_black_24dp" + android:title="@string/simple_bold" + app:showAsAction="ifRoom" /> + <item + android:id="@+id/link" + android:icon="@drawable/ic_insert_link_black_24dp" + android:title="@string/simple_link" + app:showAsAction="ifRoom" /> +</menu>
\ No newline at end of file diff --git a/markdown/src/main/res/values/strings.xml b/markdown/src/main/res/values/strings.xml new file mode 100644 index 000000000..f78123e3b --- /dev/null +++ b/markdown/src/main/res/values/strings.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="simple_checkbox">Checkbox</string> + <string name="simple_bold">Bold</string> + <string name="simple_link">Link</string> + <string name="simple_italic">Italic</string> +</resources>
\ No newline at end of file |