Rewrite fallbackphoto system.

This commit is contained in:
Alex Hart
2024-06-12 15:59:35 -03:00
committed by Greyson Parrelli
parent d698f74d0b
commit 11557e4815
42 changed files with 676 additions and 805 deletions

View File

@@ -28,11 +28,11 @@ import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.request.transition.Transition;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatar;
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatarDrawable;
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.FullscreenHelper;
@@ -91,16 +91,18 @@ public final class AvatarPreviewActivity extends PassphraseRequiredActivity {
Recipient.live(recipientId).observe(this, recipient -> {
ContactPhoto contactPhoto = recipient.isSelf() ? new ProfileContactPhoto(recipient)
: recipient.getContactPhoto();
FallbackContactPhoto fallbackPhoto = recipient.isSelf() ? new ResourceContactPhoto(R.drawable.ic_profile_outline_40, R.drawable.ic_profile_outline_20, R.drawable.ic_person_large)
: recipient.getFallbackContactPhoto();
FallbackAvatar fallbackAvatar = recipient.isSelf() ? new FallbackAvatar.Resource.Person(recipient.getAvatarColor())
: recipient.getFallbackAvatar();
Drawable fallbackDrawable = new FallbackAvatarDrawable(context, fallbackAvatar);
Resources resources = this.getResources();
Glide.with(this)
.asBitmap()
.load(contactPhoto)
.fallback(fallbackPhoto.asCallCard(this))
.error(fallbackPhoto.asCallCard(this))
.fallback(fallbackDrawable)
.error(fallbackDrawable)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.addListener(new RequestListener<Bitmap>() {
@Override

View File

@@ -0,0 +1,158 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.avatar.fallback
import androidx.annotation.DrawableRes
import androidx.annotation.Px
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import org.signal.core.util.DimensionUnit
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.util.NameUtil
/**
* Specifies what kind of avatar should be generated for a given recipient.
*/
sealed interface FallbackAvatar {
val color: AvatarColor
/**
* Transparent avatar
*/
data object Transparent : FallbackAvatar {
override val color: AvatarColor = AvatarColor.UNKNOWN
}
/**
* Generated avatars utilize the initials of the given recipient
*/
data class Text(val content: String, override val color: AvatarColor) : FallbackAvatar {
init {
check(content.isNotEmpty())
}
}
/**
* Fallback avatars that are backed by resources.
*/
sealed interface Resource : FallbackAvatar {
@DrawableRes
fun getIconBySize(size: Size): Int
/**
* Local user
*/
data class Local(override val color: AvatarColor) : Resource {
override fun getIconBySize(size: Size): Int {
return when (size) {
Size.SMALL -> R.drawable.symbol_note_compact_16
Size.MEDIUM -> R.drawable.symbol_note_24
Size.LARGE -> R.drawable.symbol_note_display_bold_40
}
}
}
/**
* Individual user without a display name.
*/
data class Person(override val color: AvatarColor) : Resource {
override fun getIconBySize(size: Size): Int {
return when (size) {
Size.SMALL -> R.drawable.symbol_person_compact_16
Size.MEDIUM -> R.drawable.symbol_person_24
Size.LARGE -> R.drawable.symbol_person_display_bold_40
}
}
}
/**
* A group
*/
data class Group(override val color: AvatarColor) : Resource {
override fun getIconBySize(size: Size): Int {
return when (size) {
Size.SMALL -> R.drawable.symbol_group_compact_16
Size.MEDIUM -> R.drawable.symbol_group_24
Size.LARGE -> R.drawable.symbol_group_display_bold_40
}
}
}
/**
* Story distribution lists
*/
data class DistributionList(override val color: AvatarColor) : Resource {
override fun getIconBySize(size: Size): Int {
return when (size) {
Size.SMALL -> R.drawable.symbol_stories_compact_16
Size.MEDIUM -> R.drawable.symbol_stories_24
Size.LARGE -> R.drawable.symbol_stories_display_bold_40
}
}
}
/**
* Call Links
*/
data class CallLink(override val color: AvatarColor) : Resource {
override fun getIconBySize(size: Size): Int {
return when (size) {
Size.SMALL -> R.drawable.symbol_video_compact_16
Size.MEDIUM -> R.drawable.symbol_video_24
Size.LARGE -> R.drawable.symbol_video_display_bold_40
}
}
}
}
enum class Size {
/**
* Smaller than 32dp
*/
SMALL,
/**
* 32dp and larger
*/
MEDIUM,
/**
* 80dp and larger
*/
LARGE
}
companion object {
const val ICON_TO_BACKGROUND_SCALE = 0.625
@JvmStatic
@JvmOverloads
fun forTextOrDefault(text: String, avatarColor: AvatarColor, default: FallbackAvatar = Resource.Person(avatarColor)): FallbackAvatar {
val abbreviation = NameUtil.getAbbreviation(text)
return if (abbreviation != null) {
Text(abbreviation, avatarColor)
} else {
default
}
}
fun getSizeByPx(@Px px: Int): Size {
return getSizeByDp(DimensionUnit.PIXELS.toDp(px.toFloat()).dp)
}
fun getSizeByDp(dp: Dp): Size {
val rawDp = dp.value
return when {
rawDp >= 80.0 -> Size.LARGE
rawDp < 32.0 -> Size.SMALL
else -> Size.MEDIUM
}
}
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.avatar.fallback
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.Drawable
import androidx.core.content.ContextCompat
import com.airbnb.lottie.SimpleColorFilter
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.RelativeCornerSize
import com.google.android.material.shape.RoundedCornerTreatment
import com.google.android.material.shape.ShapeAppearanceModel
import org.thoughtcrime.securesms.avatar.Avatar
import org.thoughtcrime.securesms.avatar.Avatars
import org.thoughtcrime.securesms.avatar.TextAvatarDrawable
import org.thoughtcrime.securesms.conversation.colors.AvatarColorPair
class FallbackAvatarDrawable(
private val context: Context,
private val fallbackAvatar: FallbackAvatar
) : MaterialShapeDrawable() {
private val avatarColorPair: AvatarColorPair = AvatarColorPair.create(context, fallbackAvatar.color)
private var avatarSize: FallbackAvatar.Size = FallbackAvatar.Size.SMALL
private var icon: Drawable? = null
init {
fillColor = ColorStateList.valueOf(avatarColorPair.backgroundColor)
}
fun circleCrop(): FallbackAvatarDrawable {
shapeAppearanceModel = ShapeAppearanceModel.builder()
.setAllCorners(RoundedCornerTreatment())
.setAllCornerSizes(RelativeCornerSize(0.5f))
.build()
return this
}
override fun onBoundsChange(bounds: Rect) {
super.onBoundsChange(bounds)
avatarSize = FallbackAvatar.getSizeByPx(bounds.width())
icon = when (fallbackAvatar) {
is FallbackAvatar.Resource -> {
val resourceIcon = ContextCompat.getDrawable(context, fallbackAvatar.getIconBySize(avatarSize))!!
val iconBounds = Rect(bounds)
iconBounds.inset(
((bounds.width() - (bounds.width() * FallbackAvatar.ICON_TO_BACKGROUND_SCALE)) / 2f).toInt(),
((bounds.height() - (bounds.height() * FallbackAvatar.ICON_TO_BACKGROUND_SCALE)) / 2f).toInt()
)
resourceIcon.bounds = iconBounds
resourceIcon
}
is FallbackAvatar.Text -> TextAvatarDrawable(
context = context,
avatar = Avatar.Text(
fallbackAvatar.content,
Avatars.ColorPair(avatarColorPair.backgroundColor, avatarColorPair.foregroundColor, ""),
Avatar.DatabaseId.DoNotPersist
),
size = bounds.width()
)
FallbackAvatar.Transparent -> null
}
icon?.alpha = alpha
icon?.colorFilter = SimpleColorFilter(avatarColorPair.foregroundColor)
}
override fun draw(canvas: Canvas) {
if (icon == null) return
super.draw(canvas)
icon?.draw(canvas)
}
override fun setAlpha(alpha: Int) {
super.setAlpha(alpha)
icon?.alpha = alpha
invalidateSelf()
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.avatar.fallback
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
import org.signal.core.ui.Previews
import org.signal.core.ui.SignalPreview
import org.signal.core.util.DimensionUnit
import org.thoughtcrime.securesms.avatar.AvatarRenderer
import org.thoughtcrime.securesms.avatar.Avatars
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.conversation.colors.AvatarColorPair
@Composable
fun FallbackAvatarImage(
fallbackAvatar: FallbackAvatar,
modifier: Modifier = Modifier,
shape: Shape = CircleShape
) {
if (fallbackAvatar is FallbackAvatar.Transparent) {
Box(modifier = modifier)
return
}
val context = LocalContext.current
val colorPair = remember(fallbackAvatar) {
AvatarColorPair.create(context, fallbackAvatar.color)
}
BoxWithConstraints(
contentAlignment = Alignment.Center,
modifier = modifier
.background(Color(colorPair.backgroundColor), shape)
) {
when (fallbackAvatar) {
is FallbackAvatar.Resource -> {
val size = remember(maxWidth) {
FallbackAvatar.getSizeByDp(maxWidth)
}
val padding = remember(maxWidth) {
((maxWidth.value - (maxWidth.value * FallbackAvatar.ICON_TO_BACKGROUND_SCALE)) / 2).dp
}
Icon(
painter = painterResource(fallbackAvatar.getIconBySize(size)),
contentDescription = null,
tint = Color(colorPair.foregroundColor),
modifier = Modifier
.fillMaxSize()
.padding(padding)
)
}
is FallbackAvatar.Text -> {
val size = DimensionUnit.DP.toPixels(maxWidth.value) * 0.8f
val textSize = DimensionUnit.PIXELS.toDp(Avatars.getTextSizeForLength(context, fallbackAvatar.content, size, size))
// TODO [alex] -- Handle emoji
Text(
text = fallbackAvatar.content,
color = Color(colorPair.foregroundColor),
fontSize = TextUnit(textSize, TextUnitType.Sp),
fontFamily = FontFamily(AvatarRenderer.getTypeface(context))
)
}
FallbackAvatar.Transparent -> {}
}
}
}
@SignalPreview
@Composable
fun FallbackAvatarImagePreview() {
Previews.Preview {
Column {
Text(text = "Compose - Large")
FallbackAvatarImage(
fallbackAvatar = FallbackAvatar.Text("AE", AvatarColor.A100),
modifier = Modifier.size(160.dp)
)
Text(text = "Compose - Medium")
FallbackAvatarImage(
fallbackAvatar = FallbackAvatar.Text("AE", AvatarColor.A100),
modifier = Modifier.size(64.dp)
)
Text(text = "Compose - Small")
FallbackAvatarImage(
fallbackAvatar = FallbackAvatar.Text("AE", AvatarColor.A100),
modifier = Modifier.size(24.dp)
)
}
}
}

View File

@@ -87,8 +87,8 @@ class AvatarView @JvmOverloads constructor(
avatar.setRecipient(recipient)
}
fun setFallbackPhotoProvider(fallbackPhotoProvider: Recipient.FallbackPhotoProvider) {
avatar.setFallbackPhotoProvider(fallbackPhotoProvider)
fun setFallbackAvatarProvider(fallbackAvatarProvider: AvatarImageView.FallbackAvatarProvider?) {
avatar.setFallbackAvatarProvider(fallbackAvatarProvider)
}
fun disableQuickContact() {

View File

@@ -12,7 +12,6 @@ import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.fragment.app.FragmentActivity;
import com.bumptech.glide.Glide;
@@ -29,14 +28,18 @@ import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.SimpleTarget;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.request.transition.Transition;
import com.google.android.material.imageview.ShapeableImageView;
import com.google.android.material.shape.RelativeCornerSize;
import com.google.android.material.shape.RoundedCornerTreatment;
import com.google.android.material.shape.ShapeAppearanceModel;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatar;
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatarDrawable;
import org.thoughtcrime.securesms.components.settings.conversation.ConversationSettingsActivity;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.conversation.colors.ChatColors;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
@@ -46,13 +49,12 @@ import org.thoughtcrime.securesms.recipients.ui.bottomsheet.RecipientBottomSheet
import org.thoughtcrime.securesms.util.AvatarUtil;
import org.thoughtcrime.securesms.util.BlurTransformation;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public final class AvatarImageView extends AppCompatImageView {
public final class AvatarImageView extends ShapeableImageView {
private static final int SIZE_LARGE = 1;
private static final int SIZE_SMALL = 2;
@@ -65,14 +67,14 @@ public final class AvatarImageView extends AppCompatImageView {
private int size;
private boolean inverted;
private OnClickListener listener;
private Recipient.FallbackPhotoProvider fallbackPhotoProvider;
private boolean blurred;
private ChatColors chatColors;
private FixedSizeTarget fixedSizeTarget;
private @Nullable RecipientContactPhoto recipientContactPhoto;
private @NonNull Drawable unknownRecipientDrawable;
private @Nullable AvatarColor fallbackPhotoColor;
private @Nullable RecipientContactPhoto recipientContactPhoto;
private @NonNull Drawable unknownRecipientDrawable;
private @NonNull FallbackAvatarProvider fallbackAvatarProvider = new DefaultFallbackAvatarProvider();
public AvatarImageView(Context context) {
super(context);
@@ -94,9 +96,11 @@ public final class AvatarImageView extends AppCompatImageView {
typedArray.recycle();
}
unknownRecipientDrawable = new ResourceContactPhoto(R.drawable.ic_profile_outline_40, R.drawable.ic_profile_outline_20).asDrawable(context, AvatarColor.UNKNOWN, inverted);
unknownRecipientDrawable = new FallbackAvatarDrawable(context, new FallbackAvatar.Resource.Person(AvatarColor.UNKNOWN));
blurred = false;
chatColors = null;
setShapeAppearanceModel(ShapeAppearanceModel.builder().setAllCorners(new RoundedCornerTreatment()).setAllCornerSizes(new RelativeCornerSize(0.5f)).build());
}
@Override
@@ -110,12 +114,8 @@ public final class AvatarImageView extends AppCompatImageView {
super.setOnClickListener(listener);
}
public void setFallbackPhotoProvider(Recipient.FallbackPhotoProvider fallbackPhotoProvider) {
this.fallbackPhotoProvider = fallbackPhotoProvider;
}
public void setFallbackPhotoColor(@Nullable AvatarColor fallbackPhotoColor) {
this.fallbackPhotoColor = fallbackPhotoColor;
public void setFallbackAvatarProvider(@Nullable FallbackAvatarProvider fallbackAvatarProvider) {
this.fallbackAvatarProvider = fallbackAvatarProvider != null ? fallbackAvatarProvider : new DefaultFallbackAvatarProvider();
}
/**
@@ -184,18 +184,21 @@ public final class AvatarImageView extends AppCompatImageView {
this.chatColors = chatColors;
recipientContactPhoto = photo;
Recipient.FallbackPhotoProvider activeFallbackPhotoProvider = this.fallbackPhotoProvider;
FallbackAvatarProvider activeFallbackPhotoProvider = this.fallbackAvatarProvider;
if (recipient.isSelf() && avatarOptions.useSelfProfileAvatar) {
activeFallbackPhotoProvider = new Recipient.FallbackPhotoProvider() {
activeFallbackPhotoProvider = new FallbackAvatarProvider() {
@Override
public @NonNull FallbackContactPhoto getPhotoForLocalNumber() {
return super.getPhotoForRecipientWithName(recipient.getDisplayName(getContext()), ViewUtil.getWidth(AvatarImageView.this));
public @NonNull FallbackAvatar getFallbackAvatar(@NonNull Recipient recipient) {
if (recipient.isSelf()) {
return new FallbackAvatar.Resource.Person(recipient.getAvatarColor());
}
return FallbackAvatarProvider.super.getFallbackAvatar(recipient);
}
};
}
Drawable fallbackContactPhotoDrawable = size == SIZE_SMALL ? photo.recipient.getSmallFallbackContactPhotoDrawable(getContext(), inverted, activeFallbackPhotoProvider, ViewUtil.getWidth(this))
: photo.recipient.getFallbackContactPhotoDrawable(getContext(), inverted, activeFallbackPhotoProvider, ViewUtil.getWidth(this));
Drawable fallback = new FallbackAvatarDrawable(getContext(), activeFallbackPhotoProvider.getFallbackAvatar(recipient));
if (fixedSizeTarget != null) {
requestManager.clear(fixedSizeTarget);
@@ -212,8 +215,8 @@ public final class AvatarImageView extends AppCompatImageView {
RequestBuilder<Drawable> request = requestManager.load(photo.contactPhoto)
.dontAnimate()
.fallback(fallbackContactPhotoDrawable)
.error(fallbackContactPhotoDrawable)
.fallback(fallback)
.error(fallback)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.downsample(DownsampleStrategy.CENTER_INSIDE)
.transform(new MultiTransformation<>(transforms))
@@ -227,7 +230,7 @@ public final class AvatarImageView extends AppCompatImageView {
}
} else {
setImageDrawable(fallbackContactPhotoDrawable);
setImageDrawable(fallback);
}
}
@@ -235,12 +238,7 @@ public final class AvatarImageView extends AppCompatImageView {
} else {
recipientContactPhoto = null;
requestManager.clear(this);
if (fallbackPhotoProvider != null) {
setImageDrawable(fallbackPhotoProvider.getPhotoForRecipientWithoutName()
.asDrawable(getContext(), Util.firstNonNull(fallbackPhotoColor, AvatarColor.UNKNOWN), inverted));
} else {
setImageDrawable(unknownRecipientDrawable);
}
setImageDrawable(unknownRecipientDrawable);
disableQuickContact();
}
@@ -267,13 +265,9 @@ public final class AvatarImageView extends AppCompatImageView {
}
}
public void setImageBytesForGroup(@Nullable byte[] avatarBytes,
@Nullable Recipient.FallbackPhotoProvider fallbackPhotoProvider,
@NonNull AvatarColor color)
public void setImageBytesForGroup(@Nullable byte[] avatarBytes, @NonNull AvatarColor color)
{
Drawable fallback = Util.firstNonNull(fallbackPhotoProvider, Recipient.DEFAULT_FALLBACK_PHOTO_PROVIDER)
.getPhotoForGroup()
.asDrawable(getContext(), color);
Drawable fallback = new FallbackAvatarDrawable(getContext(), new FallbackAvatar.Resource.Group(color));
Glide.with(this)
.load(avatarBytes)
@@ -295,6 +289,14 @@ public final class AvatarImageView extends AppCompatImageView {
setClickable(listener != null);
}
public interface FallbackAvatarProvider {
default @NonNull FallbackAvatar getFallbackAvatar(@NonNull Recipient recipient) {
return recipient.getFallbackAvatar();
}
}
private static class DefaultFallbackAvatarProvider implements FallbackAvatarProvider {}
private static class RecipientContactPhoto {
private final @NonNull Recipient recipient;

View File

@@ -20,13 +20,14 @@ import com.bumptech.glide.RequestManager;
import org.signal.ringrtc.CallLinkRootKey;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatar;
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatarDrawable;
import org.thoughtcrime.securesms.calls.links.CallLinks;
import org.thoughtcrime.securesms.conversation.colors.AvatarColorHash;
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
import org.thoughtcrime.securesms.mms.ImageSlide;
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.views.Stub;
@@ -224,10 +225,10 @@ public class LinkPreviewView extends FrameLayout {
thumbnailState.applyState(thumbnail);
thumbnail.get().setImageDrawable(
requestManager,
Recipient.DEFAULT_FALLBACK_PHOTO_PROVIDER
.getPhotoForCallLink()
.asDrawable(getContext(),
AvatarColorHash.forCallLink(callLinkRootKey.getKeyBytes()))
new FallbackAvatarDrawable(
getContext(),
new FallbackAvatar.Resource.CallLink(AvatarColorHash.forCallLink(callLinkRootKey.getKeyBytes()))
)
);
thumbnail.get().showSecondaryText(false);
} else {

View File

@@ -7,11 +7,8 @@ import org.thoughtcrime.securesms.avatar.view.AvatarView
import org.thoughtcrime.securesms.badges.BadgeImageView
import org.thoughtcrime.securesms.badges.models.Badge
import org.thoughtcrime.securesms.components.settings.PreferenceModel
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto
import org.thoughtcrime.securesms.contacts.avatars.FallbackPhoto
import org.thoughtcrime.securesms.database.model.StoryViewState
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder
@@ -43,9 +40,7 @@ object AvatarPreference {
}
private class ViewHolder(itemView: View) : MappingViewHolder<Model>(itemView) {
private val avatar: AvatarView = itemView.findViewById<AvatarView>(R.id.bio_preference_avatar).apply {
setFallbackPhotoProvider(AvatarPreferenceFallbackPhotoProvider())
}
private val avatar: AvatarView = itemView.findViewById<AvatarView>(R.id.bio_preference_avatar)
private val badge: BadgeImageView = itemView.findViewById(R.id.bio_preference_badge)
@@ -73,9 +68,4 @@ object AvatarPreference {
avatar.setOnClickListener { model.onAvatarClick(avatar) }
}
}
private class AvatarPreferenceFallbackPhotoProvider : Recipient.FallbackPhotoProvider() {
override val photoForGroup: FallbackContactPhoto
get() = FallbackPhoto(R.drawable.ic_group_outline_40, ViewUtil.dpToPx(8))
}
}

View File

@@ -25,13 +25,12 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.signal.core.util.ThreadUtil;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatarDrawable;
import org.thoughtcrime.securesms.badges.BadgeImageView;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.conversation.colors.ChatColors;
import org.thoughtcrime.securesms.events.CallParticipant;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -50,8 +49,6 @@ import java.util.concurrent.TimeUnit;
*/
public class CallParticipantView extends ConstraintLayout {
private static final FallbackPhotoProvider FALLBACK_PHOTO_PROVIDER = new FallbackPhotoProvider();
private static final long DELAY_SHOWING_MISSING_MEDIA_KEYS = TimeUnit.SECONDS.toMillis(5);
private static final int SMALL_AVATAR = ViewUtil.dpToPx(96);
private static final int LARGE_AVATAR = ViewUtil.dpToPx(112);
@@ -116,7 +113,6 @@ public class CallParticipantView extends ConstraintLayout {
raiseHandIcon = findViewById(R.id.call_participant_raise_hand_icon);
nameLabel = findViewById(R.id.call_participant_name_label);
avatar.setFallbackPhotoProvider(FALLBACK_PHOTO_PROVIDER);
useLargeAvatar();
}
@@ -422,12 +418,13 @@ public class CallParticipantView extends ConstraintLayout {
private void setPipAvatar(@NonNull Recipient recipient) {
ContactPhoto contactPhoto = recipient.isSelf() ? new ProfileContactPhoto(Recipient.self())
: recipient.getContactPhoto();
FallbackContactPhoto fallbackPhoto = recipient.getFallbackContactPhoto(FALLBACK_PHOTO_PROVIDER);
FallbackAvatarDrawable fallbackAvatarDrawable = new FallbackAvatarDrawable(getContext(), recipient.getFallbackAvatar());
Glide.with(this)
.load(contactPhoto)
.fallback(fallbackPhoto.asCallCard(getContext()))
.error(fallbackPhoto.asCallCard(getContext()))
.fallback(fallbackAvatarDrawable)
.error(fallbackAvatarDrawable)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.fitCenter()
.into(pipAvatar);
@@ -455,20 +452,6 @@ public class CallParticipantView extends ConstraintLayout {
.show();
}
private static final class FallbackPhotoProvider extends Recipient.FallbackPhotoProvider {
@Override
public @NonNull FallbackContactPhoto getPhotoForLocalNumber() {
return super.getPhotoForRecipientWithoutName();
}
@Override
public @NonNull FallbackContactPhoto getPhotoForRecipientWithoutName() {
ResourceContactPhoto photo = new ResourceContactPhoto(R.drawable.ic_profile_outline_120);
photo.setScaleType(ImageView.ScaleType.CENTER_CROP);
return photo;
}
}
public enum SelfPipMode {
NOT_SELF_PIP,
NORMAL_SELF_PIP,

View File

@@ -16,7 +16,13 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import com.google.android.material.chip.Chip;
import com.google.android.material.shape.MaterialShapeDrawable;
import com.google.android.material.shape.RelativeCornerSize;
import com.google.android.material.shape.RoundedCornerTreatment;
import com.google.android.material.shape.ShapeAppearanceModel;
import com.google.android.material.shape.Shapeable;
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatarDrawable;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -48,10 +54,16 @@ public final class ContactChip extends Chip {
if (recipient != null) {
requestManager.clear(this);
Drawable fallbackContactPhotoDrawable = new HalfScaleDrawable(recipient.getFallbackContactPhotoDrawable(getContext(), false));
ContactPhoto contactPhoto = recipient.getContactPhoto();
FallbackAvatarDrawable fallbackContactPhotoDrawable = new FallbackAvatarDrawable(getContext(), recipient.getFallbackAvatar());
ContactPhoto contactPhoto = recipient.getContactPhoto();
if (contactPhoto == null) {
fallbackContactPhotoDrawable.setShapeAppearanceModel(
ShapeAppearanceModel.builder()
.setAllCorners(new RoundedCornerTreatment())
.setAllCornerSizes(new RelativeCornerSize(0.5f)).build()
);
setChipIcon(fallbackContactPhotoDrawable);
if (onAvatarSet != null) {
onAvatarSet.run();

View File

@@ -1,17 +0,0 @@
package org.thoughtcrime.securesms.contacts.avatars;
import android.content.Context;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
public interface FallbackContactPhoto {
Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color);
Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted);
Drawable asSmallDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted);
Drawable asCallCard(@NonNull Context context);
}

View File

@@ -1,64 +0,0 @@
package org.thoughtcrime.securesms.contacts.avatars;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Px;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.graphics.drawable.DrawableCompat;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.avatar.Avatars;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import java.util.Objects;
/**
* Fallback resource based contact photo with a 20dp icon
*/
public final class FallbackPhoto implements FallbackContactPhoto {
@DrawableRes private final int drawableResource;
@Px private final int foregroundInset;
public FallbackPhoto(@DrawableRes int drawableResource, @Px int foregroundInset) {
this.drawableResource = drawableResource;
this.foregroundInset = foregroundInset;
}
@Override
public Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color) {
return buildDrawable(context, color);
}
@Override
public Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return buildDrawable(context, color);
}
@Override
public Drawable asSmallDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return buildDrawable(context, color);
}
@Override
public Drawable asCallCard(@NonNull Context context) {
throw new UnsupportedOperationException();
}
private @NonNull Drawable buildDrawable(@NonNull Context context, @NonNull AvatarColor color) {
Drawable background = DrawableCompat.wrap(Objects.requireNonNull(AppCompatResources.getDrawable(context, R.drawable.circle_tintable))).mutate();
Drawable foreground = Objects.requireNonNull(AppCompatResources.getDrawable(context, drawableResource));
LayerDrawable drawable = new LayerDrawable(new Drawable[]{background, foreground});
DrawableCompat.setTint(background, color.colorInt());
DrawableCompat.setTint(foreground, Avatars.getForegroundColor(color).getColorInt());
drawable.setLayerInset(1, foregroundInset, foregroundInset, foregroundInset, foregroundInset);
return drawable;
}
}

View File

@@ -1,63 +0,0 @@
package org.thoughtcrime.securesms.contacts.avatars;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.graphics.drawable.DrawableCompat;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.avatar.Avatars;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.Objects;
/**
* Fallback resource based contact photo with a 20dp icon
*/
public final class FallbackPhoto20dp implements FallbackContactPhoto {
@DrawableRes private final int drawable20dp;
public FallbackPhoto20dp(@DrawableRes int drawable20dp) {
this.drawable20dp = drawable20dp;
}
@Override
public Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color) {
return buildDrawable(context, color);
}
@Override
public Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return buildDrawable(context, color);
}
@Override
public Drawable asSmallDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return buildDrawable(context, color);
}
@Override
public Drawable asCallCard(@NonNull Context context) {
throw new UnsupportedOperationException();
}
private @NonNull Drawable buildDrawable(@NonNull Context context, @NonNull AvatarColor color) {
Drawable background = DrawableCompat.wrap(Objects.requireNonNull(AppCompatResources.getDrawable(context, R.drawable.circle_tintable))).mutate();
Drawable foreground = Objects.requireNonNull(AppCompatResources.getDrawable(context, drawable20dp));
LayerDrawable drawable = new LayerDrawable(new Drawable[]{background, foreground});
int foregroundInset = ViewUtil.dpToPx(2);
DrawableCompat.setTint(background, color.colorInt());
DrawableCompat.setTint(foreground, Avatars.getForegroundColor(color).getColorInt());
drawable.setLayerInset(1, foregroundInset, foregroundInset, foregroundInset, foregroundInset);
return drawable;
}
}

View File

@@ -1,71 +0,0 @@
package org.thoughtcrime.securesms.contacts.avatars;
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.graphics.drawable.DrawableCompat;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.avatar.Avatars;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.Objects;
public final class FallbackPhoto80dp implements FallbackContactPhoto {
@DrawableRes private final int drawable80dp;
private final AvatarColor color;
public FallbackPhoto80dp(@DrawableRes int drawable80dp, @NonNull AvatarColor color) {
this.drawable80dp = drawable80dp;
this.color = color;
}
@Override
public Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color) {
return buildDrawable(context);
}
@Override
public Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return buildDrawable(context);
}
@Override
public Drawable asSmallDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
throw new UnsupportedOperationException();
}
@Override
public Drawable asCallCard(@NonNull Context context) {
Drawable background = new ColorDrawable(color.colorInt());
Drawable foreground = Objects.requireNonNull(AppCompatResources.getDrawable(context, drawable80dp));
LayerDrawable drawable = new LayerDrawable(new Drawable[]{background, foreground});
int foregroundInset = ViewUtil.dpToPx(24);
DrawableCompat.setTint(foreground, Avatars.getForegroundColor(color).getColorInt());
drawable.setLayerInset(1, foregroundInset, foregroundInset, foregroundInset, foregroundInset);
return drawable;
}
private @NonNull Drawable buildDrawable(@NonNull Context context) {
Drawable background = DrawableCompat.wrap(Objects.requireNonNull(AppCompatResources.getDrawable(context, R.drawable.circle_tintable))).mutate();
Drawable foreground = Objects.requireNonNull(AppCompatResources.getDrawable(context, drawable80dp));
LayerDrawable drawable = new LayerDrawable(new Drawable[]{background, foreground});
int foregroundInset = ViewUtil.dpToPx(24);
DrawableCompat.setTint(background, color.colorInt());
DrawableCompat.setTint(foreground, Avatars.getForegroundColor(color).getColorInt());
drawable.setLayerInset(1, foregroundInset, foregroundInset, foregroundInset, foregroundInset);
return drawable;
}
}

View File

@@ -1,85 +0,0 @@
package org.thoughtcrime.securesms.contacts.avatars;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.text.TextUtils;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.content.ContextCompat;
import com.airbnb.lottie.SimpleColorFilter;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.avatar.Avatar;
import org.thoughtcrime.securesms.avatar.AvatarRenderer;
import org.thoughtcrime.securesms.avatar.Avatars;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.util.NameUtil;
import java.util.Objects;
public class GeneratedContactPhoto implements FallbackContactPhoto {
private final String name;
private final int fallbackResId;
private final int targetSize;
public GeneratedContactPhoto(@NonNull String name, @DrawableRes int fallbackResId) {
this(name, fallbackResId, -1);
}
public GeneratedContactPhoto(@NonNull String name, @DrawableRes int fallbackResId, int targetSize) {
this.name = name;
this.fallbackResId = fallbackResId;
this.targetSize = targetSize;
}
@Override
public Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color) {
return asDrawable(context, color,false);
}
@Override
public Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
int targetSize = this.targetSize > 0
? this.targetSize
: context.getResources().getDimensionPixelSize(R.dimen.contact_photo_target_size);
String character = NameUtil.getAbbreviation(name);
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, false);
Drawable background = Objects.requireNonNull(ContextCompat.getDrawable(context, R.drawable.circle_tintable));
background.setColorFilter(new SimpleColorFilter(inverted ? foregroundColor.getColorInt() : color.colorInt()));
return new LayerDrawable(new Drawable[] { background, foreground });
}
return newFallbackDrawable(context, color, inverted);
}
@Override
public Drawable asSmallDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return asDrawable(context, color, inverted);
}
protected @DrawableRes int getFallbackResId() {
return fallbackResId;
}
protected Drawable newFallbackDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return new ResourceContactPhoto(fallbackResId).asDrawable(context, color, inverted);
}
@Override
public Drawable asCallCard(@NonNull Context context) {
return AppCompatResources.getDrawable(context, R.drawable.ic_person_large);
}
}

View File

@@ -1,103 +0,0 @@
package org.thoughtcrime.securesms.contacts.avatars;
import android.content.Context;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.widget.ImageView;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.content.ContextCompat;
import com.makeramen.roundedimageview.RoundedDrawable;
import org.jetbrains.annotations.NotNull;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.conversation.colors.AvatarColorPair;
import java.util.Objects;
public class ResourceContactPhoto implements FallbackContactPhoto {
private final int resourceId;
private final int smallResourceId;
private final int callCardResourceId;
private ImageView.ScaleType scaleType = ImageView.ScaleType.CENTER;
public ResourceContactPhoto(@DrawableRes int resourceId) {
this(resourceId, resourceId, resourceId);
}
public ResourceContactPhoto(@DrawableRes int resourceId, @DrawableRes int smallResourceId) {
this(resourceId, smallResourceId, resourceId);
}
public ResourceContactPhoto(@DrawableRes int resourceId, @DrawableRes int smallResourceId, @DrawableRes int callCardResourceId) {
this.resourceId = resourceId;
this.callCardResourceId = callCardResourceId;
this.smallResourceId = smallResourceId;
}
public void setScaleType(@NonNull ImageView.ScaleType scaleType) {
this.scaleType = scaleType;
}
@Override
public @NonNull Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color) {
return asDrawable(context, color, false);
}
@Override
public @NonNull Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return buildDrawable(context, resourceId, color, inverted);
}
@Override
public @NonNull Drawable asSmallDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return buildDrawable(context, smallResourceId, color, inverted);
}
private @NonNull Drawable buildDrawable(@NonNull Context context, int resourceId, @NonNull AvatarColor color, boolean inverted) {
AvatarColorPair avatarColorPair = AvatarColorPair.create(context, color);
final int backgroundColor = avatarColorPair.getBackgroundColor();
final int foregroundColor = avatarColorPair.getForegroundColor();
Drawable background = Objects.requireNonNull(ContextCompat.getDrawable(context, R.drawable.circle_tintable));
RoundedDrawable foreground = (RoundedDrawable) RoundedDrawable.fromDrawable(AppCompatResources.getDrawable(context, resourceId));
//noinspection ConstantConditions
foreground.setScaleType(scaleType);
background.setColorFilter(inverted ? foregroundColor : backgroundColor, PorterDuff.Mode.SRC_IN);
foreground.setColorFilter(inverted ? backgroundColor : foregroundColor, PorterDuff.Mode.SRC_ATOP);
return new ExpandingLayerDrawable(new Drawable[] {background, foreground});
}
@Override
public @Nullable Drawable asCallCard(@NotNull @NonNull Context context) {
return AppCompatResources.getDrawable(context, callCardResourceId);
}
private static class ExpandingLayerDrawable extends LayerDrawable {
public ExpandingLayerDrawable(@NonNull Drawable[] layers) {
super(layers);
}
@Override
public int getIntrinsicWidth() {
return -1;
}
@Override
public int getIntrinsicHeight() {
return -1;
}
}
}

View File

@@ -1,38 +0,0 @@
package org.thoughtcrime.securesms.contacts.avatars;
import android.content.Context;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import com.makeramen.roundedimageview.RoundedDrawable;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
public class TransparentContactPhoto implements FallbackContactPhoto {
public TransparentContactPhoto() {}
@Override
public Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color) {
return asDrawable(context, color, false);
}
@Override
public Drawable asDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return RoundedDrawable.fromDrawable(context.getResources().getDrawable(android.R.color.transparent));
}
@Override
public Drawable asSmallDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return asDrawable(context, color, inverted);
}
@Override
public Drawable asCallCard(@NonNull Context context) {
return ContextCompat.getDrawable(context, R.drawable.symbol_person_display_40);
}
}

View File

@@ -13,8 +13,8 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.disposables.Disposable
import org.signal.core.util.BreakIteratorCompat
import org.signal.core.util.dp
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatar
import org.thoughtcrime.securesms.avatar.view.AvatarView
import org.thoughtcrime.securesms.badges.BadgeImageView
import org.thoughtcrime.securesms.components.AvatarImageView
@@ -24,8 +24,6 @@ import org.thoughtcrime.securesms.components.emoji.EmojiUtil
import org.thoughtcrime.securesms.components.menu.ActionItem
import org.thoughtcrime.securesms.components.menu.SignalContextMenu
import org.thoughtcrime.securesms.contacts.LetterHeaderDecoration
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto
import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode
import org.thoughtcrime.securesms.database.model.StoryViewState
import org.thoughtcrime.securesms.keyvalue.SignalStore
@@ -243,10 +241,10 @@ open class ContactSearchAdapter(
fun bindAvatar(model: StoryModel) {
if (model.story.recipient.isMyStory) {
avatar.setFallbackPhotoProvider(MyStoryFallbackPhotoProvider(Recipient.self().getDisplayName(context), 40.dp))
avatar.setFallbackAvatarProvider(MyStoryFallbackAvatarProvider)
avatar.displayProfileAvatar(Recipient.self())
} else {
avatar.setFallbackPhotoProvider(Recipient.DEFAULT_FALLBACK_PHOTO_PROVIDER)
avatar.setFallbackAvatarProvider(null)
avatar.displayChatAvatar(getRecipient(model))
}
groupStoryIndicator.visible = showStoryRing && model.story.recipient.isGroup
@@ -308,9 +306,14 @@ open class ContactSearchAdapter(
}
}
private class MyStoryFallbackPhotoProvider(private val name: String, private val targetSize: Int) : Recipient.FallbackPhotoProvider() {
override val photoForLocalNumber: FallbackContactPhoto
get() = GeneratedContactPhoto(name, R.drawable.symbol_person_40, targetSize)
private object MyStoryFallbackAvatarProvider : AvatarImageView.FallbackAvatarProvider {
override fun getFallbackAvatar(recipient: Recipient): FallbackAvatar {
if (recipient.isSelf) {
return FallbackAvatar.Resource.Person(recipient.avatarColor)
}
return super.getFallbackAvatar(recipient)
}
}
override fun onAttachedToWindow() {

View File

@@ -21,8 +21,6 @@ import org.signal.core.util.DimensionUnit;
import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.databinding.ConversationHeaderViewBinding;
import org.thoughtcrime.securesms.fonts.SignalSymbols;
@@ -50,8 +48,6 @@ public class ConversationHeaderView extends ConstraintLayout {
inflate(getContext(), R.layout.conversation_header_view, this);
binding = ConversationHeaderViewBinding.bind(this);
binding.messageRequestAvatar.setFallbackPhotoProvider(new FallbackPhotoProvider());
}
public void setBadge(@Nullable Recipient recipient) {
@@ -197,21 +193,4 @@ public class ConversationHeaderView extends ConstraintLayout {
.append(SpanUtil.space(8, DimensionUnit.SP))
.append(input);
}
private static final class FallbackPhotoProvider extends Recipient.FallbackPhotoProvider {
@Override
public @NonNull FallbackContactPhoto getPhotoForRecipientWithoutName() {
return new ResourceContactPhoto(R.drawable.ic_profile_64);
}
@Override
public @NonNull FallbackContactPhoto getPhotoForGroup() {
return new ResourceContactPhoto(R.drawable.ic_group_64);
}
@Override
public @NonNull FallbackContactPhoto getPhotoForLocalNumber() {
return new ResourceContactPhoto(R.drawable.ic_note_64);
}
}
}

View File

@@ -13,6 +13,7 @@ import android.os.Build
import android.text.SpannableStringBuilder
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.graphics.drawable.IconCompat
import androidx.core.graphics.drawable.toBitmap
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestManager
import com.bumptech.glide.request.target.CustomTarget
@@ -35,6 +36,7 @@ import org.signal.paging.PagingConfig
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.ShortcutLauncherActivity
import org.thoughtcrime.securesms.attachments.TombstoneAttachment
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatarDrawable
import org.thoughtcrime.securesms.components.emoji.EmojiStrings
import org.thoughtcrime.securesms.components.reminder.BubbleOptOutReminder
import org.thoughtcrime.securesms.components.reminder.ExpiredBuildReminder
@@ -88,7 +90,6 @@ import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.sms.MessageSender
import org.thoughtcrime.securesms.sms.MessageSender.PreUploadResult
import org.thoughtcrime.securesms.util.BitmapUtil
import org.thoughtcrime.securesms.util.DrawableUtil
import org.thoughtcrime.securesms.util.MediaUtil
import org.thoughtcrime.securesms.util.MessageUtil
@@ -489,7 +490,7 @@ class ConversationRepository(
}
fun getRecipientContactPhotoBitmap(context: Context, requestManager: RequestManager, recipient: Recipient): Single<ShortcutInfoCompat> {
val fallback = recipient.fallbackContactPhoto.asDrawable(context, recipient.avatarColor, false)
val fallback = FallbackAvatarDrawable(context, recipient.getFallbackAvatar())
return Single
.create { emitter ->
@@ -618,7 +619,7 @@ class ConversationRepository(
* The result of the Glide load to get a user's contact photo. This can then be transformed into
* something that the Android system likes via [transformToFinalBitmap]
*/
sealed interface ContactPhotoResult {
private sealed interface ContactPhotoResult {
companion object {
private val SHORTCUT_ICON_SIZE = if (Build.VERSION.SDK_INT >= 26) 72.dp else (48 + 16 * 2).dp
@@ -627,7 +628,7 @@ class ConversationRepository(
class DrawableResult(private val drawable: Drawable) : ContactPhotoResult {
override fun transformToFinalBitmap(): Single<Bitmap> {
return Single.create {
val bitmap = DrawableUtil.toBitmap(drawable, SHORTCUT_ICON_SIZE, SHORTCUT_ICON_SIZE)
val bitmap = DrawableUtil.wrapBitmapForShortcutInfo(drawable.toBitmap(SHORTCUT_ICON_SIZE, SHORTCUT_ICON_SIZE))
it.setCancellable {
bitmap.recycle()
}
@@ -639,7 +640,7 @@ class ConversationRepository(
class BitmapResult(private val bitmap: Bitmap) : ContactPhotoResult {
override fun transformToFinalBitmap(): Single<Bitmap> {
return Single.create {
val bitmap = BitmapUtil.createScaledBitmap(bitmap, SHORTCUT_ICON_SIZE, SHORTCUT_ICON_SIZE)
val bitmap = DrawableUtil.wrapBitmapForShortcutInfo(bitmap)
it.setCancellable {
bitmap.recycle()
}

View File

@@ -23,14 +23,11 @@ import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.conversation.ConversationIntents;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.groups.ui.managegroup.dialogs.GroupDescriptionDialog;
import org.thoughtcrime.securesms.groups.v2.GroupDescriptionUtil;
import org.thoughtcrime.securesms.groups.v2.GroupInviteLinkUrl;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.BottomSheetUtil;
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
import org.thoughtcrime.securesms.util.ThemeUtil;
@@ -87,7 +84,7 @@ public final class GroupJoinBottomSheetDialogFragment extends BottomSheetDialogF
groupCancelButton.setOnClickListener(v -> dismiss());
avatar.setImageBytesForGroup(null, new FallbackPhotoProvider(), AvatarColor.UNKNOWN);
avatar.setImageBytesForGroup(null, AvatarColor.UNKNOWN);
return view;
}
@@ -118,7 +115,7 @@ public final class GroupJoinBottomSheetDialogFragment extends BottomSheetDialogF
});
groupJoinButton.setVisibility(View.VISIBLE);
avatar.setImageBytesForGroup(details.getAvatarBytes(), new FallbackPhotoProvider(), AvatarColor.UNKNOWN);
avatar.setImageBytesForGroup(details.getAvatarBytes(), AvatarColor.UNKNOWN);
groupCancelButton.setVisibility(View.VISIBLE);
});
@@ -204,10 +201,4 @@ public final class GroupJoinBottomSheetDialogFragment extends BottomSheetDialogF
BottomSheetUtil.show(manager, tag, this);
}
private static final class FallbackPhotoProvider extends Recipient.FallbackPhotoProvider {
@Override
public @NonNull FallbackContactPhoto getPhotoForGroup() {
return new ResourceContactPhoto(R.drawable.ic_group_outline_48);
}
}
}

View File

@@ -19,9 +19,6 @@ import com.bumptech.glide.Glide;
import org.thoughtcrime.securesms.BaseActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import java.util.concurrent.TimeUnit;
@@ -62,7 +59,6 @@ public class CalleeMustAcceptMessageRequestActivity extends BaseActivity {
avatar = findViewById(R.id.avatar);
okay = findViewById(R.id.okay);
avatar.setFallbackPhotoProvider(new FallbackPhotoProvider());
okay.setOnClickListener(v -> finish());
RecipientId recipientId = getIntent().getParcelableExtra(RECIPIENT_ID_EXTRA);
@@ -88,11 +84,4 @@ public class CalleeMustAcceptMessageRequestActivity extends BaseActivity {
handler.removeCallbacks(finisher);
}
private static class FallbackPhotoProvider extends Recipient.FallbackPhotoProvider {
@Override
public @NonNull FallbackContactPhoto getPhotoForRecipientWithoutName() {
return new ResourceContactPhoto(R.drawable.ic_profile_80);
}
}
}

View File

@@ -10,8 +10,9 @@ import androidx.core.app.TaskStackBuilder
import org.signal.core.util.PendingIntentFlags
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatar
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatarDrawable
import org.thoughtcrime.securesms.contacts.TurnOffContactJoinedNotificationsActivity
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto
import org.thoughtcrime.securesms.conversation.ConversationIntents
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.keyvalue.SignalStore
@@ -27,7 +28,6 @@ import org.thoughtcrime.securesms.service.KeyCachingService
import org.thoughtcrime.securesms.stories.StoryViewerArgs
import org.thoughtcrime.securesms.stories.viewer.StoryViewerActivity
import org.thoughtcrime.securesms.util.Util
import java.lang.NullPointerException
/**
* Encapsulate all the notifications for a given conversation (thread) and the top
@@ -57,7 +57,7 @@ data class NotificationConversation(
return if (SignalStore.settings().messageNotificationsPrivacy.isDisplayContact) {
recipient.getContactDrawable(context)
} else {
GeneratedContactPhoto("Unknown", R.drawable.ic_profile_outline_40).asDrawable(context, AvatarColor.UNKNOWN)
FallbackAvatarDrawable(context, FallbackAvatar.forTextOrDefault("Unknown", AvatarColor.UNKNOWN)).circleCrop()
}
}

View File

@@ -13,9 +13,9 @@ import com.bumptech.glide.load.Transformation
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.CircleCrop
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatar
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatarDrawable
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri
@@ -37,7 +37,7 @@ fun Drawable?.toLargeBitmap(context: Context): Bitmap? {
fun Recipient.getContactDrawable(context: Context): Drawable? {
val contactPhoto: ContactPhoto? = if (isSelf) ProfileContactPhoto(this) else contactPhoto
val fallbackContactPhoto: FallbackContactPhoto = if (isSelf) getFallback(context) else fallbackContactPhoto
val fallbackAvatar: FallbackAvatar = if (isSelf) getFallback(context) else getFallbackAvatar()
return if (contactPhoto != null) {
try {
val transforms: MutableList<Transformation<Bitmap>> = mutableListOf()
@@ -56,12 +56,12 @@ fun Recipient.getContactDrawable(context: Context): Drawable? {
)
.get()
} catch (e: InterruptedException) {
fallbackContactPhoto.asDrawable(context, avatarColor)
FallbackAvatarDrawable(context, fallbackAvatar).circleCrop()
} catch (e: ExecutionException) {
fallbackContactPhoto.asDrawable(context, avatarColor)
FallbackAvatarDrawable(context, fallbackAvatar).circleCrop()
}
} else {
fallbackContactPhoto.asDrawable(context, avatarColor)
FallbackAvatarDrawable(context, fallbackAvatar).circleCrop()
}
}
@@ -84,8 +84,8 @@ fun Intent.makeUniqueToPreventMerging(): Intent {
return setData((Uri.parse("custom://" + System.currentTimeMillis())))
}
fun Recipient.getFallback(context: Context): FallbackContactPhoto {
return GeneratedContactPhoto(getDisplayName(context), R.drawable.ic_profile_outline_40)
fun Recipient.getFallback(context: Context): FallbackAvatar {
return FallbackAvatar.forTextOrDefault(getDisplayName(context), avatarColor)
}
fun NotificationManager.isDisplayingSummaryNotification(): Boolean {

View File

@@ -19,8 +19,9 @@ import org.signal.core.util.concurrent.SignalExecutors
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.MainActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatar
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatarDrawable
import org.thoughtcrime.securesms.components.emoji.EmojiStrings
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto
import org.thoughtcrime.securesms.conversation.ConversationIntents
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.database.SignalDatabase
@@ -390,7 +391,7 @@ object NotificationFactory {
recipient.getContactDrawable(context)
}
} else {
GeneratedContactPhoto("Unknown", R.drawable.ic_profile_outline_40).asDrawable(context, AvatarColor.UNKNOWN)
FallbackAvatarDrawable(context, FallbackAvatar.forTextOrDefault("Unknown", AvatarColor.UNKNOWN)).circleCrop()
}.toLargeBitmap(context)
val builder: NotificationBuilder = NotificationBuilder.create(context)

View File

@@ -8,12 +8,8 @@ import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.FallbackPhoto20dp;
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatar;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.databinding.ReviewBannerViewBinding;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -39,10 +35,10 @@ public class ReviewBannerView extends FrameLayout {
binding = ReviewBannerViewBinding.bind(this);
FallbackPhotoProvider provider = new FallbackPhotoProvider();
FallbackAvatarProvider provider = new FallbackAvatarProvider();
binding.bannerBottomRightAvatar.setFallbackPhotoProvider(provider);
binding.bannerTopLeftAvatar.setFallbackPhotoProvider(provider);
binding.bannerBottomRightAvatar.setFallbackAvatarProvider(provider);
binding.bannerTopLeftAvatar.setFallbackAvatarProvider(provider);
binding.bannerClose.setOnClickListener(v -> {
if (onHideListener != null && onHideListener.onHide()) {
@@ -86,44 +82,14 @@ public class ReviewBannerView extends FrameLayout {
binding.bannerTapToReview.setOnClickListener(l);
}
private static final class FallbackPhotoProvider extends Recipient.FallbackPhotoProvider {
private static final class FallbackAvatarProvider implements AvatarImageView.FallbackAvatarProvider {
@Override
public @NonNull
FallbackContactPhoto getPhotoForGroup() {
throw new UnsupportedOperationException("This provider does not support groups");
}
public @NonNull FallbackAvatar getFallbackAvatar(@NonNull Recipient recipient) {
if (recipient.isIndividual() && !recipient.isSelf()) {
return new FallbackAvatar.Resource.Person(recipient.getAvatarColor());
}
@Override
public @NonNull FallbackContactPhoto getPhotoForResolvingRecipient() {
throw new UnsupportedOperationException("This provider does not support resolving recipients");
}
@Override
public @NonNull FallbackContactPhoto getPhotoForLocalNumber() {
return new ResourceContactPhoto(R.drawable.symbol_note_light_24, R.drawable.symbol_note_light_24);
}
@NonNull
@Override
public FallbackContactPhoto getPhotoForRecipientWithName(String name, int targetSize) {
return new FixedSizeGeneratedContactPhoto(name, R.drawable.ic_profile_outline_20);
}
@NonNull
@Override
public FallbackContactPhoto getPhotoForRecipientWithoutName() {
return new FallbackPhoto20dp(R.drawable.ic_profile_outline_20);
}
}
private static final class FixedSizeGeneratedContactPhoto extends GeneratedContactPhoto {
public FixedSizeGeneratedContactPhoto(@NonNull String name, int fallbackResId) {
super(name, fallbackResId);
}
@Override
protected Drawable newFallbackDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return new FallbackPhoto20dp(getFallbackResId()).asDrawable(context, color, inverted);
return recipient.getFallbackAvatar();
}
}

View File

@@ -21,8 +21,8 @@ import org.thoughtcrime.securesms.profiles.AvatarHelper
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.service.KeyCachingService
import org.thoughtcrime.securesms.util.AdaptiveBitmapMetrics
import org.thoughtcrime.securesms.util.AvatarUtil
import org.thoughtcrime.securesms.util.DrawableUtil
import org.thoughtcrime.securesms.util.MediaUtil
import java.io.File
import java.io.FileNotFoundException
@@ -168,7 +168,7 @@ class AvatarProvider : BaseContentProvider() {
ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]).use { output ->
if (VERBOSE) Log.i(TAG, "Writing to pipe:${recipient.id}")
AvatarUtil.getBitmapForNotification(context!!, recipient, DrawableUtil.SHORTCUT_INFO_WRAPPED_SIZE).apply {
AvatarUtil.getBitmapForNotification(context!!, recipient, AdaptiveBitmapMetrics.innerWidth).apply {
compress(Bitmap.CompressFormat.PNG, 100, output)
}
output.flush()

View File

@@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.recipients
import android.content.Context
import android.graphics.drawable.Drawable
import android.net.Uri
import androidx.annotation.AnyThread
import androidx.annotation.WorkerThread
@@ -14,15 +13,12 @@ import org.signal.core.util.logging.Log
import org.signal.core.util.nullIfBlank
import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatar
import org.thoughtcrime.securesms.badges.models.Badge
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto
import org.thoughtcrime.securesms.contacts.avatars.GroupRecordContactPhoto
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto
import org.thoughtcrime.securesms.contacts.avatars.SystemContactPhoto
import org.thoughtcrime.securesms.contacts.avatars.TransparentContactPhoto
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.conversation.colors.ChatColors
import org.thoughtcrime.securesms.conversation.colors.ChatColors.Id.Auto
@@ -47,7 +43,6 @@ import org.thoughtcrime.securesms.phonenumbers.NumberUtil
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
import org.thoughtcrime.securesms.util.AvatarUtil
import org.thoughtcrime.securesms.util.UsernameUtil.isValidUsernameForSearch
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper
@@ -258,10 +253,6 @@ class Recipient(
null
}
/** A photo you can use as a fallback if [contactPhoto] fails to load. */
val fallbackContactPhoto: FallbackContactPhoto
get() = getFallbackContactPhoto(DEFAULT_FALLBACK_PHOTO_PROVIDER)
/** The URI of the ringtone that should be used when receiving a message from this recipient, if set. */
val messageRingtone: Uri? by lazy {
if (messageRingtoneUri != null && messageRingtoneUri.scheme != null && messageRingtoneUri.scheme!!.startsWith("file")) {
@@ -609,45 +600,29 @@ class Recipient(
}
}
fun getFallbackContactPhotoDrawable(context: Context?, inverted: Boolean): Drawable {
return getFallbackContactPhotoDrawable(context, inverted, DEFAULT_FALLBACK_PHOTO_PROVIDER, AvatarUtil.UNDEFINED_SIZE)
}
fun getFallbackContactPhotoDrawable(context: Context?, inverted: Boolean, fallbackPhotoProvider: FallbackPhotoProvider?, targetSize: Int): Drawable {
return getFallbackContactPhoto(Util.firstNonNull(fallbackPhotoProvider, DEFAULT_FALLBACK_PHOTO_PROVIDER), targetSize).asDrawable(context!!, avatarColor, inverted)
}
fun getSmallFallbackContactPhotoDrawable(context: Context?, inverted: Boolean, fallbackPhotoProvider: FallbackPhotoProvider?, targetSize: Int): Drawable {
return getFallbackContactPhoto(Util.firstNonNull(fallbackPhotoProvider, DEFAULT_FALLBACK_PHOTO_PROVIDER), targetSize).asSmallDrawable(context!!, avatarColor, inverted)
}
fun getFallbackContactPhoto(fallbackPhotoProvider: FallbackPhotoProvider): FallbackContactPhoto {
return getFallbackContactPhoto(fallbackPhotoProvider, AvatarUtil.UNDEFINED_SIZE)
}
private fun getFallbackContactPhoto(fallbackPhotoProvider: FallbackPhotoProvider, targetSize: Int): FallbackContactPhoto {
fun getFallbackAvatar(): FallbackAvatar {
return if (isSelf) {
fallbackPhotoProvider.photoForLocalNumber
FallbackAvatar.Resource.Local(avatarColor)
} else if (isResolving) {
fallbackPhotoProvider.photoForResolvingRecipient
FallbackAvatar.Transparent
} else if (isDistributionList) {
fallbackPhotoProvider.photoForDistributionList
FallbackAvatar.Resource.DistributionList(avatarColor)
} else if (isCallLink) {
fallbackPhotoProvider.photoForCallLink
FallbackAvatar.Resource.CallLink(avatarColor)
} else if (groupIdValue != null) {
fallbackPhotoProvider.photoForGroup
FallbackAvatar.Resource.Group(avatarColor)
} else if (isGroup) {
fallbackPhotoProvider.photoForGroup
FallbackAvatar.Resource.Group(avatarColor)
} else if (groupName.isNotNullOrBlank()) {
fallbackPhotoProvider.getPhotoForRecipientWithName(groupName, targetSize)
FallbackAvatar.forTextOrDefault(groupName, avatarColor, FallbackAvatar.Resource.Group(avatarColor))
} else if (!nickname.isEmpty) {
fallbackPhotoProvider.getPhotoForRecipientWithName(nickname.toString(), targetSize)
FallbackAvatar.forTextOrDefault(nickname.toString(), avatarColor)
} else if (systemContactName.isNotNullOrBlank()) {
fallbackPhotoProvider.getPhotoForRecipientWithName(systemContactName, targetSize)
FallbackAvatar.forTextOrDefault(systemContactName, avatarColor)
} else if (!profileName.isEmpty) {
fallbackPhotoProvider.getPhotoForRecipientWithName(profileName.toString(), targetSize)
FallbackAvatar.forTextOrDefault(profileName.toString(), avatarColor)
} else {
fallbackPhotoProvider.photoForRecipientWithoutName
FallbackAvatar.Resource.Person(avatarColor)
}
}
@@ -818,30 +793,6 @@ class Recipient(
return id.hashCode()
}
open class FallbackPhotoProvider {
open val photoForLocalNumber: FallbackContactPhoto
get() = ResourceContactPhoto(R.drawable.ic_note_34, R.drawable.ic_note_24)
open val photoForResolvingRecipient: FallbackContactPhoto
get() = TransparentContactPhoto()
open val photoForGroup: FallbackContactPhoto
get() = ResourceContactPhoto(R.drawable.ic_group_outline_34, R.drawable.ic_group_outline_20, R.drawable.ic_group_outline_48)
open val photoForRecipientWithoutName: FallbackContactPhoto
get() = ResourceContactPhoto(R.drawable.ic_profile_outline_40, R.drawable.ic_profile_outline_20, R.drawable.ic_profile_outline_48)
val photoForDistributionList: FallbackContactPhoto
get() = ResourceContactPhoto(R.drawable.symbol_stories_24, R.drawable.symbol_stories_24, R.drawable.symbol_stories_24)
val photoForCallLink: FallbackContactPhoto
get() = ResourceContactPhoto(R.drawable.symbol_video_24, R.drawable.symbol_video_24, R.drawable.symbol_video_24)
open fun getPhotoForRecipientWithName(name: String, targetSize: Int): FallbackContactPhoto {
return GeneratedContactPhoto(name, R.drawable.ic_profile_outline_40, targetSize)
}
}
private class MissingAddressError(recipientId: RecipientId) : AssertionError("Missing address for " + recipientId.serialize())
companion object {
@@ -850,9 +801,6 @@ class Recipient(
@JvmField
val UNKNOWN = Recipient()
@JvmField
val DEFAULT_FALLBACK_PHOTO_PROVIDER = FallbackPhotoProvider()
private const val MAX_MEMBER_NAMES = 10
/**

View File

@@ -31,8 +31,6 @@ import org.thoughtcrime.securesms.badges.BadgeImageView;
import org.thoughtcrime.securesms.badges.view.ViewBadgeBottomSheetDialogFragment;
import org.thoughtcrime.securesms.components.settings.DSLSettingsIcon;
import org.thoughtcrime.securesms.components.settings.conversation.preferences.ButtonStripPreference;
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.FallbackPhoto80dp;
import org.thoughtcrime.securesms.fonts.SignalSymbols;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.nicknames.NicknameActivity;
@@ -167,13 +165,6 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
viewModel.getRecipient().observe(getViewLifecycleOwner(), recipient -> {
interactionsContainer.setVisibility(recipient.isSelf() ? View.GONE : View.VISIBLE);
avatar.setFallbackPhotoProvider(new Recipient.FallbackPhotoProvider() {
@Override
public @NonNull FallbackContactPhoto getPhotoForLocalNumber() {
return new FallbackPhoto80dp(R.drawable.ic_note_80, recipient.getAvatarColor());
}
});
avatar.displayChatAvatar(recipient);
if (!recipient.isSelf()) {

View File

@@ -4,10 +4,8 @@ import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.media.AudioManager
import android.os.Bundle
import android.text.SpannableString
@@ -52,13 +50,9 @@ import org.thoughtcrime.securesms.components.emoji.EmojiTextView
import org.thoughtcrime.securesms.components.segmentedprogressbar.SegmentedProgressBar
import org.thoughtcrime.securesms.components.segmentedprogressbar.SegmentedProgressBarListener
import org.thoughtcrime.securesms.components.spoiler.SpoilerAnnotation
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto
import org.thoughtcrime.securesms.contacts.avatars.FallbackPhoto20dp
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
import org.thoughtcrime.securesms.conversation.ConversationIntents
import org.thoughtcrime.securesms.conversation.MessageStyler
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardBottomSheet
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs
@@ -229,9 +223,6 @@ class StoryViewerPageFragment :
addToGroupStoryButtonWrapper
)
senderAvatar.setFallbackPhotoProvider(FallbackPhotoProvider())
groupAvatar.setFallbackPhotoProvider(FallbackPhotoProvider())
closeView.setOnClickListener {
requireActivity().onBackPressed()
}
@@ -1376,30 +1367,6 @@ class StoryViewerPageFragment :
}
}
private class FallbackPhotoProvider : Recipient.FallbackPhotoProvider() {
override val photoForGroup: FallbackContactPhoto
get() = FallbackPhoto20dp(R.drawable.symbol_group_20)
override val photoForResolvingRecipient: FallbackContactPhoto
get() = throw UnsupportedOperationException("This provider does not support resolving recipients")
override val photoForLocalNumber: FallbackContactPhoto
get() = throw UnsupportedOperationException("This provider does not support local number")
override fun getPhotoForRecipientWithName(name: String, targetSize: Int): FallbackContactPhoto {
return FixedSizeGeneratedContactPhoto(name, R.drawable.symbol_person_20)
}
override val photoForRecipientWithoutName: FallbackContactPhoto
get() = FallbackPhoto20dp(R.drawable.symbol_person_20)
}
private class FixedSizeGeneratedContactPhoto(name: String, fallbackResId: Int) : GeneratedContactPhoto(name, fallbackResId) {
override fun newFallbackDrawable(context: Context, color: AvatarColor, inverted: Boolean): Drawable {
return FallbackPhoto20dp(fallbackResId).asDrawable(context, color, inverted)
}
}
override fun onContentReady() {
Log.d(TAG, "Story content is ready.")
sharedViewModel.setContentIsReady()

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.util
import org.signal.core.util.dp
import kotlin.math.ceil
import kotlin.math.floor
/**
* Describes the absolute measurements for adaptive icon bitmaps.
*
* Because adaptive icons are meant to be 108dp with a 72dp image,
* this can result in subpixel measurement on some devices. This class
* is responsible for on-the-fly calculation of metrics that'll look good
* and not cause off-by-a-pixel errors in Adaptive bitmaps.
*/
object AdaptiveBitmapMetrics {
private val ADAPTIVE_ICON_OUTER_SIZE: Float = 108f.dp
private val ADAPTIVE_ICON_INNER_SIZE: Float = 72f.dp
@get:JvmStatic
val outerWidth = ceil(ADAPTIVE_ICON_OUTER_SIZE).toInt()
@get:JvmStatic
val innerWidth = ceil(ADAPTIVE_ICON_INNER_SIZE).let { ceiling ->
if (floor(ADAPTIVE_ICON_OUTER_SIZE) < outerWidth) {
ceiling + 2
} else {
ceiling
}.toInt()
}
@get:JvmStatic
val padding = (outerWidth - innerWidth) / 2f
}

View File

@@ -28,14 +28,14 @@ import com.bumptech.glide.request.transition.Transition;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatar;
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatarDrawable;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
import org.thoughtcrime.securesms.providers.AvatarProvider;
import org.thoughtcrime.securesms.recipients.Recipient;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@@ -115,7 +115,7 @@ public final class AvatarUtil {
if (Build.VERSION.SDK_INT > 29) {
return IconCompat.createWithContentUri(AvatarProvider.getContentUri(recipient.getId()));
} else {
return IconCompat.createWithBitmap(getBitmapForNotification(context, recipient, DrawableUtil.SHORTCUT_INFO_WRAPPED_SIZE));
return IconCompat.createWithBitmap(getBitmapForNotification(context, recipient, AdaptiveBitmapMetrics.getInnerWidth()));
}
}
@@ -162,7 +162,7 @@ public final class AvatarUtil {
photo = recipient.getContactPhoto();
}
final int size = targetSize == -1 ? DrawableUtil.SHORTCUT_INFO_WRAPPED_SIZE : targetSize;
final int size = targetSize == -1 ? AdaptiveBitmapMetrics.getInnerWidth() : targetSize;
final RequestBuilder<T> request = requestBuilder.load(photo)
.error(getFallback(context, recipient, size))
.diskCacheStrategy(DiskCacheStrategy.ALL)
@@ -183,9 +183,12 @@ public final class AvatarUtil {
}
private static Drawable getFallback(@NonNull Context context, @NonNull Recipient recipient, int targetSize) {
String name = Optional.of(recipient.getDisplayName(context)).orElse("");
FallbackAvatar fallbackAvatar = FallbackAvatar.forTextOrDefault(recipient.getDisplayName(context), recipient.getAvatarColor());
return new GeneratedContactPhoto(name, R.drawable.ic_profile_outline_40, targetSize).asDrawable(context, recipient.getAvatarColor());
Drawable avatar = new FallbackAvatarDrawable(context, fallbackAvatar).circleCrop();
avatar.setBounds(0, 0, targetSize, targetSize);
return avatar;
}
/**
@@ -199,7 +202,7 @@ public final class AvatarUtil {
private final int size;
private AvatarTarget(int size) {
this.size = size == UNDEFINED_SIZE ? DrawableUtil.SHORTCUT_INFO_WRAPPED_SIZE : size;
this.size = size == UNDEFINED_SIZE ? AdaptiveBitmapMetrics.getInnerWidth() : size;
}
public @Nullable Bitmap await() throws InterruptedException {

View File

@@ -2,9 +2,7 @@ package org.thoughtcrime.securesms.util;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
@@ -18,12 +16,8 @@ import com.bumptech.glide.load.model.ModelLoaderFactory;
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
import org.signal.libsignal.protocol.util.ByteUtil;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.FallbackPhoto80dp;
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto;
import org.thoughtcrime.securesms.avatar.fallback.FallbackAvatarDrawable;
import org.thoughtcrime.securesms.contacts.avatars.SystemContactPhoto;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.database.model.ProfileAvatarFileDetails;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -162,50 +156,19 @@ public final class ConversationShortcutPhoto implements Key {
private @NonNull Bitmap getShortcutInfoBitmap(@NonNull Context context) throws ExecutionException, InterruptedException {
return DrawableUtil.wrapBitmapForShortcutInfo(AvatarUtil.loadIconBitmapSquareNoCache(context,
photo.recipient,
DrawableUtil.SHORTCUT_INFO_WRAPPED_SIZE,
DrawableUtil.SHORTCUT_INFO_WRAPPED_SIZE));
AdaptiveBitmapMetrics.getInnerWidth(),
AdaptiveBitmapMetrics.getInnerWidth()));
}
private @NonNull Bitmap getFallbackForShortcut(@NonNull Context context) {
Recipient recipient = photo.recipient;
@DrawableRes final int photoSource;
if (recipient.isSelf()) {
photoSource = R.drawable.ic_note_80;
} else if (recipient.isGroup()) {
photoSource = R.drawable.ic_group_80;
} else {
photoSource = R.drawable.ic_profile_80;
}
FallbackContactPhoto photo = recipient.isSelf() || recipient.isGroup() ? new FallbackPhoto80dp(photoSource, recipient.getAvatarColor())
: new ShortcutGeneratedContactPhoto(recipient.getDisplayName(context), photoSource, ViewUtil.dpToPx(80), recipient.getAvatarColor());
Bitmap toWrap = DrawableUtil.toBitmap(photo.asCallCard(context), ViewUtil.dpToPx(80), ViewUtil.dpToPx(80));
Bitmap wrapped = DrawableUtil.wrapBitmapForShortcutInfo(toWrap);
Bitmap toWrap = DrawableUtil.toBitmap(new FallbackAvatarDrawable(context, recipient.getFallbackAvatar()), ViewUtil.dpToPx(80), ViewUtil.dpToPx(80));
Bitmap wrapped = DrawableUtil.wrapBitmapForShortcutInfo(toWrap);
toWrap.recycle();
return wrapped;
}
}
private static final class ShortcutGeneratedContactPhoto extends GeneratedContactPhoto {
private final AvatarColor color;
public ShortcutGeneratedContactPhoto(@NonNull String name, int fallbackResId, int targetSize, @NonNull AvatarColor color) {
super(name, fallbackResId, targetSize);
this.color = color;
}
@Override
protected Drawable newFallbackDrawable(@NonNull Context context, @NonNull AvatarColor color, boolean inverted) {
return new FallbackPhoto80dp(getFallbackResId(), color).asDrawable(context, AvatarColor.UNKNOWN);
}
@Override public Drawable asCallCard(@NonNull Context context) {
return new FallbackPhoto80dp(getFallbackResId(), color).asCallCard(context);
}
}
}

View File

@@ -10,10 +10,6 @@ import androidx.core.graphics.drawable.DrawableCompat;
public final class DrawableUtil {
private static final int SHORTCUT_INFO_BITMAP_SIZE = ViewUtil.dpToPx(108);
public static final int SHORTCUT_INFO_WRAPPED_SIZE = ViewUtil.dpToPx(72);
private static final int SHORTCUT_INFO_PADDING = (SHORTCUT_INFO_BITMAP_SIZE - SHORTCUT_INFO_WRAPPED_SIZE) / 2;
private DrawableUtil() {}
public static @NonNull Bitmap toBitmap(@NonNull Drawable drawable, int width, int height) {
@@ -27,11 +23,11 @@ public final class DrawableUtil {
}
public static @NonNull Bitmap wrapBitmapForShortcutInfo(@NonNull Bitmap toWrap) {
Bitmap bitmap = Bitmap.createBitmap(SHORTCUT_INFO_BITMAP_SIZE, SHORTCUT_INFO_BITMAP_SIZE, Bitmap.Config.ARGB_8888);
Bitmap scaled = Bitmap.createScaledBitmap(toWrap, SHORTCUT_INFO_WRAPPED_SIZE, SHORTCUT_INFO_WRAPPED_SIZE, true);
Bitmap bitmap = Bitmap.createBitmap(AdaptiveBitmapMetrics.getOuterWidth(), AdaptiveBitmapMetrics.getOuterWidth(), Bitmap.Config.ARGB_8888);
Bitmap scaled = Bitmap.createScaledBitmap(toWrap, AdaptiveBitmapMetrics.getInnerWidth(), AdaptiveBitmapMetrics.getInnerWidth(), true);
Canvas canvas = new Canvas(bitmap);
canvas.drawBitmap(scaled, SHORTCUT_INFO_PADDING, SHORTCUT_INFO_PADDING, null);
canvas.drawBitmap(scaled, AdaptiveBitmapMetrics.getPadding(), AdaptiveBitmapMetrics.getPadding(), null);
return bitmap;
}