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,
inverted: Boolean = false,
size: Int = DIMENSIONS,
synchronous: Boolean = false
): 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) {
@ -66,7 +67,7 @@ object AvatarRenderer {
private fun renderText(context: Context, avatar: Avatar.Text, onAvatarRendered: (Media) -> Unit, onRenderFailed: (Throwable?) -> Unit) {
renderInBackground(context, onAvatarRendered, onRenderFailed) { canvas ->
val textDrawable = createTextDrawable(context, avatar)
val textDrawable = createTextDrawable(context, avatar, synchronous = true)
canvas.drawColor(avatar.color.backgroundColor)
textDrawable.draw(canvas)

View file

@ -3,52 +3,59 @@ package org.thoughtcrime.securesms.avatar
import android.content.Context
import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.Paint
import android.graphics.PixelFormat
import android.graphics.drawable.Drawable
import android.util.TypedValue
import android.view.Gravity
import android.widget.FrameLayout
import androidx.core.view.updateLayoutParams
import org.thoughtcrime.securesms.components.emoji.EmojiTextView
import android.text.Layout
import android.text.SpannableString
import android.text.StaticLayout
import android.text.TextPaint
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(
context: Context,
avatar: Avatar.Text,
private val context: Context,
private val avatar: Avatar.Text,
inverted: Boolean = false,
private val size: Int = AvatarRenderer.DIMENSIONS,
private val synchronous: Boolean = false
) : Drawable() {
private val layout: FrameLayout = FrameLayout(context)
private val textView: EmojiTextView = EmojiTextView(context)
private val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
init {
textView.typeface = AvatarRenderer.getTypeface(context)
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, Avatars.getTextSizeForLength(context, avatar.text, size * 0.8f, size * 0.45f))
textView.text = avatar.text
textView.gravity = Gravity.CENTER
textView.setTextColor(if (inverted) avatar.color.backgroundColor else avatar.color.foregroundColor)
textView.setForceCustomEmoji(true)
textPaint.typeface = AvatarRenderer.getTypeface(context)
textPaint.color = if (inverted) avatar.color.backgroundColor else avatar.color.foregroundColor
textPaint.density = context.resources.displayMetrics.density
layout.addView(textView)
textView.updateLayoutParams {
width = size
height = size
}
layout.measure(size, size)
layout.layout(0, 0, size, size)
setBounds(0, 0, size, size)
}
override fun getIntrinsicHeight(): Int = size
override fun getIntrinsicWidth(): Int = size
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
@ -56,4 +63,10 @@ class TextAvatarDrawable(
override fun setColorFilter(colorFilter: ColorFilter?) = Unit
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.ThemeUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.ArrayList;
import java.util.List;
@ -207,8 +208,8 @@ public final class AvatarImageView extends AppCompatImageView {
this.chatColors = chatColors;
recipientContactPhoto = photo;
Drawable fallbackContactPhotoDrawable = size == SIZE_SMALL ? photo.recipient.getSmallFallbackContactPhotoDrawable(getContext(), inverted, fallbackPhotoProvider)
: photo.recipient.getFallbackContactPhotoDrawable(getContext(), inverted, fallbackPhotoProvider);
Drawable fallbackContactPhotoDrawable = size == SIZE_SMALL ? photo.recipient.getSmallFallbackContactPhotoDrawable(getContext(), inverted, fallbackPhotoProvider, ViewUtil.getWidth(this))
: photo.recipient.getFallbackContactPhotoDrawable(getContext(), inverted, fallbackPhotoProvider, ViewUtil.getWidth(this));
if (fixedSizeTarget != null) {
requestManager.clear(fixedSizeTarget);

View file

@ -26,13 +26,15 @@ import org.thoughtcrime.securesms.util.FutureTaskListener;
import org.thoughtcrime.securesms.util.ListenableFutureTask;
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 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;
return new EmojiParser(EmojiSource.getLatest().getEmojiTree()).findCandidates(text);
}
@ -64,6 +66,32 @@ class EmojiProvider {
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) {
EmojiDrawInfo drawInfo = EmojiSource.getLatest().getEmojiTree().getEmoji(emoji, 0, emoji.length());
return getEmojiDrawable(context, drawInfo, null);
@ -113,6 +141,43 @@ class EmojiProvider {
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 {
private final float intrinsicWidth;
private final float intrinsicHeight;
@ -160,7 +225,6 @@ class EmojiProvider {
}
public void setBitmap(Bitmap bitmap) {
ThreadUtil.assertMainThread();
if (bmp == null || !bmp.sameAs(bitmap)) {
bmp = bitmap;
invalidateSelf();

View file

@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.components.emoji;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
@ -25,6 +26,15 @@ public class EmojiSpan extends AnimatingImageSpan {
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
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, FontMetricsInt fm) {
if (fm != null && this.fontMetrics != null) {
@ -48,6 +58,7 @@ public class EmojiSpan extends AnimatingImageSpan {
int height = bottom - top;
int centeringMargin = (height - size) / 2;
int adjustedMargin = (int) (centeringMargin * SHIFT_FACTOR);
int adjustedBottom = bottom - adjustedMargin;
super.draw(canvas, text, start, end, x, top, y, bottom - adjustedMargin, paint);
}
}

View file

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

View file

@ -45,7 +45,7 @@ public class GeneratedContactPhoto implements FallbackContactPhoto {
@Override
public Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
int targetSize = this.targetSize != -1
int targetSize = this.targetSize > 0
? this.targetSize
: context.getResources().getDimensionPixelSize(R.dimen.contact_photo_target_size);
@ -54,7 +54,7 @@ public class GeneratedContactPhoto implements FallbackContactPhoto {
if (!TextUtils.isEmpty(character)) {
Avatars.ForegroundColor foregroundColor = Avatars.getForegroundColor(color);
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));
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) {
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());
}

View file

@ -106,7 +106,7 @@ public class ReviewBannerView extends LinearLayout {
@NonNull
@Override
public FallbackContactPhoto getPhotoForRecipientWithName(String name) {
public FallbackContactPhoto getPhotoForRecipientWithName(String name, int targetSize) {
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.PhoneNumberFormatter;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.util.AvatarUtil;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.StringUtil;
import org.thoughtcrime.securesms.util.Util;
@ -791,19 +792,23 @@ public class Recipient {
}
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) {
return getSmallFallbackContactPhotoDrawable(context, inverted, DEFAULT_FALLBACK_PHOTO_PROVIDER);
}
public @NonNull Drawable getFallbackContactPhotoDrawable(Context context, boolean inverted, @Nullable FallbackPhotoProvider fallbackPhotoProvider) {
return getFallbackContactPhoto(Util.firstNonNull(fallbackPhotoProvider, DEFAULT_FALLBACK_PHOTO_PROVIDER)).asDrawable(context, avatarColor, inverted);
public @NonNull Drawable getFallbackContactPhotoDrawable(Context context, boolean inverted, @Nullable FallbackPhotoProvider fallbackPhotoProvider, int targetSize) {
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) {
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() {
@ -811,13 +816,17 @@ public class Recipient {
}
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();
else if (isResolving()) return fallbackPhotoProvider.getPhotoForResolvingRecipient();
else if (isGroupInternal()) return fallbackPhotoProvider.getPhotoForGroup();
else if (isGroup()) return fallbackPhotoProvider.getPhotoForGroup();
else if (!TextUtils.isEmpty(groupName)) return fallbackPhotoProvider.getPhotoForRecipientWithName(groupName);
else if (!TextUtils.isEmpty(systemContactName)) return fallbackPhotoProvider.getPhotoForRecipientWithName(systemContactName);
else if (!signalProfileName.isEmpty()) return fallbackPhotoProvider.getPhotoForRecipientWithName(signalProfileName.toString());
else if (!TextUtils.isEmpty(groupName)) return fallbackPhotoProvider.getPhotoForRecipientWithName(groupName, targetSize);
else if (!TextUtils.isEmpty(systemContactName)) return fallbackPhotoProvider.getPhotoForRecipientWithName(systemContactName, targetSize);
else if (!signalProfileName.isEmpty()) return fallbackPhotoProvider.getPhotoForRecipientWithName(signalProfileName.toString(), targetSize);
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);
}
public @NonNull FallbackContactPhoto getPhotoForRecipientWithName(String name) {
return new GeneratedContactPhoto(name, R.drawable.ic_profile_outline_40);
public @NonNull FallbackContactPhoto getPhotoForRecipientWithName(String name, int targetSize) {
return new GeneratedContactPhoto(name, R.drawable.ic_profile_outline_40, targetSize);
}
public @NonNull FallbackContactPhoto getPhotoForRecipientWithoutName() {

View file

@ -30,6 +30,8 @@ import java.util.concurrent.ExecutionException;
public final class AvatarUtil {
public static final int UNDEFINED_SIZE = -1;
private AvatarUtil() {
}
@ -71,9 +73,13 @@ public final class AvatarUtil {
}
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();
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,
@ -92,7 +98,7 @@ public final class AvatarUtil {
@WorkerThread
public static IconCompat getIconForNotification(@NonNull Context context, @NonNull Recipient recipient) {
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) {
return null;
}
@ -114,25 +120,25 @@ public final class AvatarUtil {
@WorkerThread
public static Bitmap getBitmapForNotification(@NonNull Context context, @NonNull Recipient recipient) {
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) {
return null;
}
}
private static <T> GlideRequest<T> requestCircle(@NonNull GlideRequest<T> glideRequest, @NonNull Context context, @NonNull Recipient recipient) {
return request(glideRequest, context, recipient).circleCrop();
private static <T> GlideRequest<T> requestCircle(@NonNull GlideRequest<T> glideRequest, @NonNull Context context, @NonNull Recipient recipient, int targetSize) {
return request(glideRequest, context, recipient, targetSize).circleCrop();
}
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) {
return request(glideRequest, context, recipient, true);
private static <T> GlideRequest<T> request(@NonNull GlideRequest<T> glideRequest, @NonNull Context context, @NonNull Recipient recipient, int targetSize) {
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;
if (Recipient.self().equals(recipient) && loadSelf) {
photo = new ProfileContactPhoto(recipient, recipient.getProfileAvatar());
@ -141,7 +147,7 @@ public final class AvatarUtil {
}
final GlideRequest<T> request = glideRequest.load(photo)
.error(getFallback(context, recipient))
.error(getFallback(context, recipient, targetSize))
.diskCacheStrategy(DiskCacheStrategy.ALL);
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("");
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();
}
public static int getWidth(@NonNull View view) {
return view.getLayoutParams().width;
}
public static void setPaddingTop(@NonNull View view, int padding) {
view.setPadding(view.getPaddingLeft(), padding, view.getPaddingRight(), view.getPaddingBottom());
}

View file

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

View file

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

View file

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