Skip to content

Commit b5af691

Browse files
authored
Add badges to Avatars in a variety of places.
1 parent 5c1b57e commit b5af691

25 files changed

+502
-237
lines changed

app/src/main/java/org/thoughtcrime/securesms/animation/transitions/CircleAvatarTransition.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import android.view.animation.AccelerateInterpolator
1414
import android.view.animation.DecelerateInterpolator
1515
import android.view.animation.Interpolator
1616
import androidx.annotation.RequiresApi
17-
import org.thoughtcrime.securesms.components.AvatarImageView
1817

1918
private const val POSITION_ON_SCREEN = "signal.circleavatartransition.positiononscreen"
2019
private const val WIDTH = "signal.circleavatartransition.width"
@@ -36,7 +35,7 @@ class CircleAvatarTransition(context: Context, attrs: AttributeSet?) : Transitio
3635
private fun captureValues(transitionValues: TransitionValues) {
3736
val view: View = transitionValues.view
3837

39-
if (view is AvatarImageView) {
38+
if (view.transitionName == "avatar") {
4039
val topLeft = intArrayOf(0, 0)
4140
view.getLocationOnScreen(topLeft)
4241
transitionValues.values[POSITION_ON_SCREEN] = topLeft
@@ -51,7 +50,7 @@ class CircleAvatarTransition(context: Context, attrs: AttributeSet?) : Transitio
5150
}
5251

5352
val view: View = endValues.view
54-
if (view !is AvatarImageView || view.transitionName != "avatar") {
53+
if (view.transitionName != "avatar") {
5554
return null
5655
}
5756

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package org.thoughtcrime.securesms.badges
2+
3+
import android.content.Context
4+
import android.graphics.Color
5+
import android.graphics.drawable.Drawable
6+
import android.util.AttributeSet
7+
import androidx.annotation.ColorInt
8+
import androidx.annotation.Px
9+
import androidx.appcompat.widget.AppCompatImageView
10+
import androidx.core.content.res.use
11+
import androidx.lifecycle.Lifecycle
12+
import org.signal.core.util.logging.Log
13+
import org.thoughtcrime.securesms.R
14+
import org.thoughtcrime.securesms.badges.Badges.insetWithOutline
15+
import org.thoughtcrime.securesms.badges.models.Badge
16+
import org.thoughtcrime.securesms.mms.GlideApp
17+
import org.thoughtcrime.securesms.recipients.Recipient
18+
import org.thoughtcrime.securesms.util.ViewUtil
19+
import org.thoughtcrime.securesms.util.visible
20+
21+
private val TAG = Log.tag(BadgeImageView::class.java)
22+
23+
class BadgeImageView @JvmOverloads constructor(
24+
context: Context,
25+
attrs: AttributeSet? = null
26+
) : AppCompatImageView(context, attrs) {
27+
28+
@Px
29+
private var outlineWidth: Float = 0f
30+
31+
@ColorInt
32+
private var outlineColor: Int = Color.BLACK
33+
34+
init {
35+
context.obtainStyledAttributes(attrs, R.styleable.BadgeImageView).use {
36+
outlineWidth = it.getDimension(R.styleable.BadgeImageView_badge_outline_width, 0f)
37+
outlineColor = it.getColor(R.styleable.BadgeImageView_badge_outline_color, Color.BLACK)
38+
}
39+
}
40+
41+
fun setBadgeFromRecipient(recipient: Recipient?) {
42+
if (recipient == null || recipient.badges.isEmpty()) {
43+
setBadge(null)
44+
} else {
45+
setBadge(recipient.badges[0])
46+
}
47+
}
48+
49+
fun setBadge(badge: Badge?) {
50+
visible = badge != null
51+
52+
val lifecycle = ViewUtil.getActivityLifecycle(this)
53+
if (lifecycle?.currentState == Lifecycle.State.DESTROYED) {
54+
Log.w(TAG, "Ignoring setBadge call for destroyed activity.")
55+
return
56+
}
57+
58+
GlideApp
59+
.with(this)
60+
.load(badge)
61+
.into(this)
62+
}
63+
64+
override fun setImageDrawable(drawable: Drawable?) {
65+
if (drawable == null || outlineWidth == 0f) {
66+
super.setImageDrawable(drawable)
67+
} else {
68+
super.setImageDrawable(
69+
drawable.insetWithOutline(
70+
outlineWidth, outlineColor
71+
)
72+
)
73+
}
74+
}
75+
}

app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsFragment.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.widget.TextView
55
import androidx.lifecycle.ViewModelProvider
66
import androidx.navigation.Navigation
77
import org.thoughtcrime.securesms.R
8+
import org.thoughtcrime.securesms.badges.BadgeImageView
89
import org.thoughtcrime.securesms.components.AvatarImageView
910
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
1011
import org.thoughtcrime.securesms.components.settings.DSLSettingsAdapter
@@ -162,6 +163,7 @@ class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__men
162163

163164
private val avatarView: AvatarImageView = itemView.findViewById(R.id.icon)
164165
private val aboutView: TextView = itemView.findViewById(R.id.about)
166+
private val badgeView: BadgeImageView = itemView.findViewById(R.id.badge)
165167

166168
override fun bind(model: BioPreference) {
167169
super.bind(model)
@@ -171,6 +173,7 @@ class AppSettingsFragment : DSLSettingsFragment(R.string.text_secure_normal__men
171173
titleView.text = model.recipient.profileName.toString()
172174
summaryView.text = PhoneNumberFormatter.prettyPrint(model.recipient.requireE164())
173175
avatarView.setRecipient(Recipient.self())
176+
badgeView.setBadgeFromRecipient(Recipient.self())
174177

175178
titleView.visibility = View.VISIBLE
176179
summaryView.visibility = View.VISIBLE

app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/ConversationSettingsFragment.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,9 @@ class ConversationSettingsFragment : DSLSettingsFragment(
259259
)
260260
}
261261
}
262+
},
263+
onBadgeClick = { badge ->
264+
ViewBadgeBottomSheetDialogFragment.show(parentFragmentManager, state.recipient.id, badge)
262265
}
263266
)
264267
)

app/src/main/java/org/thoughtcrime/securesms/components/settings/conversation/preferences/AvatarPreference.kt

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.components.settings.conversation.preferences
33
import android.view.View
44
import androidx.core.view.ViewCompat
55
import org.thoughtcrime.securesms.R
6+
import org.thoughtcrime.securesms.badges.BadgeImageView
7+
import org.thoughtcrime.securesms.badges.models.Badge
68
import org.thoughtcrime.securesms.components.AvatarImageView
79
import org.thoughtcrime.securesms.components.settings.PreferenceModel
810
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto
@@ -23,7 +25,8 @@ object AvatarPreference {
2325

2426
class Model(
2527
val recipient: Recipient,
26-
val onAvatarClick: (View) -> Unit
28+
val onAvatarClick: (View) -> Unit,
29+
val onBadgeClick: (Badge) -> Unit
2730
) : PreferenceModel<Model>() {
2831
override fun areItemsTheSame(newItem: Model): Boolean {
2932
return recipient == newItem.recipient
@@ -36,11 +39,23 @@ object AvatarPreference {
3639

3740
private class ViewHolder(itemView: View) : MappingViewHolder<Model>(itemView) {
3841
private val avatar: AvatarImageView = itemView.findViewById<AvatarImageView>(R.id.bio_preference_avatar).apply {
39-
ViewCompat.setTransitionName(this, "avatar")
4042
setFallbackPhotoProvider(AvatarPreferenceFallbackPhotoProvider())
4143
}
4244

45+
private val badge: BadgeImageView = itemView.findViewById(R.id.bio_preference_badge)
46+
47+
init {
48+
ViewCompat.setTransitionName(avatar.parent as View, "avatar")
49+
}
50+
4351
override fun bind(model: Model) {
52+
badge.setBadgeFromRecipient(model.recipient)
53+
badge.setOnClickListener {
54+
val badge = model.recipient.badges.firstOrNull()
55+
if (badge != null) {
56+
model.onBadgeClick(badge)
57+
}
58+
}
4459
avatar.setAvatar(model.recipient)
4560
avatar.disableQuickContact()
4661
avatar.setOnClickListener { model.onAvatarClick(avatar) }

app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListItem.java

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import android.util.AttributeSet;
66
import android.view.View;
77
import android.widget.CheckBox;
8-
import android.widget.LinearLayout;
98
import android.widget.TextView;
109

1110
import androidx.annotation.NonNull;
@@ -14,6 +13,7 @@
1413

1514
import org.signal.core.util.logging.Log;
1615
import org.thoughtcrime.securesms.R;
16+
import org.thoughtcrime.securesms.badges.BadgeImageView;
1717
import org.thoughtcrime.securesms.components.AvatarImageView;
1818
import org.thoughtcrime.securesms.components.FromTextView;
1919
import org.thoughtcrime.securesms.mms.GlideRequests;
@@ -37,16 +37,17 @@ public class ContactSelectionListItem extends ConstraintLayout implements Recipi
3737
private TextView labelView;
3838
private CheckBox checkBox;
3939
private View smsTag;
40-
41-
private String number;
42-
private String chipName;
43-
private int contactType;
44-
private String contactName;
45-
private String contactNumber;
46-
private String contactLabel;
47-
private String contactAbout;
48-
private LiveRecipient recipient;
49-
private GlideRequests glideRequests;
40+
private BadgeImageView badge;
41+
42+
private String number;
43+
private String chipName;
44+
private int contactType;
45+
private String contactName;
46+
private String contactNumber;
47+
private String contactLabel;
48+
private String contactAbout;
49+
private LiveRecipient recipient;
50+
private GlideRequests glideRequests;
5051

5152
public ContactSelectionListItem(Context context) {
5253
super(context);
@@ -65,6 +66,7 @@ protected void onFinishInflate() {
6566
this.nameView = findViewById(R.id.name);
6667
this.checkBox = findViewById(R.id.check_box);
6768
this.smsTag = findViewById(R.id.sms_tag);
69+
this.badge = findViewById(R.id.contact_badge);
6870

6971
ViewUtil.setTextViewGravityStart(this.nameView, getContext());
7072
}
@@ -118,6 +120,8 @@ public void set(@NonNull GlideRequests glideRequests,
118120
}
119121

120122
this.checkBox.setVisibility(checkboxVisible ? View.VISIBLE : View.GONE);
123+
124+
badge.setBadgeFromRecipient(recipientSnapshot);
121125
}
122126

123127
public void setChecked(boolean selected, boolean animate) {
@@ -229,6 +233,7 @@ public void onRecipientChanged(@NonNull Recipient recipient) {
229233
contactPhotoImage.setAvatar(glideRequests, recipient, false);
230234
setText(recipient, contactType, contactName, contactNumber, contactLabel, contactAbout);
231235
smsTag.setVisibility(recipient.isRegistered() ? GONE : VISIBLE);
236+
badge.setBadgeFromRecipient(recipient);
232237
} else {
233238
Log.w(TAG, "Bad change! Local recipient doesn't match. Ignoring. Local: " + (this.recipient == null ? "null" : this.recipient.getId()) + ", Changed: " + recipient.getId());
234239
}

app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationBannerView.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import org.signal.core.util.concurrent.SignalExecutors;
1414
import org.thoughtcrime.securesms.R;
15+
import org.thoughtcrime.securesms.badges.BadgeImageView;
1516
import org.thoughtcrime.securesms.components.AvatarImageView;
1617
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
1718
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
@@ -29,6 +30,7 @@ public class ConversationBannerView extends ConstraintLayout {
2930
private TextView contactSubtitle;
3031
private EmojiTextView contactDescription;
3132
private View tapToView;
33+
private BadgeImageView contactBadge;
3234

3335
public ConversationBannerView(Context context) {
3436
this(context, null);
@@ -44,6 +46,7 @@ public ConversationBannerView(Context context, AttributeSet attrs, int defStyleA
4446
inflate(getContext(), R.layout.conversation_banner_view, this);
4547

4648
contactAvatar = findViewById(R.id.message_request_avatar);
49+
contactBadge = findViewById(R.id.message_request_badge);
4750
contactTitle = findViewById(R.id.message_request_title);
4851
contactAbout = findViewById(R.id.message_request_about);
4952
contactSubtitle = findViewById(R.id.message_request_subtitle);
@@ -53,6 +56,10 @@ public ConversationBannerView(Context context, AttributeSet attrs, int defStyleA
5356
contactAvatar.setFallbackPhotoProvider(new FallbackPhotoProvider());
5457
}
5558

59+
public void setBadge(@Nullable Recipient recipient) {
60+
contactBadge.setBadgeFromRecipient(recipient);
61+
}
62+
5663
public void setAvatar(@NonNull GlideRequests requests, @Nullable Recipient recipient) {
5764
contactAvatar.setAvatar(requests, recipient, false);
5865

app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import androidx.core.app.ActivityCompat;
5656
import androidx.core.app.ActivityOptionsCompat;
5757
import androidx.core.text.HtmlCompat;
58+
import androidx.core.view.ViewCompat;
5859
import androidx.lifecycle.LiveData;
5960
import androidx.lifecycle.Observer;
6061
import androidx.lifecycle.ViewModelProviders;
@@ -579,6 +580,8 @@ private void presentMessageRequestProfileView(@NonNull Context context, @NonNull
579580
int pendingMemberCount = recipientInfo.getGroupPendingMemberCount();
580581
List<String> groups = recipientInfo.getSharedGroups();
581582

583+
conversationBanner.setBadge(recipient);
584+
582585
if (recipient != null) {
583586
conversationBanner.setAvatar(GlideApp.with(context), recipient);
584587
conversationBanner.showBackgroundBubble(recipient.hasWallpaper());
@@ -1510,6 +1513,7 @@ public void onViewOnceMessageClicked(@NonNull MmsMessageRecord messageRecord) {
15101513
@Override
15111514
public void onSharedContactDetailsClicked(@NonNull Contact contact, @NonNull View avatarTransitionView) {
15121515
if (getContext() != null && getActivity() != null) {
1516+
ViewCompat.setTransitionName(avatarTransitionView, "avatar");
15131517
Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(), avatarTransitionView, "avatar").toBundle();
15141518
ActivityCompat.startActivity(getActivity(), SharedContactDetailsActivity.getIntent(getContext(), contact), bundle);
15151519
}

app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1788,7 +1788,7 @@ private class SharedContactClickListener implements View.OnClickListener {
17881788
@Override
17891789
public void onClick(View view) {
17901790
if (eventListener != null && batchSelected.isEmpty() && messageRecord.isMms() && !((MmsMessageRecord) messageRecord).getSharedContacts().isEmpty()) {
1791-
eventListener.onSharedContactDetailsClicked(((MmsMessageRecord) messageRecord).getSharedContacts().get(0), sharedContactStub.get().getAvatarView());
1791+
eventListener.onSharedContactDetailsClicked(((MmsMessageRecord) messageRecord).getSharedContacts().get(0), (View) sharedContactStub.get().getAvatarView().getParent());
17921792
} else {
17931793
passthroughClickListener.onClick(view);
17941794
}

app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationTitleView.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import com.annimon.stream.Stream;
2020

2121
import org.thoughtcrime.securesms.R;
22+
import org.thoughtcrime.securesms.badges.BadgeImageView;
23+
import org.thoughtcrime.securesms.badges.models.Badge;
2224
import org.thoughtcrime.securesms.components.AvatarImageView;
2325
import org.thoughtcrime.securesms.mms.GlideRequests;
2426
import org.thoughtcrime.securesms.recipients.LiveRecipient;
@@ -31,6 +33,7 @@
3133
public class ConversationTitleView extends RelativeLayout {
3234

3335
private AvatarImageView avatar;
36+
private BadgeImageView badge;
3437
private TextView title;
3538
private TextView subtitle;
3639
private ImageView verified;
@@ -52,6 +55,7 @@ public void onFinishInflate() {
5255
super.onFinishInflate();
5356

5457
this.title = findViewById(R.id.title);
58+
this.badge = findViewById(R.id.badge);
5559
this.subtitle = findViewById(R.id.subtitle);
5660
this.verified = findViewById(R.id.verified_indicator);
5761
this.subtitleContainer = findViewById(R.id.subtitle_container);
@@ -102,6 +106,8 @@ public void setTitle(@NonNull GlideRequests glideRequests, @Nullable Recipient r
102106
this.avatar.setAvatar(glideRequests, recipient, false);
103107
}
104108

109+
badge.setBadgeFromRecipient(recipient);
110+
105111
updateVerifiedSubtitleVisibility();
106112
}
107113

app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItem.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.thoughtcrime.securesms.BindableConversationListItem;
4343
import org.thoughtcrime.securesms.R;
4444
import org.thoughtcrime.securesms.Unbindable;
45+
import org.thoughtcrime.securesms.badges.BadgeImageView;
4546
import org.thoughtcrime.securesms.components.AlertView;
4647
import org.thoughtcrime.securesms.components.AvatarImageView;
4748
import org.thoughtcrime.securesms.components.DeliveryStatusView;
@@ -105,6 +106,7 @@ public final class ConversationListItem extends ConstraintLayout
105106
private boolean batchMode;
106107
private Locale locale;
107108
private String highlightSubstring;
109+
private BadgeImageView badge;
108110

109111
private int unreadCount;
110112
private AvatarImageView contactPhotoImage;
@@ -135,6 +137,7 @@ protected void onFinishInflate() {
135137
this.thumbnailView = findViewById(R.id.conversation_list_item_thumbnail);
136138
this.archivedView = findViewById(R.id.conversation_list_item_archived);
137139
this.unreadIndicator = findViewById(R.id.conversation_list_item_unread_indicator);
140+
this.badge = findViewById(R.id.conversation_list_item_badge);
138141
thumbnailView.setClickable(false);
139142
}
140143

@@ -201,6 +204,7 @@ public void bind(@NonNull ThreadRecord thread,
201204
setThumbnailSnippet(thread);
202205
setBatchMode(batchMode);
203206
setRippleColor(recipient.get());
207+
badge.setBadgeFromRecipient(recipient.get());
204208
setUnreadIndicator(thread);
205209
this.contactPhotoImage.setAvatar(glideRequests, recipient.get(), !batchMode);
206210
}
@@ -428,6 +432,7 @@ public void onRecipientChanged(@NonNull Recipient recipient) {
428432
}
429433
contactPhotoImage.setAvatar(glideRequests, recipient, !batchMode);
430434
setRippleColor(recipient);
435+
badge.setBadgeFromRecipient(recipient);
431436
}
432437

433438
private static @NonNull LiveData<SpannableString> getThreadDisplayBody(@NonNull Context context, @NonNull ThreadRecord thread) {

0 commit comments

Comments
 (0)