mirror of
https://github.com/oxen-io/session-android.git
synced 2025-10-25 13:58:40 +00:00
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:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user