Update conversations list UI.

This commit is contained in:
Lucio Maciel
2021-09-03 17:38:20 -03:00
committed by Cody Henthorne
parent c84de8fa60
commit e09d162c1e
20 changed files with 330 additions and 211 deletions

View File

@@ -5,10 +5,14 @@ import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.style.CharacterStyle;
import android.text.style.MetricAffectingSpan;
import android.text.style.StyleSpan;
import android.text.style.TypefaceSpan;
import android.util.AttributeSet;
import androidx.annotation.Nullable;
@@ -17,15 +21,20 @@ import androidx.core.content.ContextCompat;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
import org.thoughtcrime.securesms.components.emoji.SimpleEmojiTextView;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.SpanUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.Objects;
public class FromTextView extends EmojiTextView {
public class FromTextView extends SimpleEmojiTextView {
private static final String TAG = Log.tag(FromTextView.class);
private final static Typeface BOLD_TYPEFACE = Typeface.create("sans-serif-medium", Typeface.NORMAL);
private final static Typeface LIGHT_TYPEFACE = Typeface.create("sans-serif", Typeface.NORMAL);
public FromTextView(Context context) {
super(context);
}
@@ -45,20 +54,9 @@ public class FromTextView extends EmojiTextView {
public void setText(Recipient recipient, boolean read, @Nullable String suffix) {
String fromString = recipient.getDisplayName(getContext());
int typeface;
if (!read) {
typeface = Typeface.BOLD;
} else {
typeface = Typeface.NORMAL;
}
SpannableStringBuilder builder = new SpannableStringBuilder();
SpannableString fromSpan = new SpannableString(fromString);
fromSpan.setSpan(new StyleSpan(typeface), 0, builder.length(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
SpannableStringBuilder builder = new SpannableStringBuilder();
SpannableString fromSpan = new SpannableString(fromString);
fromSpan.setSpan(getFontSpan(!read), 0, fromSpan.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
if (recipient.isSelf()) {
builder.append(getContext().getString(R.string.note_to_self));
@@ -85,4 +83,8 @@ public class FromTextView extends EmojiTextView {
return mutedDrawable;
}
private CharacterStyle getFontSpan(boolean isBold) {
return isBold ? SpanUtil.getBoldSpan() : SpanUtil.getNormalSpan();
}
}

View File

@@ -7,7 +7,7 @@ import androidx.appcompat.widget.AppCompatTextView
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.whispersystems.libsignal.util.guava.Optional
open class SingleLineEmojiTextView @JvmOverloads constructor(
open class SimpleEmojiTextView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
@@ -15,20 +15,16 @@ open class SingleLineEmojiTextView @JvmOverloads constructor(
private var bufferType: BufferType? = null
init {
maxLines = 1
}
override fun setText(text: CharSequence?, type: BufferType?) {
bufferType = type
val candidates = if (isInEditMode) null else EmojiProvider.getCandidates(text)
if (SignalStore.settings().isPreferSystemEmoji || candidates == null || candidates.size() == 0) {
super.setText(Optional.fromNullable(text).or(""), BufferType.NORMAL)
} else {
val newContent = if (width == 0) {
val newContent = if (width == 0 || maxLines == -1) {
text
} else {
TextUtils.ellipsize(text, paint, width.toFloat(), TextUtils.TruncateAt.END, false, null)
TextUtils.ellipsize(text, paint, (width * maxLines).toFloat(), TextUtils.TruncateAt.END, false, null)
}
val newCandidates = if (isInEditMode) null else EmojiProvider.getCandidates(newContent)
@@ -47,9 +43,4 @@ open class SingleLineEmojiTextView @JvmOverloads constructor(
setText(text, bufferType ?: BufferType.NORMAL)
}
}
override fun setMaxLines(maxLines: Int) {
check(maxLines == 1) { "setMaxLines: $maxLines != 1" }
super.setMaxLines(maxLines)
}
}

View File

@@ -836,8 +836,8 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
if (messageRequestAccepted) {
linkifyMessageBody(styledText, batchSelected.isEmpty());
}
styledText = SearchUtil.getHighlightedSpan(locale, () -> new BackgroundColorSpan(Color.YELLOW), styledText, searchQuery);
styledText = SearchUtil.getHighlightedSpan(locale, () -> new ForegroundColorSpan(Color.BLACK), styledText, searchQuery);
styledText = SearchUtil.getHighlightedSpan(locale, () -> new BackgroundColorSpan(Color.YELLOW), styledText, searchQuery, SearchUtil.STRICT);
styledText = SearchUtil.getHighlightedSpan(locale, () -> new ForegroundColorSpan(Color.BLACK), styledText, searchQuery, SearchUtil.STRICT);
if (hasExtraText(messageRecord)) {
bodyText.setOverflowText(getLongMessageSpan(messageRecord));

View File

@@ -101,7 +101,7 @@ class ConversationListAdapter extends ListAdapter<Conversation, RecyclerView.Vie
v.setLayoutParams(new FrameLayout.LayoutParams(1, ViewUtil.dpToPx(100)));
return new PlaceholderViewHolder(v);
} else if (viewType == TYPE_HEADER) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.conversation_list_item_header, parent, false);
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.dsl_section_header, parent, false);
return new HeaderViewHolder(v);
} else {
throw new IllegalStateException("Unknown type! " + viewType);
@@ -297,7 +297,7 @@ class ConversationListAdapter extends ListAdapter<Conversation, RecyclerView.Vie
public HeaderViewHolder(@NonNull View itemView) {
super(itemView);
headerText = (TextView) itemView;
headerText = itemView.findViewById(R.id.section_header);
}
}

View File

@@ -24,7 +24,6 @@ import android.os.Build;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.StyleSpan;
import android.text.style.TextAppearanceSpan;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
@@ -66,6 +65,7 @@ import org.thoughtcrime.securesms.util.Debouncer;
import org.thoughtcrime.securesms.util.ExpirationUtil;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.SearchUtil;
import org.thoughtcrime.securesms.util.SpanUtil;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import java.util.Collections;
@@ -102,6 +102,8 @@ public final class ConversationListItem extends ConstraintLayout
private long lastSeen;
private ThreadRecord thread;
private boolean batchMode;
private Locale locale;
private String highlightSubstring;
private int unreadCount;
private AvatarImageView contactPhotoImage;
@@ -158,17 +160,19 @@ public final class ConversationListItem extends ConstraintLayout
observeDisplayBody(null);
setSubjectViewText(null);
this.selectedThreads = selectedThreads;
this.threadId = thread.getThreadId();
this.glideRequests = glideRequests;
this.unreadCount = thread.getUnreadCount();
this.lastSeen = thread.getLastSeen();
this.thread = thread;
this.selectedThreads = selectedThreads;
this.threadId = thread.getThreadId();
this.glideRequests = glideRequests;
this.unreadCount = thread.getUnreadCount();
this.lastSeen = thread.getLastSeen();
this.thread = thread;
this.locale = locale;
this.highlightSubstring = highlightSubstring;
if (highlightSubstring != null) {
String name = recipient.get().isSelf() ? getContext().getString(R.string.note_to_self) : recipient.get().getDisplayName(getContext());
this.fromView.setText(SearchUtil.getHighlightedSpan(locale, () -> new StyleSpan(Typeface.BOLD), name, highlightSubstring));
this.fromView.setText(SearchUtil.getHighlightedSpan(locale, SpanUtil::getBoldSpan, name, highlightSubstring, SearchUtil.MATCH_ALL));
} else {
this.fromView.setText(recipient.get(), thread.isRead());
}
@@ -178,10 +182,6 @@ public final class ConversationListItem extends ConstraintLayout
observeDisplayBody(getThreadDisplayBody(getContext(), thread));
this.subjectView.setTypeface(thread.isRead() ? LIGHT_TYPEFACE : BOLD_TYPEFACE);
this.subjectView.setTextColor(thread.isRead() ? ContextCompat.getColor(getContext(), R.color.signal_text_secondary)
: ContextCompat.getColor(getContext(), R.color.signal_text_primary));
if (thread.getDate() > 0) {
CharSequence date = DateUtils.getBriefRelativeTimeSpanString(getContext(), locale, thread.getDate());
dateView.setText(date);
@@ -213,13 +213,13 @@ public final class ConversationListItem extends ConstraintLayout
observeDisplayBody(null);
setSubjectViewText(null);
this.selectedThreads = Collections.emptySet();
this.glideRequests = glideRequests;
this.selectedThreads = Collections.emptySet();
this.glideRequests = glideRequests;
this.locale = locale;
this.highlightSubstring = highlightSubstring;
fromView.setText(contact);
fromView.setText(SearchUtil.getHighlightedSpan(locale, () -> new StyleSpan(Typeface.BOLD), new SpannableString(fromView.getText()), highlightSubstring));
setSubjectViewText(SearchUtil.getHighlightedSpan(locale, () -> new StyleSpan(Typeface.BOLD), contact.getE164().or(""), highlightSubstring));
fromView.setText(SearchUtil.getHighlightedSpan(locale, SpanUtil::getBoldSpan, new SpannableString(contact.getDisplayName(getContext())), highlightSubstring, SearchUtil.MATCH_ALL));
setSubjectViewText(SearchUtil.getHighlightedSpan(locale, SpanUtil::getBoldSpan, contact.getE164().or(""), highlightSubstring, SearchUtil.MATCH_ALL));
dateView.setText("");
archivedView.setVisibility(GONE);
unreadIndicator.setVisibility(GONE);
@@ -241,11 +241,13 @@ public final class ConversationListItem extends ConstraintLayout
observeDisplayBody(null);
setSubjectViewText(null);
this.selectedThreads = Collections.emptySet();
this.glideRequests = glideRequests;
this.selectedThreads = Collections.emptySet();
this.glideRequests = glideRequests;
this.locale = locale;
this.highlightSubstring = highlightSubstring;
fromView.setText(recipient.get(), true);
setSubjectViewText(SearchUtil.getHighlightedSpan(locale, () -> new StyleSpan(Typeface.BOLD), messageResult.getBodySnippet(), highlightSubstring));
setSubjectViewText(SearchUtil.getHighlightedSpan(locale, SpanUtil::getBoldSpan, messageResult.getBodySnippet(), highlightSubstring, SearchUtil.MATCH_ALL));
dateView.setText(DateUtils.getBriefRelativeTimeSpanString(getContext(), locale, messageResult.getReceivedTimestampMs()));
archivedView.setVisibility(GONE);
unreadIndicator.setVisibility(GONE);
@@ -346,10 +348,10 @@ public final class ConversationListItem extends ConstraintLayout
private void setThumbnailSnippet(ThreadRecord thread) {
if (thread.getSnippetUri() != null) {
this.thumbnailView.setVisibility(View.VISIBLE);
this.thumbnailView.setVisibility(VISIBLE);
this.thumbnailView.setImageResource(glideRequests, thread.getSnippetUri());
} else {
this.thumbnailView.setVisibility(View.GONE);
this.thumbnailView.setVisibility(GONE);
}
}
@@ -401,11 +403,12 @@ public final class ConversationListItem extends ConstraintLayout
}
private void setUnreadIndicator(ThreadRecord thread) {
if ((thread.isOutgoing() && !thread.isForcedUnread()) || thread.isRead()) {
if ((thread.isOutgoing() && !thread.isForcedUnread()) || thread.isRead() || unreadCount == 0) {
unreadIndicator.setVisibility(View.GONE);
return;
}
String count = unreadCount > 100 ? String.valueOf(unreadCount) : "+99";
unreadIndicator.setText(unreadCount > 0 ? String.valueOf(unreadCount) : " ");
unreadIndicator.setVisibility(View.VISIBLE);
}
@@ -417,14 +420,18 @@ public final class ConversationListItem extends ConstraintLayout
return;
}
fromView.setText(recipient, unreadCount == 0);
if (highlightSubstring != null) {
String name = recipient.isSelf() ? getContext().getString(R.string.note_to_self) : recipient.getDisplayName(getContext());
fromView.setText(SearchUtil.getHighlightedSpan(locale, SpanUtil::getBoldSpan, new SpannableString(name), highlightSubstring, SearchUtil.MATCH_ALL));
} else {
fromView.setText(recipient, unreadCount == 0);
}
contactPhotoImage.setAvatar(glideRequests, recipient, !batchMode);
setRippleColor(recipient);
}
private static @NonNull LiveData<SpannableString> getThreadDisplayBody(@NonNull Context context, @NonNull ThreadRecord thread) {
int defaultTint = thread.isRead() ? ContextCompat.getColor(context, R.color.signal_text_secondary)
: ContextCompat.getColor(context, R.color.signal_text_primary);
int defaultTint = ContextCompat.getColor(context, R.color.signal_text_secondary);
if (!thread.isMessageRequestAccepted()) {
return emphasisAdded(context, context.getString(R.string.ThreadRecord_message_request), defaultTint);
@@ -520,8 +527,7 @@ public final class ConversationListItem extends ConstraintLayout
: recipient.getShortDisplayName(context)) + ": ";
SpannableString spannable = new SpannableString(sender + body);
spannable.setSpan(new TextAppearanceSpan(context, read ? R.style.Signal_Text_Preview_Medium_Secondary
: R.style.Signal_Text_Preview_Medium_Primary),
spannable.setSpan(SpanUtil.getBoldSpan(),
0,
sender.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

View File

@@ -96,7 +96,7 @@ class ConversationListSearchAdapter extends RecyclerView.Adapter<Conversation
@Override
public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent, int position, int type) {
return new HeaderViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.search_result_list_divider, parent, false));
.inflate(R.layout.dsl_section_header, parent, false));
}
@Override
@@ -198,7 +198,7 @@ class ConversationListSearchAdapter extends RecyclerView.Adapter<Conversation
public HeaderViewHolder(View itemView) {
super(itemView);
titleView = itemView.findViewById(R.id.label);
titleView = itemView.findViewById(R.id.section_header);
}
public void bind(int headerType) {

View File

@@ -12,6 +12,7 @@ import com.annimon.stream.Stream;
import org.whispersystems.libsignal.util.Pair;
import java.security.InvalidParameterException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@@ -19,10 +20,14 @@ import java.util.Locale;
public class SearchUtil {
public static final int STRICT = 0;
public static final int MATCH_ALL = 1;
public static Spannable getHighlightedSpan(@NonNull Locale locale,
@NonNull StyleFactory styleFactory,
@Nullable String text,
@Nullable String highlight)
@Nullable String highlight,
int matchMode)
{
if (TextUtils.isEmpty(text)) {
return new SpannableString("");
@@ -30,13 +35,14 @@ public class SearchUtil {
text = text.replaceAll("\n", " ");
return getHighlightedSpan(locale, styleFactory, new SpannableString(text), highlight);
return getHighlightedSpan(locale, styleFactory, new SpannableString(text), highlight, matchMode);
}
public static Spannable getHighlightedSpan(@NonNull Locale locale,
@NonNull StyleFactory styleFactory,
@Nullable Spannable text,
@Nullable String highlight)
@Nullable String highlight,
int matchMode)
{
if (TextUtils.isEmpty(text)) {
return new SpannableString("");
@@ -47,8 +53,24 @@ public class SearchUtil {
return text;
}
List<Pair<Integer, Integer>> ranges = getHighlightRanges(locale, text.toString(), highlight);
SpannableString spanned = new SpannableString(text);
List<Pair<Integer, Integer>> ranges;
switch (matchMode) {
case STRICT:
ranges = getStrictHighlightRanges(locale, text.toString(), highlight);
break;
case MATCH_ALL:
ranges = getHighlightRanges(locale, text.toString(), highlight);
break;
default:
throw new InvalidParameterException("match mode must be STRICT or MATCH_ALL: " + matchMode);
}
if (matchMode == STRICT) {
ranges = getStrictHighlightRanges(locale, text.toString(), highlight);
} else {
ranges = getHighlightRanges(locale, text.toString(), highlight);
}
for (Pair<Integer, Integer> range : ranges) {
spanned.setSpan(styleFactory.create(), range.first(), range.second(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
@@ -57,9 +79,9 @@ public class SearchUtil {
return spanned;
}
static List<Pair<Integer, Integer>> getHighlightRanges(@NonNull Locale locale,
@NonNull String text,
@NonNull String highlight)
static List<Pair<Integer, Integer>> getStrictHighlightRanges(@NonNull Locale locale,
@NonNull String text,
@NonNull String highlight)
{
if (text.length() == 0) {
return Collections.emptyList();
@@ -97,6 +119,39 @@ public class SearchUtil {
return ranges;
}
static List<Pair<Integer, Integer>> getHighlightRanges(@NonNull Locale locale,
@NonNull String text,
@NonNull String highlight)
{
if (text.length() == 0) {
return Collections.emptyList();
}
String normalizedText = text.toLowerCase(locale);
String normalizedHighlight = highlight.toLowerCase(locale);
List<String> highlightTokens = Stream.of(normalizedHighlight.split("\\s")).filter(s -> s.trim().length() > 0).toList();
List<Pair<Integer, Integer>> ranges = new LinkedList<>();
int lastHighlightEndIndex = 0;
for (String highlightToken : highlightTokens) {
int index = 0;
lastHighlightEndIndex = 0;
while (index != -1) {
index = normalizedText.indexOf(highlightToken, lastHighlightEndIndex);
if (index != -1) {
lastHighlightEndIndex = index + highlightToken.length();
ranges.add(new Pair<>(index, lastHighlightEndIndex));
index = lastHighlightEndIndex;
}
}
}
return ranges;
}
public interface StyleFactory {
CharacterStyle create();
}

View File

@@ -13,12 +13,15 @@ import android.text.TextPaint;
import android.text.TextUtils;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.BulletSpan;
import android.text.style.CharacterStyle;
import android.text.style.ClickableSpan;
import android.text.style.DynamicDrawableSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.ImageSpan;
import android.text.style.MetricAffectingSpan;
import android.text.style.RelativeSizeSpan;
import android.text.style.StyleSpan;
import android.text.style.TypefaceSpan;
import android.view.View;
import androidx.annotation.ColorInt;
@@ -34,6 +37,9 @@ public final class SpanUtil {
public static final String SPAN_PLACE_HOLDER = "<<<SPAN>>>";
private final static Typeface BOLD_TYPEFACE = Typeface.create("sans-serif-medium", Typeface.NORMAL);
private final static Typeface LIGHT_TYPEFACE = Typeface.create("sans-serif", Typeface.NORMAL);
public static CharSequence italic(CharSequence sequence) {
return italic(sequence, sequence.length());
}
@@ -205,4 +211,20 @@ public final class SpanUtil {
builder.replace(index, index + SpanUtil.SPAN_PLACE_HOLDER.length(), span);
return builder;
}
public static CharacterStyle getBoldSpan() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
return new TypefaceSpan(BOLD_TYPEFACE);
} else {
return new StyleSpan(Typeface.BOLD);
}
}
public static CharacterStyle getNormalSpan() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
return new TypefaceSpan(LIGHT_TYPEFACE);
} else {
return new StyleSpan(Typeface.NORMAL);
}
}
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="@dimen/unread_count_bubble_radius" />
<solid android:color="@color/core_ultramarine" />
<stroke
android:color="@color/core_grey_95"
android:width="2.5dp"/>
</shape>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="@dimen/unread_count_bubble_radius" />
<solid android:color="@color/core_ultramarine" />
<stroke
android:color="@color/core_white"
android:width="2.5dp"/>
</shape>

View File

@@ -25,6 +25,7 @@
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarStyle"
android:visibility="gone"
app:contentInsetStart="0dp"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible">
@@ -34,14 +35,11 @@
<org.thoughtcrime.securesms.components.AvatarImageView
android:id="@+id/toolbar_icon"
android:layout_width="58dp"
android:layout_height="48dp"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_alignParentStart="true"
android:contentDescription="@string/conversation_list_settings_shortcut"
android:paddingStart="12dp"
android:paddingTop="10dp"
android:paddingEnd="18dp"
android:paddingBottom="10dp"
android:layout_marginStart="@dimen/toolbar_avatar_margin"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
@@ -50,11 +48,11 @@
<View
android:id="@+id/unread_payments_indicator"
android:layout_width="13dp"
android:layout_height="13dp"
android:layout_marginStart="30dp"
android:layout_marginBottom="29dp"
android:layout_height="13dp"
android:background="@drawable/unread_count_background"
android:alpha="0"
android:layout_marginStart="20dp"
android:layout_marginBottom="20dp"
app:layout_constraintBottom_toBottomOf="@id/toolbar_icon"
app:layout_constraintStart_toStartOf="@id/toolbar_icon"
tools:alpha="1" />
@@ -63,7 +61,7 @@
android:id="@+id/conversation_list_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginStart="26dp"
android:text="@string/app_name"
android:textAlignment="viewStart"
android:textColor="@color/signal_text_primary"

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="40dp"
android:gravity="start|center_vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:textAppearance="@style/TextAppearance.Signal.Body2"
android:fontFamily="sans-serif-medium"
android:textColor="@color/signal_text_secondary"
tools:text="Chats" />

View File

@@ -4,56 +4,58 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="72dp"
android:layout_height="86dp"
android:background="@drawable/conversation_list_item_background"
android:focusable="true"
android:nextFocusLeft="@+id/container"
android:nextFocusRight="@+id/fab"
android:paddingStart="16dp"
android:paddingEnd="16dp">
android:paddingStart="@dimen/dsl_settings_gutter"
android:paddingEnd="@dimen/dsl_settings_gutter">
<org.thoughtcrime.securesms.components.AvatarImageView
android:id="@+id/conversation_list_item_avatar"
android:layout_width="52dp"
android:layout_height="52dp"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="@string/conversation_list_item_view__contact_photo_image"
android:foreground="@drawable/contact_photo_background"
android:layout_marginTop="12dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
tools:src="@drawable/ic_contact_picture" />
<TextView
android:id="@+id/conversation_list_item_unread_indicator"
style="@style/Signal.Text.Caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/unread_count_background"
android:layout_height="@dimen/unread_count_bubble_diameter"
android:background="@drawable/unread_count_background_new"
android:fontFamily="sans-serif-medium"
android:gravity="center"
android:paddingStart="7dp"
android:paddingEnd="7dp"
android:minWidth="@dimen/unread_count_bubble_diameter"
android:padding="5sp"
android:minHeight="@dimen/unread_count_bubble_diameter"
android:textColor="@color/core_white"
app:layout_constraintEnd_toEndOf="@id/conversation_list_item_avatar"
app:layout_constraintTop_toTopOf="@id/conversation_list_item_avatar"
tools:text="1" />
android:layout_marginBottom="27dp"
app:layout_constraintEnd_toStartOf="@id/conversation_list_item_avatar_barrier"
app:layout_constraintBottom_toBottomOf="@id/conversation_list_item_avatar"
tools:text="9" />
<org.thoughtcrime.securesms.components.FromTextView
android:id="@+id/conversation_list_item_name"
style="@style/Signal.Text.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="2dp"
android:layout_marginStart="8dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="12dp"
android:drawablePadding="5dp"
android:ellipsize="end"
android:fontFamily="sans-serif-medium"
android:fontFamily="sans-serif"
android:maxLines="1"
android:textColor="@color/signal_text_primary"
app:layout_constraintStart_toEndOf="@id/conversation_list_item_avatar"
app:layout_constraintEnd_toStartOf="@id/conversation_list_item_thumbnail"
app:layout_constraintTop_toTopOf="@id/conversation_list_item_avatar"
app:layout_constraintBottom_toTopOf="@id/conversation_list_item_summary"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Peter Parker" />
@@ -65,24 +67,24 @@
android:paddingTop="2dp"
android:visibility="gone"
app:useSmallIcon="true"
app:layout_constraintTop_toTopOf="@id/conversation_list_item_summary"
app:layout_constraintBottom_toBottomOf="@id/conversation_list_item_summary"
app:layout_constraintTop_toBottomOf="@id/conversation_list_item_name"
app:layout_constraintStart_toStartOf="@id/conversation_list_item_name"
tools:visibility="visible" />
<org.thoughtcrime.securesms.components.emoji.SingleLineEmojiTextView
<org.thoughtcrime.securesms.components.emoji.SimpleEmojiTextView
android:id="@+id/conversation_list_item_summary"
style="@style/Signal.Text.Preview"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:maxLines="2"
android:lines="2"
android:textColor="@color/signal_text_secondary"
android:layout_marginEnd="12dp"
tools:text="I'll send those photos over to the Bugle ASAP."
app:layout_constraintTop_toBottomOf="@id/conversation_list_item_name"
app:layout_constraintBottom_toBottomOf="@id/conversation_list_item_avatar"
app:layout_constraintStart_toEndOf="@id/conversation_list_item_alert"
app:layout_constraintEnd_toStartOf="@id/conversation_list_item_status_barrier"/>
app:layout_constraintEnd_toStartOf="@id/conversation_list_item_summary_barrier"/>
<org.thoughtcrime.securesms.components.TypingIndicatorView
android:id="@+id/conversation_list_item_typing_indicator"
@@ -97,18 +99,17 @@
<org.thoughtcrime.securesms.components.ThumbnailView
android:id="@+id/conversation_list_item_thumbnail"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="5dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="5dp"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:layout_toStartOf="@+id/conversation_list_item_date"
android:contentDescription="@string/conversation_activity__attachment_thumbnail"
android:visibility="gone"
tools:visibility="visible"
app:layout_constraintTop_toTopOf="@id/conversation_list_item_avatar"
app:layout_constraintBottom_toBottomOf="@id/conversation_list_item_avatar"
app:layout_constraintEnd_toStartOf="@id/conversation_list_item_metadata_barrier"/>
tools:visibility="gone"
app:thumbnail_radius="8dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@id/conversation_list_item_date"/>
<TextView
android:id="@id/conversation_list_item_date"
@@ -117,52 +118,58 @@
android:layout_height="wrap_content"
android:singleLine="true"
android:textColor="@color/signal_icon_tint_secondary"
tools:text="Now"
android:layout_marginTop="2dp"
tools:text="10:00 am"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/conversation_list_item_name"
app:layout_constraintBottom_toBottomOf="@id/conversation_list_item_name"/>
<TextView
android:id="@+id/conversation_list_item_archived"
style="@style/Signal.Text.Caption"
<LinearLayout
android:id="@+id/conversation_list_item_status_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/archived_indicator_background"
android:paddingStart="6dp"
android:paddingTop="2dp"
android:paddingEnd="6dp"
android:paddingBottom="2dp"
android:text="@string/conversation_list_item_view__archived"
android:textColor="@color/core_white"
app:layout_constraintTop_toTopOf="@id/conversation_list_item_status"
app:layout_constraintBottom_toBottomOf="@id/conversation_list_item_status"
app:layout_constraintEnd_toEndOf="parent"/>
android:layout_marginStart="12dp"
android:layout_marginTop="6dp"
android:gravity="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/conversation_list_item_date">
<org.thoughtcrime.securesms.components.DeliveryStatusView
android:id="@+id/conversation_list_item_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="5dp"
app:layout_goneMarginEnd="0dp"
app:iconColor="@color/signal_icon_tint_secondary"
app:layout_constraintEnd_toStartOf="@+id/conversation_list_item_archived"
app:layout_constraintTop_toTopOf="@id/conversation_list_item_summary"
app:layout_constraintBottom_toBottomOf="@id/conversation_list_item_summary"/>
<org.thoughtcrime.securesms.components.DeliveryStatusView
android:id="@+id/conversation_list_item_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
app:iconColor="@color/signal_icon_tint_secondary" />
<TextView
android:id="@+id/conversation_list_item_archived"
style="@style/Signal.Text.Caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:background="@drawable/archived_indicator_background"
android:paddingStart="6dp"
android:paddingEnd="6dp"
android:text="@string/conversation_list_item_view__archived"
android:textColor="@color/core_white"
tools:visibility="gone" />
</LinearLayout>
<androidx.constraintlayout.widget.Barrier
android:id="@+id/conversation_list_item_metadata_barrier"
android:id="@+id/conversation_list_item_avatar_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="start"
app:constraint_referenced_ids="conversation_list_item_status,conversation_list_item_archived,conversation_list_item_date"/>
android:layout_height="match_parent"
app:barrierDirection="end"
app:constraint_referenced_ids="conversation_list_item_avatar"
app:barrierMargin="@dimen/unread_count_bubble_barrier_margin"/>
<androidx.constraintlayout.widget.Barrier
android:id="@+id/conversation_list_item_status_barrier"
android:id="@+id/conversation_list_item_summary_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="match_parent"
app:barrierDirection="start"
app:constraint_referenced_ids="conversation_list_item_status,conversation_list_item_thumbnail"/>
app:barrierAllowsGoneWidgets="false"
app:constraint_referenced_ids="conversation_list_item_thumbnail,conversation_list_item_status_container" />
</org.thoughtcrime.securesms.conversationlist.ConversationListItem>

View File

@@ -14,6 +14,5 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.Signal.Body1.Bold"
android:textStyle="bold"
tools:text="Section Header" />
</FrameLayout>

View File

@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="16dp"
android:paddingBottom="24dp"
style="@style/Signal.Text.Preview"
android:fontFamily="sans-serif-medium"
tools:text="@string/CameraContacts_recent_contacts"/>

View File

@@ -27,7 +27,7 @@
tools:alpha="1"
tools:visibility="visible" />
<org.thoughtcrime.securesms.components.emoji.SingleLineEmojiTextView
<org.thoughtcrime.securesms.components.emoji.SimpleEmojiTextView
android:id="@+id/recipient"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -43,6 +43,7 @@
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textColor="@color/core_white"
android:visibility="gone"
android:maxLines="1"
app:drawableStartCompat="@drawable/ic_arrow_right_16"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="unread_count_bubble_barrier_margin">-4dp</dimen>
</resources>

View File

@@ -29,4 +29,6 @@
<dimen name="conversation_settings_button_strip_button_padding">16dp</dimen>
<dimen name="avatar_picker_image_width">160dp</dimen>
<dimen name="toolbar_avatar_margin">34dp</dimen>
</resources>

View File

@@ -108,8 +108,9 @@
<dimen name="conversation_list_fragment_archive_padding">16dp</dimen>
<dimen name="contact_selection_actions_tap_area">10dp</dimen>
<dimen name="unread_count_bubble_radius">13sp</dimen>
<dimen name="unread_count_bubble_diameter">26sp</dimen>
<dimen name="unread_count_bubble_radius">12.5dp</dimen>
<dimen name="unread_count_bubble_diameter">25dp</dimen>
<dimen name="unread_count_bubble_barrier_margin">4dp</dimen>
<dimen name="insights_modal_title_margin_top">41dp</dimen>
<dimen name="insights_modal_description_margin_top">12dp</dimen>
@@ -200,4 +201,6 @@
<dimen name="conversation_settings_button_strip_button_padding">12dp</dimen>
<dimen name="avatar_picker_image_width">100dp</dimen>
<dimen name="toolbar_avatar_margin">26dp</dimen>
</resources>

View File

@@ -14,6 +14,64 @@ public class SearchUtilTest {
private static final Locale LOCALE = Locale.ENGLISH;
@Test
public void getStrictHighlightRanges_singleHighlightToken() {
String text = "abc";
String highlight = "a";
List<Pair<Integer, Integer>> result = SearchUtil.getStrictHighlightRanges(LOCALE, text, highlight);
assertEquals(Arrays.asList(new Pair<>(0, 1)), result);
}
@Test
public void getStrictHighlightRanges_singleHighlightTokenWithNewLines() {
String text = "123\n\n\nabc";
String highlight = "a";
List<Pair<Integer, Integer>> result = SearchUtil.getStrictHighlightRanges(LOCALE, text, highlight);
assertEquals(Arrays.asList(new Pair<>(6, 7)), result);
}
@Test
public void getStrictHighlightRanges_multipleHighlightTokens() {
String text = "a bc";
String highlight = "a b";
List<Pair<Integer, Integer>> result = SearchUtil.getStrictHighlightRanges(LOCALE, text, highlight);
assertEquals(Arrays.asList(new Pair<>(0, 1), new Pair<>(2, 3)), result);
text = "abc def";
highlight = "ab de";
result = SearchUtil.getStrictHighlightRanges(LOCALE, text, highlight);
assertEquals(Arrays.asList(new Pair<>(0, 2), new Pair<>(4, 6)), result);
}
@Test
public void getStrictHighlightRanges_onlyHighlightPrefixes() {
String text = "abc";
String highlight = "b";
List<Pair<Integer, Integer>> result = SearchUtil.getStrictHighlightRanges(LOCALE, text, highlight);
assertTrue(result.isEmpty());
text = "abc";
highlight = "c";
result = SearchUtil.getStrictHighlightRanges(LOCALE, text, highlight);
assertTrue(result.isEmpty());
}
@Test
public void getStrictHighlightRanges_resultNotInFirstToken() {
String text = "abc def ghi";
String highlight = "gh";
List<Pair<Integer, Integer>> result = SearchUtil.getStrictHighlightRanges(LOCALE, text, highlight);
assertEquals(Arrays.asList(new Pair<>(8, 10)), result);
}
@Test
public void getHighlightRanges_singleHighlightToken() {
String text = "abc";
@@ -24,51 +82,20 @@ public class SearchUtilTest {
}
@Test
public void getHighlightRanges_singleHighlightTokenWithNewLines() {
String text = "123\n\n\nabc";
public void getHighlightRanges_singleHighlightTokenMultipleMatches() {
String text = "blabla";
String highlight = "a";
List<Pair<Integer, Integer>> result = SearchUtil.getHighlightRanges(LOCALE, text, highlight);
assertEquals(Arrays.asList(new Pair<>(6, 7)), result);
assertEquals(Arrays.asList(new Pair<>(2, 3), new Pair<>(5, 6)), result);
}
@Test
public void getHighlightRanges_multipleHighlightTokens() {
String text = "a bc";
String highlight = "a b";
public void getHighlightRanges_multipleHighlightTokenMultipleMatches() {
String text = "test search test string";
String highlight = "test str";
List<Pair<Integer, Integer>> result = SearchUtil.getHighlightRanges(LOCALE, text, highlight);
assertEquals(Arrays.asList(new Pair<>(0, 1), new Pair<>(2, 3)), result);
text = "abc def";
highlight = "ab de";
result = SearchUtil.getHighlightRanges(LOCALE, text, highlight);
assertEquals(Arrays.asList(new Pair<>(0, 2), new Pair<>(4, 6)), result);
}
@Test
public void getHighlightRanges_onlyHighlightPrefixes() {
String text = "abc";
String highlight = "b";
List<Pair<Integer, Integer>> result = SearchUtil.getHighlightRanges(LOCALE, text, highlight);
assertTrue(result.isEmpty());
text = "abc";
highlight = "c";
result = SearchUtil.getHighlightRanges(LOCALE, text, highlight);
assertTrue(result.isEmpty());
}
@Test
public void getHighlightRanges_resultNotInFirstToken() {
String text = "abc def ghi";
String highlight = "gh";
List<Pair<Integer, Integer>> result = SearchUtil.getHighlightRanges(LOCALE, text, highlight);
assertEquals(Arrays.asList(new Pair<>(8, 10)), result);
assertEquals(Arrays.asList(new Pair<>(0, 4), new Pair<>(12, 16), new Pair<>(17,20)), result);
}
}