mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-26 19:56:02 +01:00
Refresh contact search views.
This commit is contained in:
committed by
Greyson Parrelli
parent
a4d458f969
commit
a157c1ae1d
@@ -108,15 +108,31 @@ public class ContactRepository {
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public Cursor querySignalContacts(@NonNull String query) {
|
||||
public @NonNull Cursor querySignalContacts(@NonNull String query) {
|
||||
return querySignalContacts(query, true);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public Cursor querySignalContacts(@NonNull String query, boolean includeSelf) {
|
||||
Cursor cursor = TextUtils.isEmpty(query) ? recipientDatabase.getSignalContacts(includeSelf)
|
||||
: recipientDatabase.querySignalContacts(query, includeSelf);
|
||||
public @NonNull Cursor querySignalContacts(@NonNull String query, boolean includeSelf) {
|
||||
Cursor cursor = TextUtils.isEmpty(query) ? recipientDatabase.getSignalContacts(includeSelf)
|
||||
: recipientDatabase.querySignalContacts(query, includeSelf);
|
||||
|
||||
cursor = handleNoteToSelfQuery(query, includeSelf, cursor);
|
||||
|
||||
return new SearchCursorWrapper(cursor, SEARCH_CURSOR_MAPPERS);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
public @NonNull Cursor queryNonGroupContacts(@NonNull String query, boolean includeSelf) {
|
||||
Cursor cursor = TextUtils.isEmpty(query) ? recipientDatabase.getNonGroupContacts(includeSelf)
|
||||
: recipientDatabase.queryNonGroupContacts(query, includeSelf);
|
||||
|
||||
cursor = handleNoteToSelfQuery(query, includeSelf, cursor);
|
||||
|
||||
return new SearchCursorWrapper(cursor, SEARCH_CURSOR_MAPPERS);
|
||||
}
|
||||
|
||||
private @NonNull Cursor handleNoteToSelfQuery(@NonNull String query, boolean includeSelf, Cursor cursor) {
|
||||
if (includeSelf && noteToSelfTitle.toLowerCase().contains(query.toLowerCase())) {
|
||||
Recipient self = Recipient.self();
|
||||
boolean nameMatch = self.getDisplayName(context).toLowerCase().contains(query.toLowerCase());
|
||||
@@ -130,8 +146,7 @@ public class ContactRepository {
|
||||
cursor = cursor == null ? selfCursor : new MergeCursor(new Cursor[]{ cursor, selfCursor });
|
||||
}
|
||||
}
|
||||
|
||||
return new SearchCursorWrapper(cursor, SEARCH_CURSOR_MAPPERS);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* <p>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* <p>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
@@ -21,6 +21,7 @@ import android.database.Cursor;
|
||||
import android.provider.ContactsContract;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -29,7 +30,6 @@ import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
@@ -40,12 +40,15 @@ import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter.ViewHolde
|
||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.CharacterIterable;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration.StickyHeaderAdapter;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -54,8 +57,8 @@ import java.util.Set;
|
||||
* @author Jake McGinty
|
||||
*/
|
||||
public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewHolder>
|
||||
implements FastScrollAdapter,
|
||||
StickyHeaderAdapter<HeaderViewHolder>
|
||||
implements FastScrollAdapter,
|
||||
StickyHeaderAdapter<HeaderViewHolder>
|
||||
{
|
||||
@SuppressWarnings("unused")
|
||||
private final static String TAG = Log.tag(ContactSelectionListAdapter.class);
|
||||
@@ -98,14 +101,28 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
public abstract void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, String about, int color, boolean checkboxVisible);
|
||||
public abstract void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, String about, boolean checkboxVisible);
|
||||
|
||||
public abstract void unbind(@NonNull GlideRequests glideRequests);
|
||||
|
||||
public abstract void setChecked(boolean checked);
|
||||
|
||||
public void animateChecked(boolean checked) {
|
||||
// Intentionally empty.
|
||||
}
|
||||
|
||||
public abstract void setEnabled(boolean enabled);
|
||||
|
||||
public void setLetterHeaderCharacter(@Nullable String letterHeaderCharacter) {
|
||||
// Intentionally empty.
|
||||
}
|
||||
}
|
||||
|
||||
public static class ContactViewHolder extends ViewHolder {
|
||||
ContactViewHolder(@NonNull final View itemView,
|
||||
public static class ContactViewHolder extends ViewHolder implements LetterHeaderDecoration.LetterHeaderItem {
|
||||
|
||||
private String letterHeader;
|
||||
|
||||
ContactViewHolder(@NonNull final View itemView,
|
||||
@Nullable final ItemClickListener clickListener)
|
||||
{
|
||||
super(itemView);
|
||||
@@ -118,8 +135,8 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
return (ContactSelectionListItem) itemView;
|
||||
}
|
||||
|
||||
public void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, String about, int color, boolean checkBoxVisible) {
|
||||
getView().set(glideRequests, recipientId, type, name, number, label, about, color, checkBoxVisible);
|
||||
public void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, String about, boolean checkBoxVisible) {
|
||||
getView().set(glideRequests, recipientId, type, name, number, label, about, checkBoxVisible);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -129,13 +146,28 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
|
||||
@Override
|
||||
public void setChecked(boolean checked) {
|
||||
getView().setChecked(checked);
|
||||
getView().setChecked(checked, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void animateChecked(boolean checked) {
|
||||
getView().setChecked(checked, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
getView().setEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable String getHeaderLetter() {
|
||||
return letterHeader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLetterHeaderCharacter(@Nullable String letterHeaderCharacter) {
|
||||
this.letterHeader = letterHeaderCharacter;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DividerViewHolder extends ViewHolder {
|
||||
@@ -148,7 +180,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, String about, int color, boolean checkboxVisible) {
|
||||
public void bind(@NonNull GlideRequests glideRequests, @Nullable RecipientId recipientId, int type, String name, String number, String label, String about, boolean checkboxVisible) {
|
||||
this.label.setText(name);
|
||||
}
|
||||
|
||||
@@ -168,15 +200,15 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
}
|
||||
}
|
||||
|
||||
public ContactSelectionListAdapter(@NonNull Context context,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
public ContactSelectionListAdapter(@NonNull Context context,
|
||||
@NonNull GlideRequests glideRequests,
|
||||
@Nullable Cursor cursor,
|
||||
@Nullable ItemClickListener clickListener,
|
||||
boolean multiSelect,
|
||||
@NonNull Set<RecipientId> currentContacts)
|
||||
{
|
||||
super(context, cursor);
|
||||
this.layoutInflater = LayoutInflater.from(context);
|
||||
this.layoutInflater = LayoutInflater.from(context);
|
||||
this.glideRequests = glideRequests;
|
||||
this.multiSelect = multiSelect;
|
||||
this.clickListener = clickListener;
|
||||
@@ -186,7 +218,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
@Override
|
||||
public long getHeaderId(int i) {
|
||||
if (!isActiveCursor()) return -1;
|
||||
else if (i == -1) return -1;
|
||||
else if (i == -1) return -1;
|
||||
|
||||
int contactType = getContactType(i);
|
||||
|
||||
@@ -215,15 +247,10 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
String label = CursorUtil.requireString(cursor, ContactRepository.LABEL_COLUMN);
|
||||
String labelText = ContactsContract.CommonDataKinds.Phone.getTypeLabel(getContext().getResources(),
|
||||
numberType, label).toString();
|
||||
boolean isPush = (contactType & ContactRepository.PUSH_TYPE) > 0;
|
||||
|
||||
int color = isPush ? ContextCompat.getColor(getContext(), R.color.signal_text_primary)
|
||||
: ContextCompat.getColor(getContext(), R.color.signal_inverse_transparent_60);
|
||||
|
||||
boolean currentContact = currentContacts.contains(id);
|
||||
|
||||
viewHolder.unbind(glideRequests);
|
||||
viewHolder.bind(glideRequests, id, contactType, name, number, labelText, about, color, multiSelect || currentContact);
|
||||
viewHolder.bind(glideRequests, id, contactType, name, number, labelText, about, multiSelect || currentContact);
|
||||
viewHolder.setEnabled(true);
|
||||
|
||||
if (currentContact) {
|
||||
@@ -234,6 +261,54 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
} else {
|
||||
viewHolder.setChecked(selectedContacts.contains(SelectedContact.forPhone(id, number)));
|
||||
}
|
||||
|
||||
if (isContactRow(contactType)) {
|
||||
int position = cursor.getPosition();
|
||||
if (position == 0) {
|
||||
viewHolder.setLetterHeaderCharacter(getHeaderLetterForDisplayName(cursor));
|
||||
} else {
|
||||
cursor.moveToPrevious();
|
||||
|
||||
int previousRowContactType = CursorUtil.requireInt(cursor, ContactRepository.CONTACT_TYPE_COLUMN);
|
||||
|
||||
if (!isContactRow(previousRowContactType)) {
|
||||
cursor.moveToNext();
|
||||
viewHolder.setLetterHeaderCharacter(getHeaderLetterForDisplayName(cursor));
|
||||
} else {
|
||||
String previousHeaderLetter = getHeaderLetterForDisplayName(cursor);
|
||||
cursor.moveToNext();
|
||||
String newHeaderLetter = getHeaderLetterForDisplayName(cursor);
|
||||
|
||||
if (Objects.equals(previousHeaderLetter, newHeaderLetter)) {
|
||||
viewHolder.setLetterHeaderCharacter(null);
|
||||
} else {
|
||||
viewHolder.setLetterHeaderCharacter(newHeaderLetter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isContactRow(int contactType) {
|
||||
return (contactType & (ContactRepository.NEW_PHONE_TYPE | ContactRepository.NEW_USERNAME_TYPE | ContactRepository.DIVIDER_TYPE)) == 0;
|
||||
}
|
||||
|
||||
private @Nullable String getHeaderLetterForDisplayName(@NonNull Cursor cursor) {
|
||||
String name = CursorUtil.requireString(cursor, ContactRepository.NAME_COLUMN);
|
||||
Iterator<String> characterIterator = new CharacterIterable(name).iterator();
|
||||
|
||||
if (!TextUtils.isEmpty(name) && characterIterator.hasNext()) {
|
||||
String next = characterIterator.next();
|
||||
|
||||
if (Character.isLetter(next.codePointAt(0))) {
|
||||
return next.toUpperCase();
|
||||
} else {
|
||||
return "#";
|
||||
}
|
||||
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -250,12 +325,12 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
viewHolder.setEnabled(true);
|
||||
|
||||
if (currentContacts.contains(id)) {
|
||||
viewHolder.setChecked(true);
|
||||
viewHolder.animateChecked(true);
|
||||
viewHolder.setEnabled(false);
|
||||
} else if (numberType == ContactRepository.NEW_USERNAME_TYPE) {
|
||||
viewHolder.setChecked(selectedContacts.contains(SelectedContact.forUsername(id, number)));
|
||||
viewHolder.animateChecked(selectedContacts.contains(SelectedContact.forUsername(id, number)));
|
||||
} else {
|
||||
viewHolder.setChecked(selectedContacts.contains(SelectedContact.forPhone(id, number)));
|
||||
viewHolder.animateChecked(selectedContacts.contains(SelectedContact.forPhone(id, number)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,7 +350,7 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
|
||||
@Override
|
||||
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position, int type) {
|
||||
((TextView)viewHolder.itemView).setText(getSpannedHeaderString(position));
|
||||
((TextView) viewHolder.itemView).setText(getSpannedHeaderString(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -301,6 +376,10 @@ public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewH
|
||||
return selectedContacts.size();
|
||||
}
|
||||
|
||||
public int getCurrentContactsCount() {
|
||||
return currentContacts.size();
|
||||
}
|
||||
|
||||
private CharSequence getSpannedHeaderString(int position) {
|
||||
final String headerString = getHeaderString(position);
|
||||
if (isPush(position)) {
|
||||
|
||||
@@ -34,6 +34,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
|
||||
private FromTextView nameView;
|
||||
private TextView labelView;
|
||||
private CheckBox checkBox;
|
||||
private View smsTag;
|
||||
|
||||
private String number;
|
||||
private String chipName;
|
||||
@@ -61,6 +62,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
|
||||
this.labelView = findViewById(R.id.label);
|
||||
this.nameView = findViewById(R.id.name);
|
||||
this.checkBox = findViewById(R.id.check_box);
|
||||
this.smsTag = findViewById(R.id.sms_tag);
|
||||
|
||||
ViewUtil.setTextViewGravityStart(this.nameView, getContext());
|
||||
}
|
||||
@@ -72,7 +74,6 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
|
||||
String number,
|
||||
String label,
|
||||
String about,
|
||||
int color,
|
||||
boolean checkboxVisible)
|
||||
{
|
||||
this.glideRequests = glideRequests;
|
||||
@@ -92,10 +93,15 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
|
||||
name = this.recipient.get().getDisplayName(getContext());
|
||||
}
|
||||
|
||||
boolean isPush = (contactType & ContactRepository.PUSH_TYPE) > 0;
|
||||
if (isPush) {
|
||||
smsTag.setVisibility(GONE);
|
||||
} else {
|
||||
smsTag.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
Recipient recipientSnapshot = recipient != null ? recipient.get() : null;
|
||||
|
||||
this.nameView.setTextColor(color);
|
||||
this.numberView.setTextColor(color);
|
||||
if (recipientSnapshot == null || recipientSnapshot.isResolving()) {
|
||||
this.contactPhotoImage.setAvatar(glideRequests, null, false);
|
||||
setText(null, type, name, number, label, about);
|
||||
@@ -107,8 +113,20 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
|
||||
this.checkBox.setVisibility(checkboxVisible ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
public void setChecked(boolean selected) {
|
||||
this.checkBox.setChecked(selected);
|
||||
public void setChecked(boolean selected, boolean animate) {
|
||||
boolean wasSelected = checkBox.isChecked();
|
||||
|
||||
if (wasSelected != selected) {
|
||||
checkBox.setChecked(selected);
|
||||
|
||||
float alpha = selected ? 1f : 0f;
|
||||
if (animate) {
|
||||
checkBox.animate().setDuration(250L).alpha(alpha);
|
||||
} else {
|
||||
checkBox.animate().cancel();
|
||||
checkBox.setAlpha(alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -146,7 +164,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF
|
||||
} else {
|
||||
this.numberView.setText(!Util.isEmpty(about) ? about : number);
|
||||
this.nameView.setEnabled(true);
|
||||
this.labelView.setText(label != null && !label.equals("null") ? label : "");
|
||||
this.labelView.setText(label != null && !label.equals("null") ? getResources().getString(R.string.ContactSelectionListItem__dot_s, label) : "");
|
||||
this.labelView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms.contacts;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
@@ -30,7 +29,6 @@ import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.phonenumbers.NumberUtil;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
@@ -126,7 +124,9 @@ public class ContactsCursorLoader extends AbstractContactsCursorLoader {
|
||||
List<Cursor> contacts = getContactsCursors();
|
||||
|
||||
if (!isCursorListEmpty(contacts)) {
|
||||
cursorList.add(ContactsCursorRows.forContactsHeader(getContext()));
|
||||
if (!getFilter().isEmpty() || recents) {
|
||||
cursorList.add(ContactsCursorRows.forContactsHeader(getContext()));
|
||||
}
|
||||
cursorList.addAll(contacts);
|
||||
}
|
||||
}
|
||||
@@ -195,19 +195,14 @@ public class ContactsCursorLoader extends AbstractContactsCursorLoader {
|
||||
private List<Cursor> getContactsCursors() {
|
||||
List<Cursor> cursorList = new ArrayList<>(2);
|
||||
|
||||
if (!Permissions.hasAny(getContext(), Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)) {
|
||||
return cursorList;
|
||||
}
|
||||
|
||||
if (pushEnabled(mode)) {
|
||||
cursorList.add(contactRepository.querySignalContacts(getFilter(), selfEnabled(mode)));
|
||||
}
|
||||
|
||||
if (pushEnabled(mode) && smsEnabled(mode)) {
|
||||
cursorList.add(contactRepository.queryNonSignalContacts(getFilter()));
|
||||
cursorList.add(contactRepository.queryNonGroupContacts(getFilter(), selfEnabled(mode)));
|
||||
} else if (pushEnabled(mode)) {
|
||||
cursorList.add(contactRepository.querySignalContacts(getFilter(), selfEnabled(mode)));
|
||||
} else if (smsEnabled(mode)) {
|
||||
cursorList.add(filterNonPushContacts(contactRepository.queryNonSignalContacts(getFilter())));
|
||||
cursorList.add(contactRepository.queryNonSignalContacts(getFilter()));
|
||||
}
|
||||
|
||||
return cursorList;
|
||||
}
|
||||
|
||||
@@ -240,25 +235,6 @@ public class ContactsCursorLoader extends AbstractContactsCursorLoader {
|
||||
}
|
||||
}
|
||||
|
||||
private @NonNull Cursor filterNonPushContacts(@NonNull Cursor cursor) {
|
||||
try {
|
||||
final long startMillis = System.currentTimeMillis();
|
||||
final MatrixCursor matrix = ContactsCursorRows.createMatrixCursor();
|
||||
while (cursor.moveToNext()) {
|
||||
final RecipientId id = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(ContactRepository.ID_COLUMN)));
|
||||
final Recipient recipient = Recipient.resolved(id);
|
||||
|
||||
if (recipient.resolve().getRegistered() != RecipientDatabase.RegisteredState.REGISTERED) {
|
||||
matrix.addRow(ContactsCursorRows.forNonPushContact(cursor));
|
||||
}
|
||||
}
|
||||
Log.i(TAG, "filterNonPushContacts() -> " + (System.currentTimeMillis() - startMillis) + "ms");
|
||||
return matrix;
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isCursorListEmpty(List<Cursor> list) {
|
||||
int sum = 0;
|
||||
for (Cursor cursor : list) {
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.thoughtcrime.securesms.contacts
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Rect
|
||||
import android.graphics.Typeface
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
|
||||
/**
|
||||
* ItemDecoration which paints a letter header at the appropriate location above a LetterHeaderItem.
|
||||
*/
|
||||
class LetterHeaderDecoration(private val context: Context, private val hideDecoration: () -> Boolean) : RecyclerView.ItemDecoration() {
|
||||
|
||||
private val textBounds = Rect()
|
||||
private val bounds = Rect()
|
||||
private val padTop = ViewUtil.dpToPx(16)
|
||||
private val padStart = context.resources.getDimensionPixelSize(R.dimen.dsl_settings_gutter)
|
||||
|
||||
private var dividerHeight = -1
|
||||
|
||||
private val textPaint = Paint().apply {
|
||||
color = ContextCompat.getColor(context, R.color.signal_text_primary)
|
||||
isAntiAlias = true
|
||||
style = Paint.Style.FILL
|
||||
typeface = Typeface.create("sans-serif-medium", Typeface.BOLD)
|
||||
textAlign = Paint.Align.LEFT
|
||||
textSize = ViewUtil.spToPx(16f).toFloat()
|
||||
}
|
||||
|
||||
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
|
||||
val viewHolder = parent.getChildViewHolder(view)
|
||||
if (hideDecoration() || viewHolder !is LetterHeaderItem || viewHolder.getHeaderLetter() == null) {
|
||||
outRect.set(0, 0, 0, 0)
|
||||
return
|
||||
}
|
||||
|
||||
if (dividerHeight == -1) {
|
||||
val v = LayoutInflater.from(context).inflate(R.layout.dsl_section_header, parent, false)
|
||||
v.measure(0, 0)
|
||||
dividerHeight = v.measuredHeight
|
||||
}
|
||||
outRect.set(0, dividerHeight, 0, 0)
|
||||
}
|
||||
|
||||
override fun onDrawOver(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
|
||||
if (hideDecoration()) {
|
||||
return
|
||||
}
|
||||
|
||||
val childCount = parent.childCount
|
||||
val isRtl = parent.layoutDirection == View.LAYOUT_DIRECTION_RTL
|
||||
|
||||
for (i in 0 until childCount) {
|
||||
val child = parent.getChildAt(i)
|
||||
val holder = parent.getChildViewHolder(child)
|
||||
val headerLetter = if (holder is LetterHeaderItem) holder.getHeaderLetter() else null
|
||||
|
||||
if (headerLetter != null) {
|
||||
parent.getDecoratedBoundsWithMargins(child, bounds)
|
||||
|
||||
textPaint.getTextBounds(headerLetter, 0, headerLetter.length, textBounds)
|
||||
|
||||
val x = if (isRtl) getLayoutBoundsRTL() else getLayoutBoundsLTR()
|
||||
val y = bounds.top + padTop - textBounds.top
|
||||
|
||||
canvas.save()
|
||||
canvas.drawText(headerLetter, x.toFloat(), y.toFloat(), textPaint)
|
||||
canvas.restore()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLayoutBoundsLTR() = bounds.left + padStart
|
||||
|
||||
private fun getLayoutBoundsRTL() = bounds.right - padStart - textBounds.width()
|
||||
|
||||
interface LetterHeaderItem {
|
||||
fun getHeaderLetter(): String?
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user