From 9a95143f4e40a39c1ab3c13ab530973deaf15923 Mon Sep 17 00:00:00 2001 From: Stefan Niedermann Date: Thu, 26 Mar 2020 11:21:49 +0100 Subject: [PATCH] Enhance in-note search highlight --- .../android/fragment/NoteEditFragment.java | 14 ++--- .../android/fragment/NotePreviewFragment.java | 6 +- .../fragment/NoteReadonlyFragment.java | 12 ++-- .../fragment/SearchableBaseNoteFragment.java | 12 ++-- .../owncloud/notes/util/DisplayUtils.java | 61 +++++++++++++++---- app/src/main/res/values/colors.xml | 3 + app/src/main/res/values/styles.xml | 1 + 7 files changed, 76 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NoteEditFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NoteEditFragment.java index d41f161d..ff529277 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NoteEditFragment.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NoteEditFragment.java @@ -24,7 +24,6 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.core.view.ViewCompat; import androidx.preference.PreferenceManager; import com.google.android.material.floatingactionbutton.FloatingActionButton; @@ -35,12 +34,14 @@ import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.databinding.FragmentNoteEditBinding; import it.niedermann.owncloud.notes.model.CloudNote; import it.niedermann.owncloud.notes.model.ISyncCallback; -import it.niedermann.owncloud.notes.util.DisplayUtils; import it.niedermann.owncloud.notes.util.MarkDownUtil; import it.niedermann.owncloud.notes.util.NotesTextWatcher; import it.niedermann.owncloud.notes.util.format.ContextBasedFormattingCallback; import it.niedermann.owncloud.notes.util.format.ContextBasedRangeFormattingCallback; +import static androidx.core.view.ViewCompat.isAttachedToWindow; +import static it.niedermann.owncloud.notes.util.DisplayUtils.searchAndColor; + public class NoteEditFragment extends SearchableBaseNoteFragment { private static final String LOG_TAG_AUTOSAVE = "AutoSave"; @@ -238,11 +239,10 @@ public class NoteEditFragment extends SearchableBaseNoteFragment { } @Override - protected void colorWithText(String newText) { - if (binding != null && ViewCompat.isAttachedToWindow(binding.editContent)) { - binding.editContent.setText(DisplayUtils.searchAndColor(getContent(), new SpannableString - (getContent()), newText, getResources().getColor(R.color.primary)), - TextView.BufferType.SPANNABLE); + protected void colorWithText(@NonNull String newText, @Nullable Integer current) { + if (binding != null && isAttachedToWindow(binding.editContent)) { + binding.editContent.clearFocus(); + binding.editContent.setText(searchAndColor(new SpannableString(getContent()), newText, requireContext(), current), TextView.BufferType.SPANNABLE); } } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NotePreviewFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NotePreviewFragment.java index 86388eb6..a62bc733 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NotePreviewFragment.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NotePreviewFragment.java @@ -183,10 +183,10 @@ public class NotePreviewFragment extends SearchableBaseNoteFragment implements O } @Override - protected void colorWithText(String newText) { + protected void colorWithText(@NonNull String newText, @Nullable Integer current) { if (binding != null && ViewCompat.isAttachedToWindow(binding.singleNoteContent)) { - binding.singleNoteContent.setText(parseCompat(markdownProcessor, searchAndColor(getContent(), new SpannableString - (getContent()), newText, getResources().getColor(R.color.primary))), + binding.singleNoteContent.setText( + searchAndColor(new SpannableString(parseCompat(markdownProcessor, getContent())), newText, requireContext(), current), TextView.BufferType.SPANNABLE); } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NoteReadonlyFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NoteReadonlyFragment.java index c4d52c78..c0c8e2fb 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NoteReadonlyFragment.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/NoteReadonlyFragment.java @@ -19,7 +19,6 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.core.view.ViewCompat; import androidx.preference.PreferenceManager; import com.google.android.material.floatingactionbutton.FloatingActionButton; @@ -31,10 +30,11 @@ import it.niedermann.owncloud.notes.android.activity.EditNoteActivity; import it.niedermann.owncloud.notes.databinding.FragmentNotePreviewBinding; import it.niedermann.owncloud.notes.model.ISyncCallback; import it.niedermann.owncloud.notes.persistence.NotesDatabase; -import it.niedermann.owncloud.notes.util.DisplayUtils; import it.niedermann.owncloud.notes.util.MarkDownUtil; import it.niedermann.owncloud.notes.util.NoteLinksUtils; +import static androidx.core.view.ViewCompat.isAttachedToWindow; +import static it.niedermann.owncloud.notes.util.DisplayUtils.searchAndColor; import static it.niedermann.owncloud.notes.util.MarkDownUtil.parseCompat; public class NoteReadonlyFragment extends SearchableBaseNoteFragment { @@ -147,11 +147,9 @@ public class NoteReadonlyFragment extends SearchableBaseNoteFragment { } @Override - protected void colorWithText(String newText) { - if ((binding != null) && ViewCompat.isAttachedToWindow(binding.singleNoteContent)) { - binding.singleNoteContent.setText(parseCompat(markdownProcessor, DisplayUtils.searchAndColor(getContent(), new SpannableString - (getContent()), newText, getResources().getColor(R.color.primary))), - TextView.BufferType.SPANNABLE); + protected void colorWithText(@NonNull String newText, @Nullable Integer current) { + if ((binding != null) && isAttachedToWindow(binding.singleNoteContent)) { + binding.singleNoteContent.setText(searchAndColor(new SpannableString(parseCompat(markdownProcessor, getContent())), newText, requireContext(), current), TextView.BufferType.SPANNABLE); } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/SearchableBaseNoteFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/SearchableBaseNoteFragment.java index 188887cf..5287463f 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/SearchableBaseNoteFragment.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/SearchableBaseNoteFragment.java @@ -68,12 +68,12 @@ public abstract class SearchableBaseNoteFragment extends BaseNoteFragment { if (currentVisibility != oldVisibility) { if (currentVisibility != View.VISIBLE) { - colorWithText(""); + colorWithText("", null); searchQuery = ""; hideSearchFabs(); } else { jumpToOccurrence(); - colorWithText(searchQuery); + colorWithText(searchQuery, null); occurrenceCount = countOccurrences(getContent(), searchQuery); showSearchFabs(); } @@ -90,6 +90,7 @@ public abstract class SearchableBaseNoteFragment extends BaseNoteFragment { if (next != null) { next.setOnClickListener(v -> { currentOccurrence++; + colorWithText(searchView.getQuery().toString(), currentOccurrence); jumpToOccurrence(); }); } @@ -97,6 +98,7 @@ public abstract class SearchableBaseNoteFragment extends BaseNoteFragment { if (prev != null) { prev.setOnClickListener(v -> { currentOccurrence--; + colorWithText(searchView.getQuery().toString(), currentOccurrence); jumpToOccurrence(); }); } @@ -120,7 +122,7 @@ public abstract class SearchableBaseNoteFragment extends BaseNoteFragment { } currentOccurrence = 1; jumpToOccurrence(); - colorWithText(searchQuery); + colorWithText(searchQuery, currentOccurrence); return true; } }); @@ -136,7 +138,7 @@ public abstract class SearchableBaseNoteFragment extends BaseNoteFragment { } } - protected abstract void colorWithText(String newText); + protected abstract void colorWithText(@NonNull String newText, @Nullable Integer current); protected abstract ScrollView getScrollView(); @@ -194,7 +196,7 @@ public abstract class SearchableBaseNoteFragment extends BaseNoteFragment { int numberLine = layout.getLineForOffset(textUntilFirstOccurrence.length()); if (numberLine >= 0) { - getScrollView().smoothScrollTo(0, layout.getLineTop(numberLine)); + getScrollView().post(() -> getScrollView().smoothScrollTo(0, layout.getLineTop(numberLine))); } } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/util/DisplayUtils.java b/app/src/main/java/it/niedermann/owncloud/notes/util/DisplayUtils.java index e7ace892..d01df4d1 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/util/DisplayUtils.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/util/DisplayUtils.java @@ -19,29 +19,34 @@ */ package it.niedermann.owncloud.notes.util; -import android.graphics.Typeface; +import android.content.Context; +import android.graphics.Color; import android.text.Spannable; +import android.text.TextPaint; import android.text.TextUtils; -import android.text.style.CharacterStyle; -import android.text.style.ForegroundColorSpan; -import android.text.style.StyleSpan; +import android.text.style.MetricAffectingSpan; import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import java.util.regex.Matcher; import java.util.regex.Pattern; +import it.niedermann.owncloud.notes.R; + public class DisplayUtils { private DisplayUtils() { } - public static Spannable searchAndColor(String text, Spannable spannable, String searchText, @ColorInt int color) { + public static Spannable searchAndColor(Spannable spannable, CharSequence searchText, Context context, @Nullable Integer current) { + CharSequence text = spannable.toString(); Object[] spansToRemove = spannable.getSpans(0, text.length(), Object.class); - for(Object span: spansToRemove){ - if(span instanceof CharacterStyle) + for (Object span : spansToRemove) { + if (span instanceof SearchSpan) spannable.removeSpan(span); } @@ -49,18 +54,52 @@ public class DisplayUtils { return spannable; } - Matcher m = Pattern.compile(searchText, Pattern.CASE_INSENSITIVE | Pattern.LITERAL) + 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 ForegroundColorSpan(color), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - spannable.setSpan(new StyleSpan(Typeface.BOLD), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + spannable.setSpan(new SearchSpan(context, (current != null && i == current)), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + i++; } return spannable; } + + static class SearchSpan extends MetricAffectingSpan { + + private final boolean current; + private final int bgColorPrimary; + private final int bgColorSecondary; + + SearchSpan(Context context, boolean current) { + this.current = current; + this.bgColorPrimary = context.getResources().getColor(R.color.bg_search_primary); + this.bgColorSecondary = context.getResources().getColor(R.color.bg_search_secondary); + } + + @Override + public void updateDrawState(TextPaint tp) { + tp.bgColor = current ? bgColorPrimary : bgColorSecondary; + tp.setColor(current ? getForeground(Integer.toHexString(tp.bgColor)) : bgColorPrimary); + tp.setFakeBoldText(true); + } + + @Override + public void updateMeasureState(@NonNull TextPaint tp) { + tp.setFakeBoldText(true); + } + + private static @ColorInt + int getForeground(String backgroundColorHex) { + return ((float) ( + 0.2126 * Integer.valueOf(backgroundColorHex.substring(1, 3), 16) + + 0.7152 * Integer.valueOf(backgroundColorHex.substring(3, 5), 16) + + 0.0722 * Integer.valueOf(backgroundColorHex.substring(5, 7), 16) + ) < 140) ? Color.WHITE : Color.BLACK; + } + } } diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index b5f4ad6b..db265c9f 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -17,6 +17,9 @@ #757575 + @color/primary + #eee + #dfffffff #000000 #ffffff diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index a53b12c4..edf7acee 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -8,6 +8,7 @@ true @color/bg_normal +