diff options
author | Stefan Niedermann <info@niedermann.it> | 2020-12-19 16:47:26 +0300 |
---|---|---|
committer | Niedermann IT-Dienstleistungen <stefan-niedermann@users.noreply.github.com> | 2020-12-21 13:49:02 +0300 |
commit | cf31fe6ba34b6adb73242aad0f8c480b9179024a (patch) | |
tree | 4c48218b799e591ebf75c9804bea1a68d28ee1cb | |
parent | e0f1052db42a77e1310abff49eed8745dc1e3355 (diff) |
PoC SearchHighlight for editor
Signed-off-by: Stefan Niedermann <info@niedermann.it>
8 files changed, 205 insertions, 113 deletions
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 345a75347..410cacc0d 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 @@ -23,7 +23,7 @@ import it.niedermann.android.markdown.markwon.handler.CodeBlockEditHandler; import it.niedermann.android.markdown.markwon.handler.CodeEditHandler; import it.niedermann.android.markdown.markwon.handler.HeadingEditHandler; import it.niedermann.android.markdown.markwon.handler.StrikethroughEditHandler; -import it.niedermann.android.markdown.markwon.textwatcher.AutoContinuationTextWatcher; +import it.niedermann.android.markdown.markwon.textwatcher.CombinedTextWatcher; public class MarkwonMarkdownEditor extends AppCompatEditText implements MarkdownEditor { @@ -49,7 +49,7 @@ public class MarkwonMarkdownEditor extends AppCompatEditText implements Markdown .useEditHandler(new BlockQuoteEditHandler()) .useEditHandler(new HeadingEditHandler()) .build(); - addTextChangedListener(new AutoContinuationTextWatcher(editor, this)); + addTextChangedListener(new CombinedTextWatcher(editor, this)); setCustomSelectionActionModeCallback(new ContextBasedRangeFormattingCallback(this)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { setCustomInsertionActionModeCallback(new ContextBasedFormattingCallback(this)); 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 ffc67cb2f..86629bb0e 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 @@ -2,8 +2,10 @@ package it.niedermann.android.markdown.markwon; import android.content.Context; import android.text.Editable; +import android.text.Spannable; import android.text.TextUtils; +import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RestrictTo; @@ -30,6 +32,7 @@ import it.niedermann.android.markdown.markwon.model.EListType; import it.niedermann.android.markdown.markwon.plugins.NextcloudMentionsPlugin; import it.niedermann.android.markdown.markwon.plugins.SearchHighlightPlugin; import it.niedermann.android.markdown.markwon.plugins.ThemePlugin; +import it.niedermann.android.markdown.markwon.span.SearchSpan; @RestrictTo(value = RestrictTo.Scope.LIBRARY) @PrismBundle( @@ -252,4 +255,31 @@ public class MarkwonMarkdownUtil { } return false; } + + public static void searchAndColor(@NonNull Editable editable, @Nullable CharSequence searchText, @NonNull Context context, @Nullable Integer current, @ColorInt int mainColor, @ColorInt int textColor) { + resetSearchSpans(editable); + if (TextUtils.isEmpty(searchText)) { + return; + } + + //noinspection ConstantConditions + final Matcher m = Pattern.compile(searchText.toString(), Pattern.CASE_INSENSITIVE | Pattern.LITERAL) + .matcher(editable); + + int i = 1; + while (m.find()) { + int start = m.start(); + int end = m.end(); + editable.setSpan(new SearchSpan(context, mainColor, textColor, (current != null && i == current)), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + i++; + } + } + + private static void resetSearchSpans(@NonNull Editable editable) { + final Object[] spansToRemove = editable.getSpans(0, editable.length(), Object.class); + for (Object span : spansToRemove) { + if (span.getClass() == SearchSpan.class) + editable.removeSpan(span); + } + } } diff --git a/markdown/src/main/java/it/niedermann/android/markdown/markwon/plugins/SearchHighlightPlugin.java b/markdown/src/main/java/it/niedermann/android/markdown/markwon/plugins/SearchHighlightPlugin.java index 9bde67aa9..c297b3782 100644 --- a/markdown/src/main/java/it/niedermann/android/markdown/markwon/plugins/SearchHighlightPlugin.java +++ b/markdown/src/main/java/it/niedermann/android/markdown/markwon/plugins/SearchHighlightPlugin.java @@ -1,25 +1,16 @@ package it.niedermann.android.markdown.markwon.plugins; -import android.content.Context; -import android.content.res.Configuration; import android.graphics.Color; -import android.text.Spannable; +import android.text.Editable; import android.text.SpannableStringBuilder; -import android.text.TextPaint; -import android.text.TextUtils; -import android.text.style.MetricAffectingSpan; import android.widget.TextView; -import androidx.annotation.ColorInt; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; import io.noties.markwon.AbstractMarkwonPlugin; import io.noties.markwon.MarkwonPlugin; -import it.niedermann.android.util.ColorUtil; + +import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.searchAndColor; public class SearchHighlightPlugin extends AbstractMarkwonPlugin { @@ -30,97 +21,8 @@ public class SearchHighlightPlugin extends AbstractMarkwonPlugin { @Override public void afterSetText(@NonNull TextView textView) { super.afterSetText(textView); - Spannable coloredContent = searchAndColor(textView.getText(), "new", textView.getContext(), 0, Color.BLUE, Color.YELLOW); + final Editable coloredContent = new SpannableStringBuilder(textView.getText()); + searchAndColor(coloredContent, "new", textView.getContext(), 0, Color.BLUE, Color.YELLOW); textView.setText(coloredContent, TextView.BufferType.SPANNABLE); } - - public static Spannable searchAndColor(CharSequence sequence, CharSequence searchText, @NonNull Context context, @Nullable Integer current, @ColorInt int mainColor, @ColorInt int textColor) { - SpannableStringBuilder spannable = new SpannableStringBuilder(sequence); - CharSequence text = spannable.toString(); - - Object[] spansToRemove = spannable.getSpans(0, text.length(), Object.class); - for (Object span : spansToRemove) { - if (span instanceof SearchSpan) - spannable.removeSpan(span); - } - - if (TextUtils.isEmpty(text) || TextUtils.isEmpty(searchText)) { - return spannable; - } - - Matcher m = Pattern.compile(searchText.toString(), Pattern.CASE_INSENSITIVE | Pattern.LITERAL) - .matcher(text); - - int i = 1; - while (m.find()) { - int start = m.start(); - int end = m.end(); - spannable.setSpan(new SearchSpan(context, mainColor, textColor, (current != null && i == current)), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - i++; - } - - return spannable; - } - - static class SearchSpan extends MetricAffectingSpan { - - private final boolean current; - @NonNull - Context context; - @ColorInt - private final int mainColor; - @ColorInt - private final int textColor; - @ColorInt - private final int highlightColor; - - SearchSpan(@NonNull Context context, @ColorInt int mainColor, @ColorInt int textColor, boolean current) { - this.context = context; - this.mainColor = mainColor; - this.textColor = textColor; - this.current = current; - this.highlightColor = Color.RED;// context.getResources().getColor(R.color.bg_highlighted); - } - - @Override - public void updateDrawState(TextPaint tp) { - if (current) { - if (isDarkThemeActive(context)) { - if (ColorUtil.INSTANCE.isColorDark(mainColor)) { - tp.bgColor = Color.WHITE; - tp.setColor(mainColor); - } else { - tp.bgColor = mainColor; - tp.setColor(Color.BLACK); - } - } else { - if (ColorUtil.INSTANCE.isColorDark(mainColor)) { - tp.bgColor = mainColor; - tp.setColor(Color.WHITE); - } else { -// if (NotesColorUtil.contrastRatioIsSufficient(mainColor, highlightColor)) { -// tp.bgColor = highlightColor; -// } else { - tp.bgColor = Color.BLACK; -// } - tp.setColor(mainColor); - } - } - } else { - tp.bgColor = highlightColor; - tp.setColor(/*BrandingUtil.getSecondaryForegroundColorDependingOnTheme(context, */mainColor/*)*/); - } - tp.setFakeBoldText(true); - } - - @Override - public void updateMeasureState(@NonNull TextPaint tp) { - tp.setFakeBoldText(true); - } - } - - private static boolean isDarkThemeActive(Context context) { - int uiMode = context.getResources().getConfiguration().uiMode; - return (uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; - } } diff --git a/markdown/src/main/java/it/niedermann/android/markdown/markwon/span/SearchSpan.java b/markdown/src/main/java/it/niedermann/android/markdown/markwon/span/SearchSpan.java new file mode 100644 index 000000000..857d54267 --- /dev/null +++ b/markdown/src/main/java/it/niedermann/android/markdown/markwon/span/SearchSpan.java @@ -0,0 +1,74 @@ +package it.niedermann.android.markdown.markwon.span; + +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Color; +import android.text.TextPaint; +import android.text.style.MetricAffectingSpan; + +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; + +import it.niedermann.android.util.ColorUtil; + +public class SearchSpan extends MetricAffectingSpan { + + private final boolean current; + @NonNull + Context context; + @ColorInt + private final int mainColor; + @ColorInt + private final int textColor; + @ColorInt + private final int highlightColor; + + public SearchSpan(@NonNull Context context, @ColorInt int mainColor, @ColorInt int textColor, boolean current) { + this.context = context; + this.mainColor = mainColor; + this.textColor = textColor; + this.current = current; + this.highlightColor = Color.GRAY; //context.getResources().getColor(R.color.bg_highlighted); + } + + @Override + public void updateDrawState(TextPaint tp) { + if (current) { + if (isDarkThemeActive(context)) { + if (ColorUtil.INSTANCE.isColorDark(mainColor)) { + tp.bgColor = Color.WHITE; + tp.setColor(mainColor); + } else { + tp.bgColor = mainColor; + tp.setColor(Color.BLACK); + } + } else { + if (ColorUtil.INSTANCE.isColorDark(mainColor)) { + tp.bgColor = mainColor; + tp.setColor(Color.WHITE); + } else { + if (true /*NotesColorUtil.contrastRatioIsSufficient(mainColor, highlightColor)*/) { + tp.bgColor = highlightColor; + } else { + tp.bgColor = Color.BLACK; + } + tp.setColor(mainColor); + } + } + } else { + tp.bgColor = highlightColor; + tp.setColor(mainColor /*BrandingUtil.getSecondaryForegroundColorDependingOnTheme(context, mainColor)*/); + } + tp.setFakeBoldText(true); + } + + @Override + public void updateMeasureState(@NonNull TextPaint tp) { + tp.setFakeBoldText(true); + } + + private static boolean isDarkThemeActive(Context context) { + int uiMode = context.getResources().getConfiguration().uiMode; + return (uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; + } +}
\ 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 f01e62b40..136eccea6 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 @@ -5,10 +5,6 @@ import android.text.TextWatcher; import androidx.annotation.NonNull; -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; @@ -20,18 +16,18 @@ import static it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil.lineSta /** * Automatically continues lists and checkbox lists when pressing enter */ -public class AutoContinuationTextWatcher implements TextWatcher { +public class AutoContinuationTextWatcher extends InterceptorTextWatcher { - private final MarkwonEditorTextWatcher originalWatcher; + @NonNull private final MarkwonMarkdownEditor editText; private CharSequence customText = null; private boolean isInsert = true; private int sequenceStart = 0; - public AutoContinuationTextWatcher(@NonNull MarkwonEditor editor, @NonNull MarkwonMarkdownEditor editText) { + public AutoContinuationTextWatcher(@NonNull TextWatcher originalWatcher, @NonNull MarkwonMarkdownEditor editText) { + super(originalWatcher); this.editText = editText; - originalWatcher = MarkwonEditorTextWatcher.withPreRender(editor, Executors.newSingleThreadExecutor(), editText); } @Override diff --git a/markdown/src/main/java/it/niedermann/android/markdown/markwon/textwatcher/CombinedTextWatcher.java b/markdown/src/main/java/it/niedermann/android/markdown/markwon/textwatcher/CombinedTextWatcher.java new file mode 100644 index 000000000..56a9a8abb --- /dev/null +++ b/markdown/src/main/java/it/niedermann/android/markdown/markwon/textwatcher/CombinedTextWatcher.java @@ -0,0 +1,39 @@ +package it.niedermann.android.markdown.markwon.textwatcher; + +import android.text.Editable; +import android.text.TextWatcher; + +import androidx.annotation.NonNull; + +import java.util.concurrent.Executors; + +import io.noties.markwon.editor.MarkwonEditor; +import io.noties.markwon.editor.MarkwonEditorTextWatcher; +import it.niedermann.android.markdown.markwon.MarkwonMarkdownEditor; + +public class CombinedTextWatcher implements TextWatcher { + + private final TextWatcher watcher; + + public CombinedTextWatcher(@NonNull MarkwonEditor editor, @NonNull MarkwonMarkdownEditor editText) { + final TextWatcher markwonTextWatcher = MarkwonEditorTextWatcher.withPreRender(editor, Executors.newSingleThreadExecutor(), editText); + final TextWatcher autoContinuationTextWatcher = new AutoContinuationTextWatcher(markwonTextWatcher, editText); + watcher = new SearchHighlightTextWatcher(autoContinuationTextWatcher, editText); + } + + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + watcher.beforeTextChanged(s, start, count, after); + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + watcher.onTextChanged(s, start, before, count); + } + + @Override + public void afterTextChanged(Editable s) { + watcher.afterTextChanged(s); + } +} diff --git a/markdown/src/main/java/it/niedermann/android/markdown/markwon/textwatcher/InterceptorTextWatcher.java b/markdown/src/main/java/it/niedermann/android/markdown/markwon/textwatcher/InterceptorTextWatcher.java new file mode 100644 index 000000000..4b5e9d5f3 --- /dev/null +++ b/markdown/src/main/java/it/niedermann/android/markdown/markwon/textwatcher/InterceptorTextWatcher.java @@ -0,0 +1,15 @@ +package it.niedermann.android.markdown.markwon.textwatcher; + +import android.text.TextWatcher; + +import androidx.annotation.NonNull; + +abstract public class InterceptorTextWatcher implements TextWatcher { + + @NonNull + protected final TextWatcher originalWatcher; + + public InterceptorTextWatcher(@NonNull TextWatcher originalWatcher) { + this.originalWatcher = originalWatcher; + } +} diff --git a/markdown/src/main/java/it/niedermann/android/markdown/markwon/textwatcher/SearchHighlightTextWatcher.java b/markdown/src/main/java/it/niedermann/android/markdown/markwon/textwatcher/SearchHighlightTextWatcher.java new file mode 100644 index 000000000..896be9610 --- /dev/null +++ b/markdown/src/main/java/it/niedermann/android/markdown/markwon/textwatcher/SearchHighlightTextWatcher.java @@ -0,0 +1,36 @@ +package it.niedermann.android.markdown.markwon.textwatcher; + +import android.graphics.Color; +import android.text.Editable; +import android.text.TextWatcher; + +import androidx.annotation.NonNull; + +import it.niedermann.android.markdown.markwon.MarkwonMarkdownEditor; +import it.niedermann.android.markdown.markwon.MarkwonMarkdownUtil; + +public class SearchHighlightTextWatcher extends InterceptorTextWatcher { + + private final MarkwonMarkdownEditor editText; + + public SearchHighlightTextWatcher(@NonNull TextWatcher originalWatcher, @NonNull MarkwonMarkdownEditor editText) { + super(originalWatcher); + this.editText = editText; + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + this.originalWatcher.beforeTextChanged(s, start, count, after); + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + this.originalWatcher.onTextChanged(s, start, before, count); + } + + @Override + public void afterTextChanged(Editable s) { + originalWatcher.afterTextChanged(s); + MarkwonMarkdownUtil.searchAndColor(s, "new", editText.getContext(), 0, Color.MAGENTA, Color.GREEN); + } +} |