Fix emoji avatar missing after edit.

This commit is contained in:
Lucio Maciel 2021-09-16 13:40:51 -03:00 committed by Alex Hart
parent 5e968eb831
commit 18ba5fa291
15 changed files with 181 additions and 69 deletions

View file

@ -48,8 +48,9 @@ object AvatarRenderer {
avatar: Avatar.Text, avatar: Avatar.Text,
inverted: Boolean = false, inverted: Boolean = false,
size: Int = DIMENSIONS, size: Int = DIMENSIONS,
synchronous: Boolean = false
): Drawable { ): Drawable {
return TextAvatarDrawable(context, avatar, inverted, size) return TextAvatarDrawable(context, avatar, inverted, size, synchronous)
} }
private fun renderVector(context: Context, avatar: Avatar.Vector, onAvatarRendered: (Media) -> Unit, onRenderFailed: (Throwable?) -> Unit) { private fun renderVector(context: Context, avatar: Avatar.Vector, onAvatarRendered: (Media) -> Unit, onRenderFailed: (Throwable?) -> Unit) {
@ -66,7 +67,7 @@ object AvatarRenderer {
private fun renderText(context: Context, avatar: Avatar.Text, onAvatarRendered: (Media) -> Unit, onRenderFailed: (Throwable?) -> Unit) { private fun renderText(context: Context, avatar: Avatar.Text, onAvatarRendered: (Media) -> Unit, onRenderFailed: (Throwable?) -> Unit) {
renderInBackground(context, onAvatarRendered, onRenderFailed) { canvas -> renderInBackground(context, onAvatarRendered, onRenderFailed) { canvas ->
val textDrawable = createTextDrawable(context, avatar) val textDrawable = createTextDrawable(context, avatar, synchronous = true)
canvas.drawColor(avatar.color.backgroundColor) canvas.drawColor(avatar.color.backgroundColor)
textDrawable.draw(canvas) textDrawable.draw(canvas)

View file

@ -3,52 +3,59 @@ package org.thoughtcrime.securesms.avatar
import android.content.Context import android.content.Context
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.ColorFilter import android.graphics.ColorFilter
import android.graphics.Paint
import android.graphics.PixelFormat import android.graphics.PixelFormat
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.util.TypedValue import android.text.Layout
import android.view.Gravity import android.text.SpannableString
import android.widget.FrameLayout import android.text.StaticLayout
import androidx.core.view.updateLayoutParams import android.text.TextPaint
import org.thoughtcrime.securesms.components.emoji.EmojiTextView import androidx.core.graphics.withTranslation
import org.thoughtcrime.securesms.components.emoji.EmojiProvider
/**
* Uses EmojiTextView to properly render a Text Avatar with emoji in it.
*/
class TextAvatarDrawable( class TextAvatarDrawable(
context: Context, private val context: Context,
avatar: Avatar.Text, private val avatar: Avatar.Text,
inverted: Boolean = false, inverted: Boolean = false,
private val size: Int = AvatarRenderer.DIMENSIONS, private val size: Int = AvatarRenderer.DIMENSIONS,
private val synchronous: Boolean = false
) : Drawable() { ) : Drawable() {
private val layout: FrameLayout = FrameLayout(context) private val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
private val textView: EmojiTextView = EmojiTextView(context)
init { init {
textView.typeface = AvatarRenderer.getTypeface(context) textPaint.typeface = AvatarRenderer.getTypeface(context)
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, Avatars.getTextSizeForLength(context, avatar.text, size * 0.8f, size * 0.45f)) textPaint.color = if (inverted) avatar.color.backgroundColor else avatar.color.foregroundColor
textView.text = avatar.text textPaint.density = context.resources.displayMetrics.density
textView.gravity = Gravity.CENTER
textView.setTextColor(if (inverted) avatar.color.backgroundColor else avatar.color.foregroundColor)
textView.setForceCustomEmoji(true)
layout.addView(textView) setBounds(0, 0, size, size)
textView.updateLayoutParams {
width = size
height = size
}
layout.measure(size, size)
layout.layout(0, 0, size, size)
} }
override fun getIntrinsicHeight(): Int = size
override fun getIntrinsicWidth(): Int = size
override fun draw(canvas: Canvas) { override fun draw(canvas: Canvas) {
layout.draw(canvas) val textSize = Avatars.getTextSizeForLength(context, avatar.text, size * 0.8f, size * 0.45f)
val width = bounds.width()
val candidates = EmojiProvider.getCandidates(avatar.text)
var hasEmoji = false
textPaint.textSize = textSize
val newText = if (candidates == null || candidates.size() == 0) {
SpannableString(avatar.text)
} else {
EmojiProvider.emojify(context, candidates, avatar.text, textPaint, synchronous)
}
if (newText == null) return
val layout = StaticLayout(SpannableString(newText), textPaint, width, Layout.Alignment.ALIGN_NORMAL, 0f, 0f, true)
layout.draw(canvas, getStartX(layout), ((bounds.height() / 2) - ((layout.height / 2))).toFloat())
}
private fun getStartX(layout: StaticLayout): Float {
val direction = layout.getParagraphDirection(0)
val lineWidth = layout.getLineWidth(0)
val width = bounds.width()
val xPos = (width - lineWidth) / 2
return if (direction == Layout.DIR_LEFT_TO_RIGHT) xPos else -xPos
} }
override fun setAlpha(alpha: Int) = Unit override fun setAlpha(alpha: Int) = Unit
@ -56,4 +63,10 @@ class TextAvatarDrawable(
override fun setColorFilter(colorFilter: ColorFilter?) = Unit override fun setColorFilter(colorFilter: ColorFilter?) = Unit
override fun getOpacity(): Int = PixelFormat.OPAQUE override fun getOpacity(): Int = PixelFormat.OPAQUE
private fun Layout.draw(canvas: Canvas, x: Float, y: Float) {
canvas.withTranslation(x, y) {
draw(canvas)
}
}
} }

View file

@ -44,6 +44,7 @@ import org.thoughtcrime.securesms.util.AvatarUtil;
import org.thoughtcrime.securesms.util.BlurTransformation; import org.thoughtcrime.securesms.util.BlurTransformation;
import org.thoughtcrime.securesms.util.ThemeUtil; import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -207,8 +208,8 @@ public final class AvatarImageView extends AppCompatImageView {
this.chatColors = chatColors; this.chatColors = chatColors;
recipientContactPhoto = photo; recipientContactPhoto = photo;
Drawable fallbackContactPhotoDrawable = size == SIZE_SMALL ? photo.recipient.getSmallFallbackContactPhotoDrawable(getContext(), inverted, fallbackPhotoProvider) Drawable fallbackContactPhotoDrawable = size == SIZE_SMALL ? photo.recipient.getSmallFallbackContactPhotoDrawable(getContext(), inverted, fallbackPhotoProvider, ViewUtil.getWidth(this))
: photo.recipient.getFallbackContactPhotoDrawable(getContext(), inverted, fallbackPhotoProvider); : photo.recipient.getFallbackContactPhotoDrawable(getContext(), inverted, fallbackPhotoProvider, ViewUtil.getWidth(this));
if (fixedSizeTarget != null) { if (fixedSizeTarget != null) {
requestManager.clear(fixedSizeTarget); requestManager.clear(fixedSizeTarget);

View file

@ -26,13 +26,15 @@ import org.thoughtcrime.securesms.util.FutureTaskListener;
import org.thoughtcrime.securesms.util.ListenableFutureTask; import org.thoughtcrime.securesms.util.ListenableFutureTask;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
class EmojiProvider { public class EmojiProvider {
private static final String TAG = Log.tag(EmojiProvider.class); private static final String TAG = Log.tag(EmojiProvider.class);
private static final Paint PAINT = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG); private static final Paint PAINT = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
static @Nullable EmojiParser.CandidateList getCandidates(@Nullable CharSequence text) { public static @Nullable EmojiParser.CandidateList getCandidates(@Nullable CharSequence text) {
if (text == null) return null; if (text == null) return null;
return new EmojiParser(EmojiSource.getLatest().getEmojiTree()).findCandidates(text); return new EmojiParser(EmojiSource.getLatest().getEmojiTree()).findCandidates(text);
} }
@ -64,6 +66,32 @@ class EmojiProvider {
return builder; return builder;
} }
public static @Nullable Spannable emojify(@NonNull Context context,
@Nullable EmojiParser.CandidateList matches,
@Nullable CharSequence text,
@NonNull Paint paint,
boolean synchronous)
{
if (matches == null || text == null) return null;
SpannableStringBuilder builder = new SpannableStringBuilder(text);
for (EmojiParser.Candidate candidate : matches) {
Drawable drawable;
if (synchronous) {
drawable = getEmojiDrawableSync(context, candidate.getDrawInfo());
} else {
drawable = getEmojiDrawable(context, candidate.getDrawInfo(), null);
}
if (drawable != null) {
builder.setSpan(new EmojiSpan(context, drawable, paint), candidate.getStartIndex(), candidate.getEndIndex(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
return builder;
}
static @Nullable Drawable getEmojiDrawable(@NonNull Context context, @Nullable CharSequence emoji) { static @Nullable Drawable getEmojiDrawable(@NonNull Context context, @Nullable CharSequence emoji) {
EmojiDrawInfo drawInfo = EmojiSource.getLatest().getEmojiTree().getEmoji(emoji, 0, emoji.length()); EmojiDrawInfo drawInfo = EmojiSource.getLatest().getEmojiTree().getEmoji(emoji, 0, emoji.length());
return getEmojiDrawable(context, drawInfo, null); return getEmojiDrawable(context, drawInfo, null);
@ -113,6 +141,43 @@ class EmojiProvider {
return drawable; return drawable;
} }
/**
* Gets an EmojiDrawable from the Page Cache synchronously
*
* @param context Context object used in reading and writing from disk
* @param drawInfo Information about the emoji being displayed
*/
private static @Nullable Drawable getEmojiDrawableSync(@NonNull Context context, @Nullable EmojiDrawInfo drawInfo) {
ThreadUtil.assertNotMainThread();
if (drawInfo == null) {
return null;
}
final int lowMemoryDecodeScale = DeviceProperties.isLowMemoryDevice(context) ? 2 : 1;
final EmojiSource source = EmojiSource.getLatest();
final EmojiDrawable drawable = new EmojiDrawable(source, drawInfo, lowMemoryDecodeScale);
EmojiPageCache.LoadResult loadResult = EmojiPageCache.INSTANCE.load(context, drawInfo.getPage(), lowMemoryDecodeScale);
Bitmap bitmap = null;
if (loadResult instanceof EmojiPageCache.LoadResult.Immediate) {
Log.d(TAG, "Cached emoji page: " + drawInfo.getPage().getUri().toString());
bitmap = ((EmojiPageCache.LoadResult.Immediate) loadResult).getBitmap();
} else if (loadResult instanceof EmojiPageCache.LoadResult.Async) {
Log.d(TAG, "Loading emoji page: " + drawInfo.getPage().getUri().toString());
try {
bitmap = ((EmojiPageCache.LoadResult.Async) loadResult).getTask().get(2, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException exception) {
Log.d(TAG, "Failed to load emoji bitmap resource", exception);
}
} else {
throw new IllegalStateException("Unexpected subclass " + loadResult.getClass());
}
drawable.setBitmap(bitmap);
return drawable;
}
static final class EmojiDrawable extends Drawable { static final class EmojiDrawable extends Drawable {
private final float intrinsicWidth; private final float intrinsicWidth;
private final float intrinsicHeight; private final float intrinsicHeight;
@ -160,7 +225,6 @@ class EmojiProvider {
} }
public void setBitmap(Bitmap bitmap) { public void setBitmap(Bitmap bitmap) {
ThreadUtil.assertMainThread();
if (bmp == null || !bmp.sameAs(bitmap)) { if (bmp == null || !bmp.sameAs(bitmap)) {
bmp = bitmap; bmp = bitmap;
invalidateSelf(); invalidateSelf();

View file

@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.components.emoji; package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt; import android.graphics.Paint.FontMetricsInt;
@ -25,6 +26,15 @@ public class EmojiSpan extends AnimatingImageSpan {
getDrawable().setBounds(0, 0, size, size); getDrawable().setBounds(0, 0, size, size);
} }
public EmojiSpan(@NonNull Context context, @NonNull Drawable drawable, @NonNull Paint paint) {
super(drawable, null);
fontMetrics = paint.getFontMetricsInt();
size = fontMetrics != null ? Math.abs(fontMetrics.descent) + Math.abs(fontMetrics.ascent)
: context.getResources().getDimensionPixelSize(R.dimen.conversation_item_body_text_size);
getDrawable().setBounds(0, 0, size, size);
}
@Override @Override
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, FontMetricsInt fm) { public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, FontMetricsInt fm) {
if (fm != null && this.fontMetrics != null) { if (fm != null && this.fontMetrics != null) {
@ -48,6 +58,7 @@ public class EmojiSpan extends AnimatingImageSpan {
int height = bottom - top; int height = bottom - top;
int centeringMargin = (height - size) / 2; int centeringMargin = (height - size) / 2;
int adjustedMargin = (int) (centeringMargin * SHIFT_FACTOR); int adjustedMargin = (int) (centeringMargin * SHIFT_FACTOR);
int adjustedBottom = bottom - adjustedMargin;
super.draw(canvas, text, start, end, x, top, y, bottom - adjustedMargin, paint); super.draw(canvas, text, start, end, x, top, y, bottom - adjustedMargin, paint);
} }
} }

View file

@ -33,6 +33,7 @@ open class SimpleEmojiTextView @JvmOverloads constructor(
} else { } else {
EmojiProvider.emojify(newCandidates, newContent, this) EmojiProvider.emojify(newCandidates, newContent, this)
} }
bufferType = BufferType.SPANNABLE
super.setText(newText, type) super.setText(newText, type)
} }
} }

View file

@ -45,7 +45,7 @@ public class GeneratedContactPhoto implements FallbackContactPhoto {
@Override @Override
public Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) { public Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
int targetSize = this.targetSize != -1 int targetSize = this.targetSize > 0
? this.targetSize ? this.targetSize
: context.getResources().getDimensionPixelSize(R.dimen.contact_photo_target_size); : context.getResources().getDimensionPixelSize(R.dimen.contact_photo_target_size);
@ -54,7 +54,7 @@ public class GeneratedContactPhoto implements FallbackContactPhoto {
if (!TextUtils.isEmpty(character)) { if (!TextUtils.isEmpty(character)) {
Avatars.ForegroundColor foregroundColor = Avatars.getForegroundColor(color); Avatars.ForegroundColor foregroundColor = Avatars.getForegroundColor(color);
Avatar.Text avatar = new Avatar.Text(character, new Avatars.ColorPair(color, foregroundColor), Avatar.DatabaseId.DoNotPersist.INSTANCE); Avatar.Text avatar = new Avatar.Text(character, new Avatars.ColorPair(color, foregroundColor), Avatar.DatabaseId.DoNotPersist.INSTANCE);
Drawable foreground = AvatarRenderer.createTextDrawable(context, avatar, inverted, targetSize); Drawable foreground = AvatarRenderer.createTextDrawable(context, avatar, inverted, targetSize, false);
Drawable background = Objects.requireNonNull(ContextCompat.getDrawable(context, R.drawable.circle_tintable)); Drawable background = Objects.requireNonNull(ContextCompat.getDrawable(context, R.drawable.circle_tintable));
background.setColorFilter(new SimpleColorFilter(inverted ? foregroundColor.getColorInt() : color.colorInt())); background.setColorFilter(new SimpleColorFilter(inverted ? foregroundColor.getColorInt() : color.colorInt()));

View file

@ -490,7 +490,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
private void initializeProfileIcon(@NonNull Recipient recipient) { private void initializeProfileIcon(@NonNull Recipient recipient) {
ImageView icon = requireView().findViewById(R.id.toolbar_icon); ImageView icon = requireView().findViewById(R.id.toolbar_icon);
AvatarUtil.loadIconIntoImageView(recipient, icon); AvatarUtil.loadIconIntoImageView(recipient, icon, getResources().getDimensionPixelSize(R.dimen.toolbar_avatar_size));
icon.setOnClickListener(v -> getNavigator().goToAppSettings()); icon.setOnClickListener(v -> getNavigator().goToAppSettings());
} }

View file

@ -106,7 +106,7 @@ public class ReviewBannerView extends LinearLayout {
@NonNull @NonNull
@Override @Override
public FallbackContactPhoto getPhotoForRecipientWithName(String name) { public FallbackContactPhoto getPhotoForRecipientWithName(String name, int targetSize) {
return new FixedSizeGeneratedContactPhoto(name, R.drawable.ic_profile_outline_20); return new FixedSizeGeneratedContactPhoto(name, R.drawable.ic_profile_outline_20);
} }

View file

@ -40,6 +40,7 @@ import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.phonenumbers.NumberUtil; import org.thoughtcrime.securesms.phonenumbers.NumberUtil;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.profiles.ProfileName; import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.util.AvatarUtil;
import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.StringUtil; import org.thoughtcrime.securesms.util.StringUtil;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
@ -791,19 +792,23 @@ public class Recipient {
} }
public @NonNull Drawable getFallbackContactPhotoDrawable(Context context, boolean inverted) { public @NonNull Drawable getFallbackContactPhotoDrawable(Context context, boolean inverted) {
return getFallbackContactPhotoDrawable(context, inverted, DEFAULT_FALLBACK_PHOTO_PROVIDER); return getFallbackContactPhotoDrawable(context, inverted, DEFAULT_FALLBACK_PHOTO_PROVIDER, AvatarUtil.UNDEFINED_SIZE);
} }
public @NonNull Drawable getSmallFallbackContactPhotoDrawable(Context context, boolean inverted) { public @NonNull Drawable getSmallFallbackContactPhotoDrawable(Context context, boolean inverted) {
return getSmallFallbackContactPhotoDrawable(context, inverted, DEFAULT_FALLBACK_PHOTO_PROVIDER); return getSmallFallbackContactPhotoDrawable(context, inverted, DEFAULT_FALLBACK_PHOTO_PROVIDER);
} }
public @NonNull Drawable getFallbackContactPhotoDrawable(Context context, boolean inverted, @Nullable FallbackPhotoProvider fallbackPhotoProvider) { public @NonNull Drawable getFallbackContactPhotoDrawable(Context context, boolean inverted, @Nullable FallbackPhotoProvider fallbackPhotoProvider, int targetSize) {
return getFallbackContactPhoto(Util.firstNonNull(fallbackPhotoProvider, DEFAULT_FALLBACK_PHOTO_PROVIDER)).asDrawable(context, avatarColor, inverted); return getFallbackContactPhoto(Util.firstNonNull(fallbackPhotoProvider, DEFAULT_FALLBACK_PHOTO_PROVIDER), targetSize).asDrawable(context, avatarColor, inverted);
} }
public @NonNull Drawable getSmallFallbackContactPhotoDrawable(Context context, boolean inverted, @Nullable FallbackPhotoProvider fallbackPhotoProvider) { public @NonNull Drawable getSmallFallbackContactPhotoDrawable(Context context, boolean inverted, @Nullable FallbackPhotoProvider fallbackPhotoProvider) {
return getFallbackContactPhoto(Util.firstNonNull(fallbackPhotoProvider, DEFAULT_FALLBACK_PHOTO_PROVIDER)).asSmallDrawable(context, avatarColor, inverted); return getSmallFallbackContactPhotoDrawable(context, inverted, fallbackPhotoProvider, AvatarUtil.UNDEFINED_SIZE);
}
public @NonNull Drawable getSmallFallbackContactPhotoDrawable(Context context, boolean inverted, @Nullable FallbackPhotoProvider fallbackPhotoProvider, int targetSize) {
return getFallbackContactPhoto(Util.firstNonNull(fallbackPhotoProvider, DEFAULT_FALLBACK_PHOTO_PROVIDER), targetSize).asSmallDrawable(context, avatarColor, inverted);
} }
public @NonNull FallbackContactPhoto getFallbackContactPhoto() { public @NonNull FallbackContactPhoto getFallbackContactPhoto() {
@ -811,13 +816,17 @@ public class Recipient {
} }
public @NonNull FallbackContactPhoto getFallbackContactPhoto(@NonNull FallbackPhotoProvider fallbackPhotoProvider) { public @NonNull FallbackContactPhoto getFallbackContactPhoto(@NonNull FallbackPhotoProvider fallbackPhotoProvider) {
return getFallbackContactPhoto(fallbackPhotoProvider, AvatarUtil.UNDEFINED_SIZE);
}
public @NonNull FallbackContactPhoto getFallbackContactPhoto(@NonNull FallbackPhotoProvider fallbackPhotoProvider, int targetSize) {
if (isSelf) return fallbackPhotoProvider.getPhotoForLocalNumber(); if (isSelf) return fallbackPhotoProvider.getPhotoForLocalNumber();
else if (isResolving()) return fallbackPhotoProvider.getPhotoForResolvingRecipient(); else if (isResolving()) return fallbackPhotoProvider.getPhotoForResolvingRecipient();
else if (isGroupInternal()) return fallbackPhotoProvider.getPhotoForGroup(); else if (isGroupInternal()) return fallbackPhotoProvider.getPhotoForGroup();
else if (isGroup()) return fallbackPhotoProvider.getPhotoForGroup(); else if (isGroup()) return fallbackPhotoProvider.getPhotoForGroup();
else if (!TextUtils.isEmpty(groupName)) return fallbackPhotoProvider.getPhotoForRecipientWithName(groupName); else if (!TextUtils.isEmpty(groupName)) return fallbackPhotoProvider.getPhotoForRecipientWithName(groupName, targetSize);
else if (!TextUtils.isEmpty(systemContactName)) return fallbackPhotoProvider.getPhotoForRecipientWithName(systemContactName); else if (!TextUtils.isEmpty(systemContactName)) return fallbackPhotoProvider.getPhotoForRecipientWithName(systemContactName, targetSize);
else if (!signalProfileName.isEmpty()) return fallbackPhotoProvider.getPhotoForRecipientWithName(signalProfileName.toString()); else if (!signalProfileName.isEmpty()) return fallbackPhotoProvider.getPhotoForRecipientWithName(signalProfileName.toString(), targetSize);
else return fallbackPhotoProvider.getPhotoForRecipientWithoutName(); else return fallbackPhotoProvider.getPhotoForRecipientWithoutName();
} }
@ -1224,8 +1233,8 @@ public class Recipient {
return new ResourceContactPhoto(R.drawable.ic_group_outline_34, R.drawable.ic_group_outline_20, R.drawable.ic_group_outline_48); return new ResourceContactPhoto(R.drawable.ic_group_outline_34, R.drawable.ic_group_outline_20, R.drawable.ic_group_outline_48);
} }
public @NonNull FallbackContactPhoto getPhotoForRecipientWithName(String name) { public @NonNull FallbackContactPhoto getPhotoForRecipientWithName(String name, int targetSize) {
return new GeneratedContactPhoto(name, R.drawable.ic_profile_outline_40); return new GeneratedContactPhoto(name, R.drawable.ic_profile_outline_40, targetSize);
} }
public @NonNull FallbackContactPhoto getPhotoForRecipientWithoutName() { public @NonNull FallbackContactPhoto getPhotoForRecipientWithoutName() {

View file

@ -30,6 +30,8 @@ import java.util.concurrent.ExecutionException;
public final class AvatarUtil { public final class AvatarUtil {
public static final int UNDEFINED_SIZE = -1;
private AvatarUtil() { private AvatarUtil() {
} }
@ -71,9 +73,13 @@ public final class AvatarUtil {
} }
public static void loadIconIntoImageView(@NonNull Recipient recipient, @NonNull ImageView target) { public static void loadIconIntoImageView(@NonNull Recipient recipient, @NonNull ImageView target) {
loadIconIntoImageView(recipient, target, -1);
}
public static void loadIconIntoImageView(@NonNull Recipient recipient, @NonNull ImageView target, int requestedSize) {
Context context = target.getContext(); Context context = target.getContext();
requestCircle(GlideApp.with(context).asDrawable(), context, recipient).into(target); requestCircle(GlideApp.with(context).asDrawable(), context, recipient, requestedSize).into(target);
} }
public static Bitmap loadIconBitmapSquareNoCache(@NonNull Context context, public static Bitmap loadIconBitmapSquareNoCache(@NonNull Context context,
@ -92,7 +98,7 @@ public final class AvatarUtil {
@WorkerThread @WorkerThread
public static IconCompat getIconForNotification(@NonNull Context context, @NonNull Recipient recipient) { public static IconCompat getIconForNotification(@NonNull Context context, @NonNull Recipient recipient) {
try { try {
return IconCompat.createWithBitmap(requestCircle(GlideApp.with(context).asBitmap(), context, recipient).submit().get()); return IconCompat.createWithBitmap(requestCircle(GlideApp.with(context).asBitmap(), context, recipient, UNDEFINED_SIZE).submit().get());
} catch (ExecutionException | InterruptedException e) { } catch (ExecutionException | InterruptedException e) {
return null; return null;
} }
@ -114,25 +120,25 @@ public final class AvatarUtil {
@WorkerThread @WorkerThread
public static Bitmap getBitmapForNotification(@NonNull Context context, @NonNull Recipient recipient) { public static Bitmap getBitmapForNotification(@NonNull Context context, @NonNull Recipient recipient) {
try { try {
return requestCircle(GlideApp.with(context).asBitmap(), context, recipient).submit().get(); return requestCircle(GlideApp.with(context).asBitmap(), context, recipient, UNDEFINED_SIZE).submit().get();
} catch (ExecutionException | InterruptedException e) { } catch (ExecutionException | InterruptedException e) {
return null; return null;
} }
} }
private static <T> GlideRequest<T> requestCircle(@NonNull GlideRequest<T> glideRequest, @NonNull Context context, @NonNull Recipient recipient) { private static <T> GlideRequest<T> requestCircle(@NonNull GlideRequest<T> glideRequest, @NonNull Context context, @NonNull Recipient recipient, int targetSize) {
return request(glideRequest, context, recipient).circleCrop(); return request(glideRequest, context, recipient, targetSize).circleCrop();
} }
private static <T> GlideRequest<T> requestSquare(@NonNull GlideRequest<T> glideRequest, @NonNull Context context, @NonNull Recipient recipient) { private static <T> GlideRequest<T> requestSquare(@NonNull GlideRequest<T> glideRequest, @NonNull Context context, @NonNull Recipient recipient) {
return request(glideRequest, context, recipient).centerCrop(); return request(glideRequest, context, recipient, UNDEFINED_SIZE).centerCrop();
} }
private static <T> GlideRequest<T> request(@NonNull GlideRequest<T> glideRequest, @NonNull Context context, @NonNull Recipient recipient) { private static <T> GlideRequest<T> request(@NonNull GlideRequest<T> glideRequest, @NonNull Context context, @NonNull Recipient recipient, int targetSize) {
return request(glideRequest, context, recipient, true); return request(glideRequest, context, recipient, true, targetSize);
} }
private static <T> GlideRequest<T> request(@NonNull GlideRequest<T> glideRequest, @NonNull Context context, @NonNull Recipient recipient, boolean loadSelf) { private static <T> GlideRequest<T> request(@NonNull GlideRequest<T> glideRequest, @NonNull Context context, @NonNull Recipient recipient, boolean loadSelf, int targetSize) {
final ContactPhoto photo; final ContactPhoto photo;
if (Recipient.self().equals(recipient) && loadSelf) { if (Recipient.self().equals(recipient) && loadSelf) {
photo = new ProfileContactPhoto(recipient, recipient.getProfileAvatar()); photo = new ProfileContactPhoto(recipient, recipient.getProfileAvatar());
@ -141,7 +147,7 @@ public final class AvatarUtil {
} }
final GlideRequest<T> request = glideRequest.load(photo) final GlideRequest<T> request = glideRequest.load(photo)
.error(getFallback(context, recipient)) .error(getFallback(context, recipient, targetSize))
.diskCacheStrategy(DiskCacheStrategy.ALL); .diskCacheStrategy(DiskCacheStrategy.ALL);
if (recipient.shouldBlurAvatar()) { if (recipient.shouldBlurAvatar()) {
@ -151,9 +157,9 @@ public final class AvatarUtil {
} }
} }
private static Drawable getFallback(@NonNull Context context, @NonNull Recipient recipient) { private static Drawable getFallback(@NonNull Context context, @NonNull Recipient recipient, int targetSize) {
String name = Optional.fromNullable(recipient.getDisplayName(context)).or(""); String name = Optional.fromNullable(recipient.getDisplayName(context)).or("");
return new GeneratedContactPhoto(name, R.drawable.ic_profile_outline_40).asDrawable(context, recipient.getAvatarColor()); return new GeneratedContactPhoto(name, R.drawable.ic_profile_outline_40, targetSize).asDrawable(context, recipient.getAvatarColor());
} }
} }

View file

@ -280,6 +280,10 @@ public final class ViewUtil {
view.requestLayout(); view.requestLayout();
} }
public static int getWidth(@NonNull View view) {
return view.getLayoutParams().width;
}
public static void setPaddingTop(@NonNull View view, int padding) { public static void setPaddingTop(@NonNull View view, int padding) {
view.setPadding(view.getPaddingLeft(), padding, view.getPaddingRight(), view.getPaddingBottom()); view.setPadding(view.getPaddingLeft(), padding, view.getPaddingRight(), view.getPaddingBottom());
} }

View file

@ -35,8 +35,8 @@
<org.thoughtcrime.securesms.components.AvatarImageView <org.thoughtcrime.securesms.components.AvatarImageView
android:id="@+id/toolbar_icon" android:id="@+id/toolbar_icon"
android:layout_width="28dp" android:layout_width="@dimen/toolbar_avatar_size"
android:layout_height="28dp" android:layout_height="@dimen/toolbar_avatar_size"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:contentDescription="@string/conversation_list_settings_shortcut" android:contentDescription="@string/conversation_list_settings_shortcut"
android:layout_marginStart="@dimen/toolbar_avatar_margin" android:layout_marginStart="@dimen/toolbar_avatar_margin"

View file

@ -15,8 +15,8 @@
<org.thoughtcrime.securesms.components.AvatarImageView <org.thoughtcrime.securesms.components.AvatarImageView
android:id="@+id/conversation_list_item_avatar" android:id="@+id/conversation_list_item_avatar"
android:layout_width="48dp" android:layout_width="@dimen/conversation_list_avatar_size"
android:layout_height="48dp" android:layout_height="@dimen/conversation_list_avatar_size"
android:contentDescription="@string/conversation_list_item_view__contact_photo_image" android:contentDescription="@string/conversation_list_item_view__contact_photo_image"
android:foreground="@drawable/contact_photo_background" android:foreground="@drawable/contact_photo_background"
android:layout_marginTop="12dp" android:layout_marginTop="12dp"

View file

@ -202,7 +202,9 @@
<dimen name="avatar_picker_image_width">100dp</dimen> <dimen name="avatar_picker_image_width">100dp</dimen>
<dimen name="toolbar_avatar_size">28dp</dimen>
<dimen name="toolbar_avatar_margin">26dp</dimen> <dimen name="toolbar_avatar_margin">26dp</dimen>
<dimen name="conversation_list_avatar_size">48dp</dimen>
<dimen name="verify_identity_vertical_margin">16dp</dimen> <dimen name="verify_identity_vertical_margin">16dp</dimen>
</resources> </resources>