mirror of
https://github.com/nextcloud/notes-android.git
synced 2024-10-25 14:15:48 +03:00
Enhance in-note search highlight
This commit is contained in:
parent
f9754f0013
commit
9a95143f4e
7 changed files with 76 additions and 33 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
|
||||
<color name="icon_color_default">#757575</color>
|
||||
|
||||
<color name="bg_search_primary">@color/primary</color>
|
||||
<color name="bg_search_secondary">#eee</color>
|
||||
|
||||
<color name="widget_background">#dfffffff</color>
|
||||
<color name="widget_fg_default">#000000</color>
|
||||
<color name="widget_fg_contrast">#ffffff</color>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<item name="windowActionModeOverlay">true</item>
|
||||
<item name="android:colorBackground">@color/bg_normal</item>
|
||||
</style>
|
||||
|
||||
<style name="toolbarStyle" parent="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
|
||||
<item name="colorAccent">#fff</item>
|
||||
</style>
|
||||
|
|
Loading…
Reference in a new issue