Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9a95143f authored by Stefan Niedermann's avatar Stefan Niedermann
Browse files

Enhance in-note search highlight

parent f9754f00
Loading
Loading
Loading
Loading
+7 −7
Original line number Diff line number Diff line
@@ -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);
        }
    }
}
+3 −3
Original line number Diff line number Diff line
@@ -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);
        }
    }
+5 −7
Original line number Diff line number Diff line
@@ -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);
        }
    }

+7 −5
Original line number Diff line number Diff line
@@ -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)));
            }
        }
    }
+50 −11
Original line number Diff line number Diff line
@@ -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)
            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;
        }
    }
}
Loading