highlight search term in search results

Daniel Gultsch created

Change summary

art/message_bubble_received_warning.svg                             |  2 
src/main/java/eu/siacs/conversations/ui/SearchActivity.java         |  2 
src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java |  9 
src/main/java/eu/siacs/conversations/utils/StylingHelper.java       | 51 
src/main/res/drawable-hdpi/date_bubble_grey.9.png                   |  0 
src/main/res/drawable-hdpi/date_bubble_white.9.png                  |  0 
src/main/res/drawable-hdpi/message_bubble_received.9.png            |  0 
src/main/res/drawable-hdpi/message_bubble_received_dark.9.png       |  0 
src/main/res/drawable-hdpi/message_bubble_received_grey.9.png       |  0 
src/main/res/drawable-hdpi/message_bubble_received_warning.9.png    |  0 
src/main/res/drawable-hdpi/message_bubble_received_white.9.png      |  0 
src/main/res/drawable-hdpi/message_bubble_sent.9.png                |  0 
src/main/res/drawable-hdpi/message_bubble_sent_grey.9.png           |  0 
src/main/res/drawable-mdpi/date_bubble_grey.9.png                   |  0 
src/main/res/drawable-mdpi/date_bubble_white.9.png                  |  0 
src/main/res/drawable-mdpi/message_bubble_received.9.png            |  0 
src/main/res/drawable-mdpi/message_bubble_received_dark.9.png       |  0 
src/main/res/drawable-mdpi/message_bubble_received_grey.9.png       |  0 
src/main/res/drawable-mdpi/message_bubble_received_warning.9.png    |  0 
src/main/res/drawable-mdpi/message_bubble_received_white.9.png      |  0 
src/main/res/drawable-mdpi/message_bubble_sent.9.png                |  0 
src/main/res/drawable-mdpi/message_bubble_sent_grey.9.png           |  0 
src/main/res/drawable-xhdpi/date_bubble_grey.9.png                  |  0 
src/main/res/drawable-xhdpi/date_bubble_white.9.png                 |  0 
src/main/res/drawable-xhdpi/message_bubble_received.9.png           |  0 
src/main/res/drawable-xhdpi/message_bubble_received_dark.9.png      |  0 
src/main/res/drawable-xhdpi/message_bubble_received_grey.9.png      |  0 
src/main/res/drawable-xhdpi/message_bubble_received_warning.9.png   |  0 
src/main/res/drawable-xhdpi/message_bubble_received_white.9.png     |  0 
src/main/res/drawable-xhdpi/message_bubble_sent.9.png               |  0 
src/main/res/drawable-xhdpi/message_bubble_sent_grey.9.png          |  0 
src/main/res/drawable-xxhdpi/date_bubble_grey.9.png                 |  0 
src/main/res/drawable-xxhdpi/date_bubble_white.9.png                |  0 
src/main/res/drawable-xxhdpi/message_bubble_received.9.png          |  0 
src/main/res/drawable-xxhdpi/message_bubble_received_dark.9.png     |  0 
src/main/res/drawable-xxhdpi/message_bubble_received_grey.9.png     |  0 
src/main/res/drawable-xxhdpi/message_bubble_received_warning.9.png  |  0 
src/main/res/drawable-xxhdpi/message_bubble_received_white.9.png    |  0 
src/main/res/drawable-xxhdpi/message_bubble_sent.9.png              |  0 
src/main/res/drawable-xxhdpi/message_bubble_sent_grey.9.png         |  0 
src/main/res/drawable-xxxhdpi/date_bubble_grey.9.png                |  0 
src/main/res/drawable-xxxhdpi/date_bubble_white.9.png               |  0 
src/main/res/drawable-xxxhdpi/message_bubble_received.9.png         |  0 
src/main/res/drawable-xxxhdpi/message_bubble_received_dark.9.png    |  0 
src/main/res/drawable-xxxhdpi/message_bubble_received_grey.9.png    |  0 
src/main/res/drawable-xxxhdpi/message_bubble_received_warning.9.png |  0 
src/main/res/drawable-xxxhdpi/message_bubble_received_white.9.png   |  0 
src/main/res/drawable-xxxhdpi/message_bubble_sent.9.png             |  0 
src/main/res/drawable-xxxhdpi/message_bubble_sent_grey.9.png        |  0 
49 files changed, 59 insertions(+), 5 deletions(-)

Detailed changes

art/message_bubble_received_warning.svg 🔗

@@ -140,7 +140,7 @@
      transform="translate(0,-2)">
     <g
        id="g3759"
-       style="fill:#c64545;fill-opacity:1;stroke:none;fill-rule:nonzero;filter:url(#filter3811)">
+       style="fill:#ad4545;fill-opacity:1;stroke:none;fill-rule:nonzero;filter:url(#filter3811)">
       <path
          style="display:none"
          d="m 8,6 c 2,2 4,6 4,10 L 16,6 z"

src/main/java/eu/siacs/conversations/ui/SearchActivity.java 🔗

@@ -207,6 +207,7 @@ public class SearchActivity extends XmppActivity implements TextWatcher, OnSearc
 		} else {
 			MessageSearchTask.cancelRunningTasks();
 			this.messages.clear();
+			messageListAdapter.setHighlightedTerm(null);
 			messageListAdapter.notifyDataSetChanged();
 			changeBackground(false, false);
 		}
@@ -216,6 +217,7 @@ public class SearchActivity extends XmppActivity implements TextWatcher, OnSearc
 	public void onSearchResultsAvailable(String term, List<Message> messages) {
 		runOnUiThread(() -> {
 			this.messages.clear();
+			messageListAdapter.setHighlightedTerm(term);
 			DateSeparator.addAll(messages);
 			this.messages.addAll(messages);
 			messageListAdapter.notifyDataSetChanged();

src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java 🔗

@@ -99,6 +99,8 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 					+ "\\;\\/\\?\\@\\&\\=\\#\\~\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])"
 					+ "|(?:\\%[a-fA-F0-9]{2}))+");
 
+	private String highlightedText = null;
+
 	private static final Linkify.TransformFilter WEBURL_TRANSFORM_FILTER = (matcher, url) -> {
 		if (url == null) {
 			return null;
@@ -548,6 +550,9 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 			}
 
 			StylingHelper.format(body, viewHolder.messageBody.getCurrentTextColor());
+			if (highlightedText != null) {
+				StylingHelper.highlight(activity, body, highlightedText, StylingHelper.isDarkText(viewHolder.messageBody));
+			}
 
 			Linkify.addLinks(body, XMPP_PATTERN, "xmpp", XMPPURI_MATCH_FILTER, null);
 			Linkify.addLinks(body, Patterns.AUTOLINK_WEB_URL, "http", WEBURL_MATCH_FILTER, WEBURL_TRANSFORM_FILTER);
@@ -1003,6 +1008,10 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
 		}
 	}
 
+	public void setHighlightedTerm(String term) {
+		this.highlightedText = term;
+	}
+
 	public interface OnQuoteListener {
 		void onQuote(String text);
 	}

src/main/java/eu/siacs/conversations/utils/StylingHelper.java 🔗

@@ -29,22 +29,28 @@
 
 package eu.siacs.conversations.utils;
 
+import android.content.Context;
 import android.graphics.Color;
 import android.graphics.Typeface;
 import android.support.annotation.ColorInt;
+import android.support.v4.content.ContextCompat;
 import android.text.Editable;
 import android.text.ParcelableSpan;
+import android.text.SpannableString;
 import android.text.Spanned;
 import android.text.TextWatcher;
+import android.text.style.BackgroundColorSpan;
 import android.text.style.ForegroundColorSpan;
 import android.text.style.StrikethroughSpan;
 import android.text.style.StyleSpan;
 import android.text.style.TypefaceSpan;
 import android.widget.EditText;
+import android.widget.TextView;
 
 import java.util.Arrays;
 import java.util.List;
 
+import eu.siacs.conversations.R;
 import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.ui.text.QuoteSpan;
 
@@ -67,7 +73,7 @@ public class StylingHelper {
 	}
 
 	public static void format(final Editable editable, int start, int end, @ColorInt int textColor) {
-		for (ImStyleParser.Style style : ImStyleParser.parse(editable,start,end)) {
+		for (ImStyleParser.Style style : ImStyleParser.parse(editable, start, end)) {
 			final int keywordLength = style.getKeyword().length();
 			editable.setSpan(createSpanForStyle(style), style.getStart() + keywordLength, style.getEnd() - keywordLength + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
 			makeKeywordOpaque(editable, style.getStart(), style.getStart() + keywordLength, textColor);
@@ -78,11 +84,29 @@ public class StylingHelper {
 	public static void format(final Editable editable, @ColorInt int textColor) {
 		int end = 0;
 		Message.MergeSeparator[] spans = editable.getSpans(0, editable.length() - 1, Message.MergeSeparator.class);
-		for(Message.MergeSeparator span : spans) {
-			format(editable,end,editable.getSpanStart(span),textColor);
+		for (Message.MergeSeparator span : spans) {
+			format(editable, end, editable.getSpanStart(span), textColor);
 			end = editable.getSpanEnd(span);
 		}
-		format(editable,end,editable.length() -1,textColor);
+		format(editable, end, editable.length() - 1, textColor);
+	}
+
+	public static void highlight(final Context context, final Editable editable, String needle, boolean dark) {
+		final int length = needle.length();
+		String string = editable.toString();
+		int start = indexOfIgnoreCase(string, needle, 0);
+		while (start != -1) {
+			int end = start + length;
+			editable.setSpan(new BackgroundColorSpan(ContextCompat.getColor(context, dark ? R.color.deep_purple_a100 : R.color.deep_purple_a200)), start, end, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
+			editable.setSpan(new ForegroundColorSpan(ContextCompat.getColor(context, dark ? R.color.black87 : R.color.white)), start, end, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
+			start = indexOfIgnoreCase(string, needle, start + length);
+		}
+
+	}
+
+	public static boolean isDarkText(TextView textView) {
+		int argb = textView.getCurrentTextColor();
+		return Color.red(argb) + Color.green(argb) + Color.blue(argb) == 0;
 	}
 
 	private static ParcelableSpan createSpanForStyle(ImStyleParser.Style style) {
@@ -114,6 +138,25 @@ public class StylingHelper {
 		return Color.argb(Math.round(Color.alpha(c) * 0.6f), Color.red(c), Color.green(c), Color.blue(c));
 	}
 
+	private static int indexOfIgnoreCase(final String haystack, final String needle, final int start) {
+		if (haystack == null || needle == null) {
+			return -1;
+		}
+		final int endLimit = haystack.length() - needle.length() + 1;
+		if (start > endLimit) {
+			return -1;
+		}
+		if (needle.length() == 0) {
+			return start;
+		}
+		for (int i = start; i < endLimit; i++) {
+			if (haystack.regionMatches(true, i, needle, 0, needle.length())) {
+				return i;
+			}
+		}
+		return -1;
+	}
+
 	public static class MessageEditorStyler implements TextWatcher {
 
 		private final EditText mEditText;