diff --git a/app/src/main/java/org/thoughtcrime/securesms/AvatarPreviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/AvatarPreviewActivity.java index 2a4ee7fd03..3f555f7c68 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/AvatarPreviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/AvatarPreviewActivity.java @@ -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() { @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/avatar/fallback/FallbackAvatar.kt b/app/src/main/java/org/thoughtcrime/securesms/avatar/fallback/FallbackAvatar.kt new file mode 100644 index 0000000000..601d6bdbf3 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/avatar/fallback/FallbackAvatar.kt @@ -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 + } + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/avatar/fallback/FallbackAvatarDrawable.kt b/app/src/main/java/org/thoughtcrime/securesms/avatar/fallback/FallbackAvatarDrawable.kt new file mode 100644 index 0000000000..d8c22f872d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/avatar/fallback/FallbackAvatarDrawable.kt @@ -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() + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/avatar/fallback/FallbackAvatarImage.kt b/app/src/main/java/org/thoughtcrime/securesms/avatar/fallback/FallbackAvatarImage.kt new file mode 100644 index 0000000000..324ff924dd --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/avatar/fallback/FallbackAvatarImage.kt @@ -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) + ) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/avatar/view/AvatarView.kt b/app/src/main/java/org/thoughtcrime/securesms/avatar/view/AvatarView.kt index f44c8ca9e9..66d68f3b9f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/avatar/view/AvatarView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/avatar/view/AvatarView.kt @@ -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() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/AvatarImageView.java b/app/src/main/java/org/thoughtcrime/securesms/components/AvatarImageView.java index fce2d5da02..83f4b92b01 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/AvatarImageView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/AvatarImageView.java @@ -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 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; diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewView.java b/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewView.java index 8866f223f3..c7527c5953 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewView.java @@ -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 { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/AvatarPreference.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/AvatarPreference.kt index 3a8446a21d..3ff63e0512 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/AvatarPreference.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/AvatarPreference.kt @@ -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(itemView) { - private val avatar: AvatarView = itemView.findViewById(R.id.bio_preference_avatar).apply { - setFallbackPhotoProvider(AvatarPreferenceFallbackPhotoProvider()) - } + private val avatar: AvatarView = itemView.findViewById(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)) - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantView.java b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantView.java index 3cc56a9ebb..bc75c45973 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantView.java @@ -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, diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactChip.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactChip.java index 731e3ddb34..d678d971d9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactChip.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactChip.java @@ -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(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/FallbackContactPhoto.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/FallbackContactPhoto.java deleted file mode 100644 index 6ee00d7fd0..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/FallbackContactPhoto.java +++ /dev/null @@ -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); - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/FallbackPhoto.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/FallbackPhoto.java deleted file mode 100644 index eec617576a..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/FallbackPhoto.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/FallbackPhoto20dp.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/FallbackPhoto20dp.java deleted file mode 100644 index fe2630b73e..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/FallbackPhoto20dp.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/FallbackPhoto80dp.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/FallbackPhoto80dp.java deleted file mode 100644 index 4d19fe5fcf..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/FallbackPhoto80dp.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/GeneratedContactPhoto.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/GeneratedContactPhoto.java deleted file mode 100644 index 1812801b1a..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/GeneratedContactPhoto.java +++ /dev/null @@ -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); - - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/ResourceContactPhoto.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/ResourceContactPhoto.java deleted file mode 100644 index 500c43b14e..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/ResourceContactPhoto.java +++ /dev/null @@ -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; - } - } - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/TransparentContactPhoto.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/TransparentContactPhoto.java deleted file mode 100644 index 2a452af5e1..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/TransparentContactPhoto.java +++ /dev/null @@ -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); - } - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchAdapter.kt index 5b2a056846..d6e2231377 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/paged/ContactSearchAdapter.kt @@ -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() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationHeaderView.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationHeaderView.java index 6c8b804dfe..1c158eddb5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationHeaderView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationHeaderView.java @@ -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); - } - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt index 50dd7b0971..0c8bfc7a32 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt @@ -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 { - 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 { 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 { return Single.create { - val bitmap = BitmapUtil.createScaledBitmap(bitmap, SHORTCUT_ICON_SIZE, SHORTCUT_ICON_SIZE) + val bitmap = DrawableUtil.wrapBitmapForShortcutInfo(bitmap) it.setCancellable { bitmap.recycle() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinBottomSheetDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinBottomSheetDialogFragment.java index ff23ee3c69..e7385ba7ac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinBottomSheetDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/invitesandrequests/joining/GroupJoinBottomSheetDialogFragment.java @@ -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); - } - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/CalleeMustAcceptMessageRequestActivity.java b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/CalleeMustAcceptMessageRequestActivity.java index 123c4d4b2a..a250ac5af8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/CalleeMustAcceptMessageRequestActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/CalleeMustAcceptMessageRequestActivity.java @@ -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); - } - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt index d5f6b49b62..b86a1132fa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt @@ -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() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationExtensions.kt index 2c62829d82..ef5e8d8910 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationExtensions.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationExtensions.kt @@ -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> = 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 { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt index 7c413736f4..ebc86a375e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt @@ -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) diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/spoofing/ReviewBannerView.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/spoofing/ReviewBannerView.java index 549c5b9bf6..caad1944b5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/profiles/spoofing/ReviewBannerView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/spoofing/ReviewBannerView.java @@ -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(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/providers/AvatarProvider.kt b/app/src/main/java/org/thoughtcrime/securesms/providers/AvatarProvider.kt index e66e2279d7..bbdb15fbcd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/providers/AvatarProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/providers/AvatarProvider.kt @@ -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() diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.kt b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.kt index 0ea672ffbc..0bdab79d96 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.kt @@ -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 /** diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientBottomSheetDialogFragment.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientBottomSheetDialogFragment.java index 4f1d83c454..fda8f9f410 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientBottomSheetDialogFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/bottomsheet/RecipientBottomSheetDialogFragment.java @@ -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()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt index ce6e1e4c59..c25b3a8821 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt @@ -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() diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/AdaptiveBitmapMetrics.kt b/app/src/main/java/org/thoughtcrime/securesms/util/AdaptiveBitmapMetrics.kt new file mode 100644 index 0000000000..a4c1f5f5fb --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/util/AdaptiveBitmapMetrics.kt @@ -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 +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtil.java index dc4bced401..7b6d1ed7ac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtil.java @@ -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 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 { diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ConversationShortcutPhoto.java b/app/src/main/java/org/thoughtcrime/securesms/util/ConversationShortcutPhoto.java index c1acb968d6..c992b091ba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ConversationShortcutPhoto.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ConversationShortcutPhoto.java @@ -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); - } - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/DrawableUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/DrawableUtil.java index 884e6247d4..2c1b2208a3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/DrawableUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/DrawableUtil.java @@ -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; } diff --git a/app/src/main/res/drawable/symbol_group_display_bold_40.xml b/app/src/main/res/drawable/symbol_group_display_bold_40.xml new file mode 100644 index 0000000000..3eed63a1e2 --- /dev/null +++ b/app/src/main/res/drawable/symbol_group_display_bold_40.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/symbol_note_24.xml b/app/src/main/res/drawable/symbol_note_24.xml new file mode 100644 index 0000000000..263ae1a31f --- /dev/null +++ b/app/src/main/res/drawable/symbol_note_24.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/symbol_note_compact_16.xml b/app/src/main/res/drawable/symbol_note_compact_16.xml new file mode 100644 index 0000000000..a6832d39db --- /dev/null +++ b/app/src/main/res/drawable/symbol_note_compact_16.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/symbol_note_display_bold_40.xml b/app/src/main/res/drawable/symbol_note_display_bold_40.xml new file mode 100644 index 0000000000..9ed6bc17f2 --- /dev/null +++ b/app/src/main/res/drawable/symbol_note_display_bold_40.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/symbol_person_display_bold_40.xml b/app/src/main/res/drawable/symbol_person_display_bold_40.xml new file mode 100644 index 0000000000..4860e8cb74 --- /dev/null +++ b/app/src/main/res/drawable/symbol_person_display_bold_40.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/symbol_stories_compact_16.xml b/app/src/main/res/drawable/symbol_stories_compact_16.xml new file mode 100644 index 0000000000..d757fc2dbd --- /dev/null +++ b/app/src/main/res/drawable/symbol_stories_compact_16.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/symbol_stories_display_bold_40.xml b/app/src/main/res/drawable/symbol_stories_display_bold_40.xml new file mode 100644 index 0000000000..0a3515ae80 --- /dev/null +++ b/app/src/main/res/drawable/symbol_stories_display_bold_40.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/symbol_video_compact_16.xml b/app/src/main/res/drawable/symbol_video_compact_16.xml new file mode 100644 index 0000000000..a3d8df9923 --- /dev/null +++ b/app/src/main/res/drawable/symbol_video_compact_16.xml @@ -0,0 +1,9 @@ + + +