From 49d6743cbb8051872be2ab0db211820fc1ba6849 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Tue, 12 Dec 2023 12:14:28 -0400 Subject: [PATCH] Fix conversation list jank after returning from calls tab. --- .../BindableConversationListItem.java | 1 + .../ConversationListAdapter.java | 21 ++++--- .../ConversationListFragment.java | 4 +- .../ConversationListItem.java | 56 ++++++++----------- .../ConversationListItemAction.java | 5 ++ .../ConversationListSearchAdapter.kt | 43 +++++++++++--- .../TimestampPayloadSupport.kt | 14 +++++ 7 files changed, 95 insertions(+), 49 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversationlist/TimestampPayloadSupport.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationListItem.java b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationListItem.java index 5b11c1fa2f..63f0970803 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationListItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationListItem.java @@ -20,4 +20,5 @@ public interface BindableConversationListItem extends Unbindable { void setSelectedConversations(@NonNull ConversationSet conversations); void updateTypingIndicator(@NonNull Set typingThreads); + void updateTimestamp(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListAdapter.java index bb47ac5db3..e9962a59fe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListAdapter.java @@ -29,7 +29,7 @@ import java.util.Locale; import java.util.Objects; import java.util.Set; -class ConversationListAdapter extends ListAdapter { +class ConversationListAdapter extends ListAdapter implements TimestampPayloadSupport { private static final int TYPE_THREAD = 1; private static final int TYPE_ACTION = 2; @@ -41,7 +41,8 @@ class ConversationListAdapter extends ListAdapter vh.getConversationListItem().updateTypingIndicator(typingSet); + case SELECTION -> vh.getConversationListItem().setSelectedConversations(selectedConversations); + case TIMESTAMP -> vh.getConversationListItem().updateTimestamp(); } } } @@ -190,6 +192,11 @@ class ConversationListAdapter extends ListAdapter displayBody; private Disposable joinMembersDisposable = Disposable.empty(); + private Runnable updateDateView = null; public ConversationListItem(Context context) { this(context, null); @@ -249,11 +250,16 @@ public final class ConversationListItem extends ConstraintLayout implements Bind observeDisplayBody(lifecycleOwner, displayBody); if (thread.getDate() > 0) { - CharSequence date = DateUtils.getBriefRelativeTimeSpanString(getContext(), locale, thread.getDate()); - dateView.setText(date); dateView.setTypeface(thread.isRead() ? LIGHT_TYPEFACE : BOLD_TYPEFACE); dateView.setTextColor(thread.isRead() ? ContextCompat.getColor(getContext(), R.color.signal_text_secondary) : ContextCompat.getColor(getContext(), R.color.signal_text_primary)); + + updateDateView = () -> { + CharSequence date = DateUtils.getBriefRelativeTimeSpanString(getContext(), locale, thread.getDate()); + dateView.setText(date); + }; + + updateDateView.run(); } if (thread.isArchived()) { @@ -278,35 +284,6 @@ public final class ConversationListItem extends ConstraintLayout implements Bind } } - public void bindContact(@NonNull LifecycleOwner lifecycleOwner, - @NonNull Recipient contact, - @NonNull GlideRequests glideRequests, - @NonNull Locale locale, - @Nullable String highlightSubstring) - { - this.glideRequests = glideRequests; - this.locale = locale; - this.highlightSubstring = highlightSubstring; - - observeRecipient(lifecycleOwner, contact.live()); - observeDisplayBody(null, null); - joinMembersDisposable.dispose(); - setSubjectViewText(null); - - fromView.setText(contact, SearchUtil.getHighlightedSpan(locale, searchStyleFactory, new SpannableString(contact.getDisplayName(getContext())), highlightSubstring, SearchUtil.MATCH_ALL), true, null); - setSubjectViewText(SearchUtil.getHighlightedSpan(locale, searchStyleFactory, contact.getE164().orElse(""), highlightSubstring, SearchUtil.MATCH_ALL)); - dateView.setText(""); - archivedView.setVisibility(GONE); - unreadIndicator.setVisibility(GONE); - unreadMentions.setVisibility(GONE); - deliveryStatusIndicator.setNone(); - alertView.setNone(); - - setSelectedConversations(new ConversationSet()); - setBadgeFromRecipient(recipient.get()); - contactPhotoImage.setAvatar(glideRequests, recipient.get(), !batchMode); - } - public void bindMessage(@NonNull LifecycleOwner lifecycleOwner, @NonNull MessageResult messageResult, @NonNull GlideRequests glideRequests, @@ -324,7 +301,10 @@ public final class ConversationListItem extends ConstraintLayout implements Bind fromView.setText(recipient.get(), recipient.get().getDisplayNameOrUsername(getContext()), false, null, false); setSubjectViewText(SearchUtil.getHighlightedSpan(locale, searchStyleFactory, messageResult.getBodySnippet(), highlightSubstring, SearchUtil.MATCH_ALL)); - dateView.setText(DateUtils.getBriefRelativeTimeSpanString(getContext(), locale, messageResult.getReceivedTimestampMs())); + + updateDateView = () -> dateView.setText(DateUtils.getBriefRelativeTimeSpanString(getContext(), locale, messageResult.getReceivedTimestampMs())); + + updateDateView.run(); archivedView.setVisibility(GONE); unreadIndicator.setVisibility(GONE); unreadMentions.setVisibility(GONE); @@ -353,7 +333,9 @@ public final class ConversationListItem extends ConstraintLayout implements Bind }); fromView.setText(recipient.get(), false); - dateView.setText(DateUtils.getBriefRelativeTimeSpanString(getContext(), locale, groupWithMembers.getDate())); + + updateDateView = () -> dateView.setText(DateUtils.getBriefRelativeTimeSpanString(getContext(), locale, groupWithMembers.getDate())); + updateDateView.run(); archivedView.setVisibility(GONE); unreadIndicator.setVisibility(GONE); unreadMentions.setVisibility(GONE); @@ -386,6 +368,7 @@ public final class ConversationListItem extends ConstraintLayout implements Bind observeDisplayBody(null, null); joinMembersDisposable.dispose(); + updateDateView = null; } @Override @@ -429,6 +412,13 @@ public final class ConversationListItem extends ConstraintLayout implements Bind } } + @Override + public void updateTimestamp() { + if (updateDateView != null) { + updateDateView.run(); + } + } + public Recipient getRecipient() { return recipient.get(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItemAction.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItemAction.java index a3b01a7606..e88dbd2909 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItemAction.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListItemAction.java @@ -64,4 +64,9 @@ public class ConversationListItemAction extends FrameLayout implements BindableC public void updateTypingIndicator(@NonNull Set typingThreads) { } + + @Override + public void updateTimestamp() { + // Intentionally left blank. + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListSearchAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListSearchAdapter.kt index 39264b8594..797650ddab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListSearchAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListSearchAdapter.kt @@ -33,7 +33,11 @@ class ConversationListSearchAdapter( callButtonClickCallbacks: CallButtonClickCallbacks, lifecycleOwner: LifecycleOwner, glideRequests: GlideRequests -) : ContactSearchAdapter(context, fixedContacts, displayOptions, onClickedCallbacks, longClickCallbacks, storyContextMenuCallbacks, callButtonClickCallbacks) { +) : ContactSearchAdapter(context, fixedContacts, displayOptions, onClickedCallbacks, longClickCallbacks, storyContextMenuCallbacks, callButtonClickCallbacks), TimestampPayloadSupport { + + companion object { + private const val PAYLOAD_TIMESTAMP = 0 + } init { registerFactory( @@ -62,6 +66,27 @@ class ConversationListSearchAdapter( ) } + override fun notifyTimestampPayloadUpdate() { + notifyItemRangeChanged(0, itemCount, PAYLOAD_TIMESTAMP) + } + + private abstract class ConversationListItemViewHolder>( + itemView: View + ) : MappingViewHolder(itemView) { + private val conversationListItem: ConversationListItem = itemView as ConversationListItem + + override fun bind(model: M) { + if (payload.contains(PAYLOAD_TIMESTAMP)) { + conversationListItem.updateTimestamp() + return + } + + fullBind(model) + } + + abstract fun fullBind(model: M) + } + private class EmptyViewHolder( itemView: View ) : MappingViewHolder(itemView) { @@ -69,6 +94,10 @@ class ConversationListSearchAdapter( private val noResults = itemView.findViewById(R.id.search_no_results) override fun bind(model: EmptyModel) { + if (payload.isNotEmpty()) { + return + } + noResults.text = context.getString(R.string.SearchFragment_no_results, model.empty.query ?: "") } } @@ -78,8 +107,8 @@ class ConversationListSearchAdapter( private val lifecycleOwner: LifecycleOwner, private val glideRequests: GlideRequests, itemView: View - ) : MappingViewHolder(itemView) { - override fun bind(model: ThreadModel) { + ) : ConversationListItemViewHolder(itemView) { + override fun fullBind(model: ThreadModel) { itemView.setOnClickListener { threadListener.onClicked(itemView, model.thread, false) } @@ -101,8 +130,8 @@ class ConversationListSearchAdapter( private val lifecycleOwner: LifecycleOwner, private val glideRequests: GlideRequests, itemView: View - ) : MappingViewHolder(itemView) { - override fun bind(model: MessageModel) { + ) : ConversationListItemViewHolder(itemView) { + override fun fullBind(model: MessageModel) { itemView.setOnClickListener { messageListener.onClicked(itemView, model.message, false) } @@ -122,8 +151,8 @@ class ConversationListSearchAdapter( private val lifecycleOwner: LifecycleOwner, private val glideRequests: GlideRequests, itemView: View - ) : MappingViewHolder(itemView) { - override fun bind(model: GroupWithMembersModel) { + ) : ConversationListItemViewHolder(itemView) { + override fun fullBind(model: GroupWithMembersModel) { itemView.setOnClickListener { groupWithMembersListener.onClicked(itemView, model.groupWithMembers, false) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/TimestampPayloadSupport.kt b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/TimestampPayloadSupport.kt new file mode 100644 index 0000000000..921bb60c5d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/TimestampPayloadSupport.kt @@ -0,0 +1,14 @@ +/* + * Copyright 2023 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.conversationlist + +/** + * Generic interface for the adapters to support updating the + * timestamp in a given row as opposed to rebinding every item. + */ +interface TimestampPayloadSupport { + fun notifyTimestampPayloadUpdate() +}