Display a generated avatar icon rather than a single default.

If the contact doesn't have an image, render a color-coded
background and the first letter of the contact's name.

1) Don't display anything during recipient resolution.

2) Display a # icon in material gray for recipients with no name.

3) Display a material group icon in material gray for groups with
   no avatar icon set.

Closes #3104

// FREEBIE
This commit is contained in:
Moxie Marlinspike 2015-05-04 11:36:18 -07:00
parent 356d9949b7
commit 41cad291f9
24 changed files with 342 additions and 422 deletions

View File

@ -5,6 +5,8 @@
android:versionCode="113"
android:versionName="2.13.0">
<uses-sdk tools:overrideLibrary="com.amulyakhare.textdrawable"/>
<permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"
android:label="Access to TextSecure Secrets"
android:protectionLevel="signature" />

View File

@ -26,6 +26,9 @@ repositories {
maven {
url "https://raw.github.com/whispersystems/maven/master/shortcutbadger/releases/"
}
maven { // textdrawable
url 'https://dl.bintray.com/amulyakhare/maven'
}
jcenter()
mavenLocal()
}
@ -64,6 +67,7 @@ dependencies {
compile 'org.whispersystems:jobmanager:0.11.0'
compile 'org.whispersystems:libpastelog:1.0.6'
compile 'org.whispersystems:textsecure-android:1.3.0'
compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
@ -104,6 +108,7 @@ dependencyVerification {
'org.whispersystems:jobmanager:ea9cb943c4892fb90c1eea1be30efeb85cefca213d52c788419553b58d0ed70d',
'org.whispersystems:libpastelog:550d33c565380d90f4c671e7b8ed5f3a6da55a9fda468373177106b2eb5220b2',
'org.whispersystems:textsecure-android:df4c1ac9ee8f7cd43c8c07d64b16e31875e04632afc3fe73f2a47292a86c79a1',
'com.amulyakhare:com.amulyakhare.textdrawable:54c92b5fba38cfd316a07e5a30528068f45ce8515a6890f1297df4c401af5dcb',
'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a',
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f',

View File

@ -26,17 +26,15 @@
android:layout_marginBottom="6dp"
android:layout_marginRight="0dp">
<org.thoughtcrime.securesms.components.ForegroundImageView
<org.thoughtcrime.securesms.components.AvatarImageView
android:id="@+id/contact_photo"
android:foreground="@drawable/contact_photo_background"
android:layout_width="40dp"
android:layout_height="40dp"
app:riv_oval="true"
android:layout_marginRight="10dp"
android:layout_alignParentLeft="true"
android:cropToPadding="true"
android:contentDescription="@string/conversation_item_received__contact_photo_description"
android:scaleType="centerCrop" />
android:contentDescription="@string/conversation_item_received__contact_photo_description" />
<org.thoughtcrime.securesms.components.IncomingBubbleContainer
android:id="@+id/bubble"

View File

@ -17,14 +17,12 @@
android:paddingRight="10dp"
android:visibility="visible">
<com.makeramen.RoundedImageView
<org.thoughtcrime.securesms.components.AvatarImageView
android:id="@+id/contact_photo_image"
android:foreground="@drawable/contact_photo_background"
android:layout_width="50dp"
android:layout_height="50dp"
app:riv_oval="true"
android:cropToPadding="true"
android:scaleType="centerCrop"
android:visibility="gone"
tools:src="@drawable/ic_contact_picture"
android:contentDescription="@string/conversation_list_item_view__contact_photo_image"
@ -49,7 +47,8 @@
android:weightSum="1"
android:gravity="bottom">
<TextView android:id="@+id/from"
<org.thoughtcrime.securesms.components.FromTextView
android:id="@+id/from"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"

View File

@ -9,10 +9,9 @@
android:paddingLeft="10dp"
android:paddingRight="10dp">
<com.makeramen.RoundedImageView
<org.thoughtcrime.securesms.components.AvatarImageView
android:id="@+id/contact_photo_image"
android:foreground="@drawable/contact_photo_background"
app:riv_oval="true"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_centerVertical="true"
@ -21,7 +20,6 @@
android:layout_marginBottom="3dp"
android:layout_marginRight="10dp"
android:cropToPadding="true"
android:scaleType="centerCrop"
tools:src="@drawable/ic_contact_picture"
android:contentDescription="@string/SingleContactSelectionActivity_contact_photo" />
@ -38,7 +36,8 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView android:id="@+id/from"
<org.thoughtcrime.securesms.components.FromTextView
android:id="@+id/from"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"

View File

@ -1,12 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:paddingRight="25dip">
<com.makeramen.RoundedImageView
<ImageView
android:id="@+id/contact_photo_image"
android:layout_width="@dimen/contact_selection_photo_size"
android:layout_height="@dimen/contact_selection_photo_size"
@ -15,7 +14,6 @@
android:layout_marginLeft="10dp"
android:cropToPadding="true"
android:scaleType="centerCrop"
app:riv_oval="true"
android:contentDescription="@string/SingleContactSelectionActivity_contact_photo" />
<TextView android:id="@+id/number"

View File

@ -5,7 +5,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.makeramen.RoundedImageView
<org.thoughtcrime.securesms.components.AvatarImageView
android:id="@+id/contact_photo_image"
android:foreground="@drawable/contact_photo_background"
android:layout_width="50dp"
@ -17,8 +17,6 @@
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:cropToPadding="true"
android:scaleType="centerCrop"
app:riv_oval="true"
android:contentDescription="@string/SingleContactSelectionActivity_contact_photo" />
<LinearLayout
@ -32,7 +30,8 @@
android:layout_toRightOf="@id/contact_photo_image"
android:orientation="vertical">
<TextView android:id="@+id/from"
<org.thoughtcrime.securesms.components.FromTextView
android:id="@+id/from"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="0dp"

View File

@ -113,4 +113,5 @@
</declare-styleable>
<attr name="group_members_dialog_icon" format="reference"/>
</resources>

View File

@ -21,9 +21,6 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.provider.ContactsContract;
import android.provider.ContactsContract.QuickContact;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.AttributeSet;
@ -38,9 +35,9 @@ import android.widget.Toast;
import com.afollestad.materialdialogs.AlertDialogWrapper;
import org.thoughtcrime.securesms.ConversationFragment.SelectionClickListener;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.components.BubbleContainer;
import org.thoughtcrime.securesms.components.ThumbnailView;
import org.thoughtcrime.securesms.contacts.ContactPhotoFactory;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
@ -85,7 +82,7 @@ public class ConversationItem extends LinearLayout {
private TextView groupStatusText;
private ImageView secureImage;
private ImageView failedImage;
private ImageView contactPhoto;
private AvatarImageView contactPhoto;
private ImageView deliveryImage;
private ImageView pendingIndicator;
private BubbleContainer bubbleContainer;
@ -123,7 +120,7 @@ public class ConversationItem extends LinearLayout {
this.failedImage = (ImageView)findViewById(R.id.sms_failed_indicator);
this.mmsDownloadButton = (Button) findViewById(R.id.mms_download_button);
this.mmsDownloadingLabel = (TextView) findViewById(R.id.mms_label_downloading);
this.contactPhoto = (ImageView)findViewById(R.id.contact_photo);
this.contactPhoto = (AvatarImageView) findViewById(R.id.contact_photo);
this.deliveryImage = (ImageView)findViewById(R.id.delivered_indicator);
this.bodyBubble = findViewById(R.id.body_bubble);
this.pendingIndicator = (ImageView)findViewById(R.id.pending_approval_indicator);
@ -369,30 +366,7 @@ public class ConversationItem extends LinearLayout {
private void setContactPhotoForRecipient(final Recipient recipient) {
if (contactPhoto == null) return;
Bitmap contactPhotoBitmap;
if ((recipient.getContactPhoto() == ContactPhotoFactory.getDefaultContactPhoto(context)) && (groupThread)) {
contactPhotoBitmap = recipient.getGeneratedAvatar(context);
} else {
contactPhotoBitmap = recipient.getContactPhoto();
}
contactPhoto.setImageBitmap(contactPhotoBitmap);
contactPhoto.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (recipient.getContactUri() != null) {
QuickContact.showQuickContact(context, contactPhoto, recipient.getContactUri(), QuickContact.MODE_LARGE, null);
} else {
final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
intent.putExtra(ContactsContract.Intents.Insert.PHONE, recipient.getNumber());
intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
context.startActivity(intent);
}
}
});
contactPhoto.setAvatar(recipient, true);
contactPhoto.setVisibility(View.VISIBLE);
}

View File

@ -22,16 +22,16 @@ import android.graphics.Typeface;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.components.FromTextView;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.Emoji;
import org.thoughtcrime.securesms.util.RecipientViewUtil;
import java.util.Locale;
import java.util.Set;
@ -53,15 +53,15 @@ public class ConversationListItem extends RelativeLayout
private final static Typeface BOLD_TYPEFACE = Typeface.create("sans-serif", Typeface.BOLD);
private final static Typeface LIGHT_TYPEFACE = Typeface.create("sans-serif-light", Typeface.NORMAL);
private Context context;
private Set<Long> selectedThreads;
private Recipients recipients;
private long threadId;
private TextView subjectView;
private TextView fromView;
private TextView dateView;
private boolean read;
private ImageView contactPhotoImage;
private Context context;
private Set<Long> selectedThreads;
private Recipients recipients;
private long threadId;
private TextView subjectView;
private FromTextView fromView;
private TextView dateView;
private boolean read;
private AvatarImageView contactPhotoImage;
private final Handler handler = new Handler();
private int distributionType;
@ -79,10 +79,9 @@ public class ConversationListItem extends RelativeLayout
@Override
protected void onFinishInflate() {
this.subjectView = (TextView) findViewById(R.id.subject);
this.fromView = (TextView) findViewById(R.id.from);
this.fromView = (FromTextView) findViewById(R.id.from);
this.dateView = (TextView) findViewById(R.id.date);
this.contactPhotoImage = (ImageView) findViewById(R.id.contact_photo_image);
this.contactPhotoImage = (AvatarImageView) findViewById(R.id.contact_photo_image);
initializeContactWidgetVisibility();
}
@ -95,7 +94,7 @@ public class ConversationListItem extends RelativeLayout
this.distributionType = thread.getDistributionType();
this.recipients.addListener(this);
this.fromView.setText(RecipientViewUtil.formatFrom(context, recipients, read));
this.fromView.setText(recipients, read);
this.subjectView.setText(Emoji.getInstance(context).emojify(thread.getDisplayBody(),
Emoji.EMOJI_SMALL,
@ -110,7 +109,7 @@ public class ConversationListItem extends RelativeLayout
}
setBackground(read, batchMode);
RecipientViewUtil.setContactPhoto(context, contactPhotoImage, recipients.getPrimaryRecipient(), true);
this.contactPhotoImage.setAvatar(recipients.getPrimaryRecipient(), true);
}
public void unbind() {
@ -157,8 +156,8 @@ public class ConversationListItem extends RelativeLayout
handler.post(new Runnable() {
@Override
public void run() {
ConversationListItem.this.fromView.setText(RecipientViewUtil.formatFrom(context, recipients, read));
RecipientViewUtil.setContactPhoto(context, contactPhotoImage, recipients.getPrimaryRecipient(), true);
fromView.setText(recipients, read);
contactPhotoImage.setAvatar(recipients.getPrimaryRecipient(), true);
}
});
}

View File

@ -23,10 +23,11 @@ import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.components.FromTextView;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
@ -35,7 +36,6 @@ import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.RecipientViewUtil;
/**
* A simple view to show the recipients of a message
@ -47,12 +47,12 @@ public class MessageRecipientListItem extends RelativeLayout
{
private final static String TAG = MessageRecipientListItem.class.getSimpleName();
private Recipient recipient;
private TextView fromView;
private TextView errorDescription;
private Button conflictButton;
private Button resendButton;
private ImageView contactPhotoImage;
private Recipient recipient;
private FromTextView fromView;
private TextView errorDescription;
private Button conflictButton;
private Button resendButton;
private AvatarImageView contactPhotoImage;
private final Handler handler = new Handler();
@ -66,11 +66,11 @@ public class MessageRecipientListItem extends RelativeLayout
@Override
protected void onFinishInflate() {
this.fromView = (TextView) findViewById(R.id.from);
this.errorDescription = (TextView) findViewById(R.id.error_description);
this.contactPhotoImage = (ImageView) findViewById(R.id.contact_photo_image);
this.conflictButton = (Button) findViewById(R.id.conflict_button);
this.resendButton = (Button) findViewById(R.id.resend_button);
this.fromView = (FromTextView) findViewById(R.id.from);
this.errorDescription = (TextView) findViewById(R.id.error_description);
this.contactPhotoImage = (AvatarImageView) findViewById(R.id.contact_photo_image);
this.conflictButton = (Button) findViewById(R.id.conflict_button);
this.resendButton = (Button) findViewById(R.id.resend_button);
}
public void set(final MasterSecret masterSecret,
@ -81,9 +81,8 @@ public class MessageRecipientListItem extends RelativeLayout
this.recipient = recipient;
recipient.addListener(this);
fromView.setText(RecipientViewUtil.formatFrom(getContext(), recipient));
RecipientViewUtil.setContactPhoto(getContext(), contactPhotoImage, recipient, false);
fromView.setText(recipient);
contactPhotoImage.setAvatar(recipient, false);
setIssueIndicators(masterSecret, record, isPushGroup);
}
@ -160,8 +159,8 @@ public class MessageRecipientListItem extends RelativeLayout
handler.post(new Runnable() {
@Override
public void run() {
fromView.setText(RecipientViewUtil.formatFrom(getContext(), recipient));
RecipientViewUtil.setContactPhoto(getContext(), contactPhotoImage, recipient, false);
fromView.setText(recipient);
contactPhotoImage.setAvatar(recipient, false);
}
});
}

View File

@ -20,17 +20,13 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.makeramen.RoundedImageView;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.components.FromTextView;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.RecipientViewUtil;
/**
* A simple view to show the recipients of an open conversation
@ -42,12 +38,12 @@ public class ShareListItem extends RelativeLayout
{
private final static String TAG = ShareListItem.class.getSimpleName();
private Context context;
private Recipients recipients;
private long threadId;
private TextView fromView;
private Context context;
private Recipients recipients;
private long threadId;
private FromTextView fromView;
private RoundedImageView contactPhotoImage;
private AvatarImageView contactPhotoImage;
private final Handler handler = new Handler();
private int distributionType;
@ -64,8 +60,8 @@ public class ShareListItem extends RelativeLayout
@Override
protected void onFinishInflate() {
this.fromView = (TextView) findViewById(R.id.from);
this.contactPhotoImage = (RoundedImageView) findViewById(R.id.contact_photo_image);
this.fromView = (FromTextView) findViewById(R.id.from);
this.contactPhotoImage = (AvatarImageView) findViewById(R.id.contact_photo_image);
}
public void set(ThreadRecord thread) {
@ -74,10 +70,10 @@ public class ShareListItem extends RelativeLayout
this.distributionType = thread.getDistributionType();
this.recipients.addListener(this);
this.fromView.setText(RecipientViewUtil.formatFrom(getContext(), recipients));
this.fromView.setText(recipients);
setBackground();
RecipientViewUtil.setContactPhoto(getContext(), contactPhotoImage, this.recipients.getPrimaryRecipient(), false);
this.contactPhotoImage.setAvatar(this.recipients.getPrimaryRecipient(), false);
}
public void unbind() {
@ -110,8 +106,8 @@ public class ShareListItem extends RelativeLayout
handler.post(new Runnable() {
@Override
public void run() {
fromView.setText(RecipientViewUtil.formatFrom(getContext(), recipients));
RecipientViewUtil.setContactPhoto(getContext(), contactPhotoImage, recipients.getPrimaryRecipient(), false);
fromView.setText(recipients);
contactPhotoImage.setAvatar(recipients.getPrimaryRecipient(), false);
}
});
}

View File

@ -0,0 +1,49 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.content.Intent;
import android.provider.ContactsContract;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import org.thoughtcrime.securesms.recipients.Recipient;
public class AvatarImageView extends ImageView {
public AvatarImageView(Context context) {
super(context);
setScaleType(ScaleType.CENTER_INSIDE);
}
public AvatarImageView(Context context, AttributeSet attrs) {
super(context, attrs);
setScaleType(ScaleType.CENTER_INSIDE);
}
public void setAvatar(Recipient recipient, boolean quickContactEnabled) {
setImageDrawable(recipient.getContactPhoto());
setAvatarClickHandler(recipient, quickContactEnabled);
}
private void setAvatarClickHandler(final Recipient recipient, boolean quickContactEnabled) {
if (!recipient.isGroupRecipient() && quickContactEnabled) {
setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (recipient.getContactUri() != null) {
ContactsContract.QuickContact.showQuickContact(getContext(), AvatarImageView.this, recipient.getContactUri(), ContactsContract.QuickContact.MODE_LARGE, null);
} else {
final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
intent.putExtra(ContactsContract.Intents.Insert.PHONE, recipient.getNumber());
intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
getContext().startActivity(intent);
}
}
});
} else {
setOnClickListener(null);
}
}
}

View File

@ -0,0 +1,69 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.StyleSpan;
import android.util.AttributeSet;
import android.widget.TextView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
public class FromTextView extends TextView {
public FromTextView(Context context) {
super(context);
}
public FromTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setText(Recipient recipient) {
setText(new Recipients(recipient));
}
public void setText(Recipients recipients) {
setText(recipients, true);
}
public void setText(Recipients recipients, boolean read) {
int attributes[] = new int[]{R.attr.conversation_list_item_count_color};
TypedArray colors = getContext().obtainStyledAttributes(attributes);
boolean isUnnamedGroup = recipients.isGroupRecipient() && TextUtils.isEmpty(recipients.getPrimaryRecipient().getName());
String fromString;
if (isUnnamedGroup) {
fromString = getContext().getString(R.string.ConversationActivity_unnamed_group);
} else {
fromString = recipients.toShortString();
}
int typeface;
if (isUnnamedGroup) {
if (!read) typeface = Typeface.BOLD_ITALIC;
else typeface = Typeface.ITALIC;
} else if (!read) {
typeface = Typeface.BOLD;
} else {
typeface = Typeface.NORMAL;
}
SpannableStringBuilder builder = new SpannableStringBuilder(fromString);
builder.setSpan(new StyleSpan(typeface), 0, builder.length(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
colors.recycle();
setText(builder);
}
}

View File

@ -1,15 +1,31 @@
package org.thoughtcrime.securesms.contacts;
import android.content.Context;
import android.database.Cursor;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.Gravity;
import android.widget.ImageView;
import com.amulyakhare.textdrawable.TextDrawable;
import com.amulyakhare.textdrawable.util.ColorGenerator;
import com.makeramen.RoundedDrawable;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.recipients.Recipient;
@ -24,62 +40,56 @@ import java.util.Map;
public class ContactPhotoFactory {
private static final String TAG = ContactPhotoFactory.class.getSimpleName();
private static final ColorGenerator COLOR_GENERATOR = ColorGenerator.MATERIAL;
private static final int UNKNOWN_COLOR = 0xff9E9E9E;
private static final Object defaultPhotoLock = new Object();
private static final Object defaultGroupPhotoLock = new Object();
private static final Object loadingPhotoLock = new Object();
private static Bitmap defaultContactPhoto;
private static Bitmap defaultGroupContactPhoto;
private static Drawable defaultContactPhoto;
private static Drawable defaultGroupContactPhoto;
private static Drawable loadingPhoto;
private static final Map<Uri,Bitmap> localUserContactPhotoCache =
Collections.synchronizedMap(new LRUCache<Uri,Bitmap>(2));
private static final String[] CONTENT_URI_PROJECTION = new String[] {
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.LOOKUP_KEY
};
public static Drawable getLoadingPhoto(Context context) {
synchronized (loadingPhotoLock) {
if (loadingPhoto == null)
loadingPhoto = RoundedDrawable.fromDrawable(context.getResources().getDrawable(android.R.color.transparent));
return loadingPhoto;
}
}
public static Drawable getDefaultContactPhoto(@Nullable String name) {
if (name != null && !name.isEmpty()) {
return TextDrawable.builder().buildRound(String.valueOf(name.charAt(0)),
COLOR_GENERATOR.getColor(name));
}
public static Bitmap getDefaultContactPhoto(Context context) {
synchronized (defaultPhotoLock) {
if (defaultContactPhoto == null)
defaultContactPhoto = BitmapFactory.decodeResource(context.getResources(),
R.drawable.ic_contact_picture);
defaultContactPhoto = TextDrawable.builder().buildRound("#", UNKNOWN_COLOR);
return defaultContactPhoto;
}
}
public static Bitmap getDefaultGroupPhoto(Context context) {
public static Drawable getDefaultGroupPhoto(Context context) {
synchronized (defaultGroupPhotoLock) {
if (defaultGroupContactPhoto == null)
defaultGroupContactPhoto = BitmapFactory.decodeResource(context.getResources(),
R.drawable.ic_group_photo);
return defaultGroupContactPhoto;
}
}
if (defaultGroupContactPhoto == null) {
Drawable background = TextDrawable.builder().buildRound(" ", UNKNOWN_COLOR);
RoundedDrawable foreground = (RoundedDrawable) RoundedDrawable.fromDrawable(context.getResources().getDrawable(R.drawable.ic_group_white_24dp));
foreground.setScaleType(ImageView.ScaleType.CENTER);
public static Bitmap getLocalUserContactPhoto(Context context, Uri uri) {
if (uri == null) return getDefaultContactPhoto(context);
Bitmap contactPhoto = localUserContactPhotoCache.get(uri);
if (contactPhoto == null) {
Cursor cursor = context.getContentResolver().query(uri, CONTENT_URI_PROJECTION,
null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
contactPhoto = getContactPhoto(context, Uri.withAppendedPath(Contacts.CONTENT_URI,
cursor.getLong(0) + ""));
} else {
contactPhoto = getDefaultContactPhoto(context);
}
} finally {
if (cursor != null) cursor.close();
defaultGroupContactPhoto = new ExpandingLayerDrawable(new Drawable[] {background, foreground});
}
localUserContactPhotoCache.put(uri, contactPhoto);
return defaultGroupContactPhoto;
}
return contactPhoto;
}
public static void clearCache() {
@ -91,25 +101,32 @@ public class ContactPhotoFactory {
localUserContactPhotoCache.remove(recipient.getContactUri());
}
public static Bitmap getContactPhoto(Context context, Uri uri) {
public static Drawable getContactPhoto(Context context, Uri uri, String name) {
final InputStream inputStream = getContactPhotoStream(context, uri);
final int targetSize = context.getResources().getDimensionPixelSize(R.dimen.contact_photo_target_size);
Bitmap contactPhoto = null;
if (inputStream != null) {
try {
contactPhoto = BitmapUtil.createScaledBitmap(inputStream,
getContactPhotoStream(context, uri),
targetSize,
targetSize);
return RoundedDrawable.fromBitmap(BitmapUtil.createScaledBitmap(inputStream,
getContactPhotoStream(context, uri),
targetSize,
targetSize))
.setScaleType(ImageView.ScaleType.CENTER_CROP)
.setOval(true);
} catch (BitmapDecodingException bde) {
Log.w(TAG, bde);
}
}
if (contactPhoto == null) {
contactPhoto = ContactPhotoFactory.getDefaultContactPhoto(context);
}
return contactPhoto;
return getDefaultContactPhoto(name);
}
public static Drawable getGroupContactPhoto(Context context, @Nullable byte[] avatar) {
if (avatar == null) return getDefaultGroupPhoto(context);
return RoundedDrawable.fromBitmap(BitmapFactory.decodeByteArray(avatar, 0, avatar.length))
.setScaleType(ImageView.ScaleType.CENTER_CROP)
.setOval(true);
}
private static InputStream getContactPhotoStream(Context context, Uri uri) {
@ -119,4 +136,21 @@ public class ContactPhotoFactory {
return ContactsContract.Contacts.openContactPhotoInputStream(context.getContentResolver(), uri);
}
}
private static class ExpandingLayerDrawable extends LayerDrawable {
public ExpandingLayerDrawable(Drawable[] layers) {
super(layers);
}
@Override
public int getIntrinsicWidth() {
return -1;
}
@Override
public int getIntrinsicHeight() {
return -1;
}
}
}

View File

@ -19,7 +19,6 @@ package org.thoughtcrime.securesms.contacts;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.provider.ContactsContract;
import android.support.v4.widget.CursorAdapter;
import android.text.Spannable;
@ -73,7 +72,6 @@ public class ContactSelectionListAdapter extends CursorAdapter
private final boolean multiSelect;
private final LayoutInflater li;
private final TypedArray drawables;
private final Bitmap defaultPhoto;
private final int scaledPhotoSize;
private final HashMap<Long, ContactAccessor.ContactData> selectedContacts = new HashMap<>();
@ -84,7 +82,6 @@ public class ContactSelectionListAdapter extends CursorAdapter
this.li = LayoutInflater.from(context);
this.drawables = context.obtainStyledAttributes(STYLE_ATTRIBUTES);
this.multiSelect = multiSelect;
this.defaultPhoto = ContactPhotoFactory.getDefaultContactPhoto(context);
this.scaledPhotoSize = context.getResources().getDimensionPixelSize(R.dimen.contact_selection_photo_size);
}
@ -180,7 +177,7 @@ public class ContactSelectionListAdapter extends CursorAdapter
numberLabelSpan.setSpan(new ForegroundColorSpan(drawables.getColor(2, 0xff444444)), contactData.number.length(), numberWithLabel.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
holder.number.setText(numberLabelSpan);
}
holder.contactPhoto.setImageBitmap(defaultPhoto);
holder.contactPhoto.setImageDrawable(ContactPhotoFactory.getLoadingPhoto(context));
if (contactData.id > -1) loadBitmap(contactData.number, holder.contactPhoto);
}
@ -229,11 +226,14 @@ public class ContactSelectionListAdapter extends CursorAdapter
return true;
}
// FIXME -- It should be unnecessary to duplicate the existing asynchronous resolution
// infrastructure we've built for Recipient objects here.
public void loadBitmap(String number, ImageView imageView) {
if (cancelPotentialWork(number, imageView)) {
final BitmapWorkerRunnable runnable = new BitmapWorkerRunnable(context, imageView, defaultPhoto, number, scaledPhotoSize);
final BitmapWorkerRunnable runnable = new BitmapWorkerRunnable(context, imageView, number, scaledPhotoSize);
final TaggedFutureTask<?> task = new TaggedFutureTask<Void>(runnable, null, number);
final AsyncDrawable asyncDrawable = new AsyncDrawable(defaultPhoto, task);
final AsyncDrawable asyncDrawable = new AsyncDrawable(task);
imageView.setImageDrawable(asyncDrawable);
if (!task.isCancelled()) photoResolver.execute(new FutureTask<Void>(task, null));

View File

@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.util.Log;
import org.thoughtcrime.securesms.Release;
@ -74,7 +75,7 @@ public class AvatarDownloadJob extends MasterSecretJob {
Recipient groupRecipient = RecipientFactory.getRecipientsFromString(context, GroupUtil.getEncodedId(groupId), true)
.getPrimaryRecipient();
groupRecipient.setContactPhoto(avatar);
groupRecipient.setContactPhoto(new BitmapDrawable(avatar));
}
} catch (InvalidMessageException | BitmapDecodingException | NonSuccessfulResponseCodeException e) {
Log.w(TAG, e);

View File

@ -24,9 +24,9 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
@ -181,9 +181,9 @@ public class MessageNotifier {
List<NotificationItem> notifications = notificationState.getNotifications();
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
Recipient recipient = notifications.get(0).getIndividualRecipient();
Bitmap recipientPhoto = recipient.getContactPhoto();
Drawable recipientPhoto = recipient.getContactPhoto();
if (recipientPhoto != null) builder.setLargeIcon(BitmapUtil.getCircleBitmap(recipientPhoto));
if (recipientPhoto != null) builder.setLargeIcon(BitmapUtil.createFromDrawable(recipientPhoto));
builder.setSmallIcon(R.drawable.icon_notification);
builder.setColor(context.getResources().getColor(R.color.textsecure_primary));
builder.setContentTitle(recipient.toShortString());

View File

@ -1,120 +0,0 @@
package org.thoughtcrime.securesms.recipients;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contacts.ContactPhotoFactory;
import org.thoughtcrime.securesms.util.BitmapUtil;
/**
* Utility class to generate avatars for contacts who don't have a contact
* picture set.
*
* @author Lukas Barth
*/
public class AvatarGenerator {
public static Bitmap generateFor(Context context, Recipient recipient) {
if ((recipient == null) || (recipient.getName() == null)) {
return ContactPhotoFactory.getDefaultContactPhoto(context);
}
final int size = ContactPhotoFactory.getDefaultContactPhoto(context).getHeight();
final Bitmap output = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(output);
final int color = getColorForRecipient(recipient, context);
final Paint paint = new Paint();
final int innerRectOffset = (int) Math.ceil((size - Math.sqrt(2) * (size / 2)) / 2);
final Rect innerRect = new Rect(innerRectOffset, innerRectOffset,
size - innerRectOffset, size - innerRectOffset);
paint.setAntiAlias(true);
paint.setColor(color);
canvas.drawCircle(size / 2, size / 2, size / 2, paint);
paint.setColor(Color.WHITE);
Typeface robotoLightTypeface = Typeface.createFromAsset(context.getAssets(), "fonts/Roboto-Light.ttf");
paint.setTypeface(robotoLightTypeface);
setFontSize(innerRect, paint);
paint.setTextAlign(Paint.Align.CENTER);
int initialIndex = 0;
char[] contactName = recipient.getName().toCharArray();
if (contactName.length == 0) {
contactName = new char[]{'?'};
initialIndex = 0;
} else {
while ((! Character.isLetter(contactName[initialIndex]))) {
initialIndex ++;
if (initialIndex >= contactName.length) {
contactName[0] = '?';
initialIndex = 0;
break;
}
}
}
Rect textBounds = new Rect();
paint.getTextBounds(contactName, initialIndex, 1, textBounds);
int bottomOffset = (innerRect.height() - textBounds.height()) / 2;
canvas.drawText(Character.toString(contactName[initialIndex]),
innerRect.centerX(), innerRect.bottom - bottomOffset, paint);
return output;
}
private static int getColorForRecipient(Recipient recipient, Context context) {
if ((recipient == null) || (recipient.getName() == null)) {
return Color.WHITE;
}
long nameHash = recipient.getName().hashCode();
Resources res = context.getResources();
TypedArray colorArray = res.obtainTypedArray(R.array.avatar_colors);
int index = Math.abs((int) (nameHash % colorArray.length()));
int color = colorArray.getColor(index, Color.BLACK);
colorArray.recycle();
return color;
}
private static int setFontSize(Rect textRect, Paint paint) {
boolean overflow = false;
int currentSize = 0;
while (!overflow) {
currentSize++;
paint.setTextSize(currentSize);
Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt();
int textHeight = fontMetrics.descent - fontMetrics.ascent;
if (textHeight > textRect.height()) {
overflow = true;
}
}
currentSize--;
currentSize *= 1.2;
paint.setTextSize(currentSize);
return currentSize;
}
}

View File

@ -18,13 +18,14 @@ package org.thoughtcrime.securesms.recipients;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.Log;
import org.thoughtcrime.securesms.contacts.ContactPhotoFactory;
import org.thoughtcrime.securesms.recipients.RecipientProvider.RecipientDetails;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.FutureTaskListener;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.ListenableFutureTask;
import java.util.HashSet;
@ -33,25 +34,22 @@ public class Recipient {
private final static String TAG = Recipient.class.getSimpleName();
private final HashSet<RecipientModifiedListener> listeners = new HashSet<RecipientModifiedListener>();
private final HashSet<RecipientModifiedListener> listeners = new HashSet<>();
private final long recipientId;
private String number;
private String name;
private Bitmap contactPhoto;
private Bitmap generatedAvatar;
private Drawable contactPhoto;
private Uri contactUri;
private Uri contactUri;
Recipient(String number, Bitmap contactPhoto,
Recipient(String number, Drawable contactPhoto,
long recipientId, ListenableFutureTask<RecipientDetails> future)
{
this.number = number;
this.contactPhoto = contactPhoto;
this.recipientId = recipientId;
this.generatedAvatar = null;
future.addListener(new FutureTaskListener<RecipientDetails>() {
@Override
@ -81,7 +79,7 @@ public class Recipient {
});
}
Recipient(String name, String number, long recipientId, Uri contactUri, Bitmap contactPhoto) {
Recipient(String name, String number, long recipientId, Uri contactUri, Drawable contactPhoto) {
this.number = number;
this.recipientId = recipientId;
this.contactUri = contactUri;
@ -93,7 +91,7 @@ public class Recipient {
return this.contactUri;
}
public synchronized void setContactPhoto(Bitmap bitmap) {
public synchronized void setContactPhoto(Drawable bitmap) {
this.contactPhoto = bitmap;
notifyListeners();
}
@ -143,20 +141,13 @@ public class Recipient {
return (name == null ? number : name);
}
public synchronized Bitmap getContactPhoto() {
public synchronized Drawable getContactPhoto() {
return contactPhoto;
}
public synchronized Bitmap getGeneratedAvatar(Context context) {
if (this.generatedAvatar == null)
this.generatedAvatar = AvatarGenerator.generateFor(context, this);
return this.generatedAvatar;
}
public static Recipient getUnknownRecipient(Context context) {
return new Recipient("Unknown", "Unknown", -1, null,
ContactPhotoFactory.getDefaultContactPhoto(context));
ContactPhotoFactory.getDefaultContactPhoto(null));
}
@Override
@ -174,7 +165,7 @@ public class Recipient {
return 31 + (int)this.recipientId;
}
public static interface RecipientModifiedListener {
public interface RecipientModifiedListener {
public void onModified(Recipient recipient);
}
}

View File

@ -18,8 +18,7 @@ package org.thoughtcrime.securesms.recipients;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.PhoneLookup;
@ -29,11 +28,10 @@ import org.thoughtcrime.securesms.contacts.ContactPhotoFactory;
import org.thoughtcrime.securesms.database.CanonicalAddressDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.LRUCache;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ListenableFutureTask;
import org.thoughtcrime.securesms.util.Util;
import java.io.IOException;
import java.util.Collections;
@ -75,9 +73,9 @@ public class RecipientProvider {
if (details != null) {
recipient = new Recipient(details.name, details.number, recipientId, details.contactUri, details.avatar);
} else {
final Bitmap defaultPhoto = isGroupRecipient
final Drawable defaultPhoto = isGroupRecipient
? ContactPhotoFactory.getDefaultGroupPhoto(context)
: ContactPhotoFactory.getDefaultContactPhoto(context);
: ContactPhotoFactory.getDefaultContactPhoto(null);
recipient = new Recipient(null, number, recipientId, null, defaultPhoto);
}
@ -100,16 +98,16 @@ public class RecipientProvider {
}
};
ListenableFutureTask<RecipientDetails> future = new ListenableFutureTask<RecipientDetails>(task);
ListenableFutureTask<RecipientDetails> future = new ListenableFutureTask<>(task);
asyncRecipientResolver.submit(future);
Bitmap contactPhoto;
Drawable contactPhoto;
if (isGroupRecipient) {
contactPhoto = ContactPhotoFactory.getDefaultGroupPhoto(context);
} else {
contactPhoto = ContactPhotoFactory.getDefaultContactPhoto(context);
contactPhoto = ContactPhotoFactory.getLoadingPhoto(context);
}
Recipient recipient = new Recipient(number, contactPhoto, recipientId, future);
@ -134,9 +132,10 @@ public class RecipientProvider {
try {
if (cursor != null && cursor.moveToFirst()) {
Uri contactUri = Contacts.getLookupUri(cursor.getLong(2), cursor.getString(1));
Bitmap contactPhoto = ContactPhotoFactory.getContactPhoto(context, Uri.withAppendedPath(Contacts.CONTENT_URI,
cursor.getLong(2)+""));
Uri contactUri = Contacts.getLookupUri(cursor.getLong(2), cursor.getString(1));
Drawable contactPhoto = ContactPhotoFactory.getContactPhoto(context,
Uri.withAppendedPath(Contacts.CONTENT_URI, cursor.getLong(2)+""),
cursor.getString(0));
return new RecipientDetails(cursor.getString(0), cursor.getString(3), contactUri, contactPhoto);
}
} finally {
@ -144,7 +143,7 @@ public class RecipientProvider {
cursor.close();
}
return null;
return new RecipientDetails(null, number, null, ContactPhotoFactory.getDefaultContactPhoto(null));
}
private RecipientDetails getGroupRecipientDetails(Context context, String groupId) {
@ -153,12 +152,7 @@ public class RecipientProvider {
.getGroup(GroupUtil.getDecodedId(groupId));
if (record != null) {
byte[] avatarBytes = record.getAvatar();
Bitmap avatar;
if (avatarBytes == null) avatar = ContactPhotoFactory.getDefaultGroupPhoto(context);
else avatar = BitmapFactory.decodeByteArray(avatarBytes, 0, avatarBytes.length);
Drawable avatar = ContactPhotoFactory.getGroupContactPhoto(context, record.getAvatar());
return new RecipientDetails(record.getTitle(), groupId, null, avatar);
}
@ -170,12 +164,12 @@ public class RecipientProvider {
}
public static class RecipientDetails {
public final String name;
public final String number;
public final Bitmap avatar;
public final Uri contactUri;
public final String name;
public final String number;
public final Drawable avatar;
public final Uri contactUri;
public RecipientDetails(String name, String number, Uri contactUri, Bitmap avatar) {
public RecipientDetails(String name, String number, Uri contactUri, Drawable avatar) {
this.name = name;
this.number = number;
this.avatar = avatar;
@ -183,4 +177,5 @@ public class RecipientProvider {
}
}
}

View File

@ -12,6 +12,8 @@ import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.Log;
import android.util.Pair;
@ -253,4 +255,23 @@ public class BitmapUtil {
return output;
}
public static Bitmap createFromDrawable(Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable)drawable).getBitmap();
}
int width = drawable.getIntrinsicWidth();
width = width > 0 ? width : 1;
int height = drawable.getIntrinsicHeight();
height = height > 0 ? height : 1;
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
}

View File

@ -21,6 +21,7 @@ import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.DrawableContainer;
import android.widget.ImageView;
import com.makeramen.RoundedDrawable;
@ -38,49 +39,42 @@ import java.lang.ref.WeakReference;
public class BitmapWorkerRunnable implements Runnable {
private final static String TAG = BitmapWorkerRunnable.class.getSimpleName();
private final Bitmap defaultPhoto;
private final WeakReference<ImageView> imageViewReference;
private final Context context;
private final int size;
public final String number;
public BitmapWorkerRunnable(Context context, ImageView imageView, Bitmap defaultPhoto, String number, int size) {
public BitmapWorkerRunnable(Context context, ImageView imageView, String number, int size) {
this.imageViewReference = new WeakReference<>(imageView);
this.context = context;
this.defaultPhoto = defaultPhoto;
this.size = size;
this.number = number;
}
@Override
public void run() {
final Recipient recipient = RecipientFactory.getRecipientsFromString(context, number, false).getPrimaryRecipient();
final Bitmap contactPhoto = recipient.getContactPhoto();
if (defaultPhoto == contactPhoto) {
return;
}
if (recipient.getContactPhoto() != null) {
final Recipient recipient = RecipientFactory.getRecipientsFromString(context, number, false).getPrimaryRecipient();
final Drawable contactPhoto = recipient.getContactPhoto();
if (contactPhoto != null) {
final ImageView imageView = imageViewReference.get();
final TaggedFutureTask<?> bitmapWorkerTask = AsyncDrawable.getBitmapWorkerTask(imageView);
if (bitmapWorkerTask.getTag().equals(number) && imageView != null) {
final BitmapDrawable drawable = new BitmapDrawable(context.getResources(), recipient.getContactPhoto());
imageView.post(new Runnable() {
@Override
public void run() {
imageView.setImageDrawable(drawable);
imageView.setImageDrawable(contactPhoto);
}
});
}
}
}
public static class AsyncDrawable extends RoundedDrawable {
public static class AsyncDrawable extends BitmapDrawable {
private final WeakReference<TaggedFutureTask<?>> bitmapWorkerTaskReference;
public AsyncDrawable(Bitmap bitmap, TaggedFutureTask<?> bitmapWorkerTask) {
super(bitmap);
public AsyncDrawable(TaggedFutureTask<?> bitmapWorkerTask) {
bitmapWorkerTaskReference =
new WeakReference<TaggedFutureTask<?>>(bitmapWorkerTask);
}

View File

@ -1,83 +0,0 @@
package org.thoughtcrime.securesms.util;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.net.Uri;
import android.provider.Contacts.Intents;
import android.provider.ContactsContract.QuickContact;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.StyleSpan;
import android.view.View;
import android.widget.ImageView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients;
public class RecipientViewUtil {
public static CharSequence formatFrom(Context context, Recipient recipient) {
return formatFrom(context, new Recipients(recipient));
}
public static CharSequence formatFrom(Context context, Recipients from) {
return formatFrom(context, from, true);
}
public static CharSequence formatFrom(Context context, Recipients from, boolean read) {
int attributes[] = new int[] {R.attr.conversation_list_item_count_color};
TypedArray colors = context.obtainStyledAttributes(attributes);
final String fromString;
final boolean isUnnamedGroup = from.isGroupRecipient() && TextUtils.isEmpty(from.getPrimaryRecipient().getName());
if (isUnnamedGroup) {
fromString = context.getString(R.string.ConversationActivity_unnamed_group);
} else {
fromString = from.toShortString();
}
SpannableStringBuilder builder = new SpannableStringBuilder(fromString);
final int typeface;
if (isUnnamedGroup) {
if (!read) typeface = Typeface.BOLD_ITALIC;
else typeface = Typeface.ITALIC;
} else if (!read) {
typeface = Typeface.BOLD;
} else {
typeface = Typeface.NORMAL;
}
builder.setSpan(new StyleSpan(typeface), 0, builder.length(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
colors.recycle();
return builder;
}
public static void setContactPhoto(final Context context, final ImageView imageView, final Recipient recipient, boolean showQuickContact) {
if (recipient == null) return;
imageView.setImageBitmap(recipient.getContactPhoto());
if (!recipient.isGroupRecipient() && showQuickContact) {
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (recipient.getContactUri() != null) {
QuickContact.showQuickContact(context, imageView, recipient.getContactUri(), QuickContact.MODE_LARGE, null);
} else {
Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, Uri.fromParts("tel", recipient.getNumber(), null));
context.startActivity(intent);
}
}
});
} else {
imageView.setOnClickListener(null);
}
}
}