From 0ce3eab3cdac01ea48a01e8145a0c769a2ee1a97 Mon Sep 17 00:00:00 2001 From: Michelle Tang Date: Tue, 31 Mar 2026 11:29:26 -0400 Subject: [PATCH] Fix scroll state of collapsed events. --- .../v2/items/V2ConversationItemShapeTest.kt | 4 +-- .../test/InternalConversationTestFragment.kt | 4 +-- .../securesms/BindableConversationItem.java | 4 +-- .../conversation/ConversationUpdateItem.java | 4 +-- .../edit/EmptyConversationAdapterListener.kt | 4 +-- .../conversation/v2/ConversationAdapterV2.kt | 4 +++ .../conversation/v2/ConversationFragment.kt | 25 +++++++++++++++++-- .../messagedetails/MessageDetailsFragment.kt | 4 +-- .../starred/StarredMessagesActivity.kt | 4 +-- 9 files changed, 41 insertions(+), 16 deletions(-) diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShapeTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShapeTest.kt index 5862b7ad0b..1de15ca866 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShapeTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShapeTest.kt @@ -359,8 +359,8 @@ class V2ConversationItemShapeTest { override fun onViewPinnedMessage(messageId: Long) = Unit - override fun onExpandEvents(messageId: Long) = Unit + override fun onExpandEvents(messageId: Long, itemView: View, collapsedSize: Int) = Unit - override fun onCollapseEvents(messageId: Long) = Unit + override fun onCollapseEvents(messageId: Long, itemView: View, collapsedSize: Int) = Unit } } diff --git a/app/src/debug/java/org/thoughtcrime/securesms/components/settings/app/internal/conversation/test/InternalConversationTestFragment.kt b/app/src/debug/java/org/thoughtcrime/securesms/components/settings/app/internal/conversation/test/InternalConversationTestFragment.kt index 12482957a5..3ccc420da5 100644 --- a/app/src/debug/java/org/thoughtcrime/securesms/components/settings/app/internal/conversation/test/InternalConversationTestFragment.kt +++ b/app/src/debug/java/org/thoughtcrime/securesms/components/settings/app/internal/conversation/test/InternalConversationTestFragment.kt @@ -354,11 +354,11 @@ class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fra Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show() } - override fun onExpandEvents(messageId: Long) { + override fun onExpandEvents(messageId: Long, itemView: View, collapsedSize: Int) { Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show() } - override fun onCollapseEvents(messageId: Long) { + override fun onCollapseEvents(messageId: Long, itemView: View, collapsedSize: Int) { Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java index 2d31750fa1..fc8575f301 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java @@ -148,7 +148,7 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable, void onViewPollClicked(long messageId); void onToggleVote(@NonNull PollRecord poll, @NonNull PollOption pollOption, Boolean isChecked); void onViewPinnedMessage(long messageId); - void onExpandEvents(long messageId); - void onCollapseEvents(long messageId); + void onExpandEvents(long messageId, @NonNull View itemView, int collapsedSize); + void onCollapseEvents(long messageId, @NonNull View itemView, int collapsedSize); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java index e065e36340..0177992666 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java @@ -875,9 +875,9 @@ public final class ConversationUpdateItem extends FrameLayout collapsedButton.setOnClickListener(v -> { if (eventListener != null) { if (CollapsedState.isCollapsed(collapsedState)) { - eventListener.onExpandEvents(conversationMessage.getMessageRecord().getId()); + eventListener.onExpandEvents(conversationMessage.getMessageRecord().getId(), ConversationUpdateItem.this, conversationMessage.getCollapsedSize()); } else if (!anyCollapsibleChildrenSelected()) { - eventListener.onCollapseEvents(conversationMessage.getMessageRecord().getId()); + eventListener.onCollapseEvents(conversationMessage.getMessageRecord().getId(), ConversationUpdateItem.this, conversationMessage.getCollapsedSize()); } } else { passthroughClickListener.onClick(v); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/edit/EmptyConversationAdapterListener.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/edit/EmptyConversationAdapterListener.kt index 973f0034b9..e569a6ab45 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/edit/EmptyConversationAdapterListener.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/edit/EmptyConversationAdapterListener.kt @@ -94,6 +94,6 @@ object EmptyConversationAdapterListener : ConversationAdapter.ItemClickListener override fun onViewPollClicked(messageId: Long) = Unit override fun onToggleVote(poll: PollRecord, pollOption: PollOption, isChecked: Boolean?) = Unit override fun onViewPinnedMessage(messageId: Long) = Unit - override fun onExpandEvents(messageId: Long) = Unit - override fun onCollapseEvents(messageId: Long) = Unit + override fun onExpandEvents(messageId: Long, itemView: View, collapsedSize: Int) = Unit + override fun onCollapseEvents(messageId: Long, itemView: View, collapsedSize: Int) = Unit } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2.kt index 90b0010b88..4dca517a0e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2.kt @@ -387,6 +387,7 @@ class ConversationAdapterV2( return } + bindable.setParentScrolling(true) bindable.bind( lifecycleOwner, model.conversationMessage, @@ -404,6 +405,7 @@ class ConversationAdapterV2( colorizer, displayMode ) + bindable.setParentScrolling(isParentInScroll) } } @@ -415,6 +417,7 @@ class ConversationAdapterV2( return } + bindable.setParentScrolling(true) bindable.bind( lifecycleOwner, model.conversationMessage, @@ -432,6 +435,7 @@ class ConversationAdapterV2( colorizer, displayMode ) + bindable.setParentScrolling(isParentInScroll) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt index 7c39f4bd97..c71a2af94d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt @@ -587,6 +587,7 @@ class ConversationFragment : private var progressDialog: ProgressCardDialogFragment? = null private var firstPinRender: Boolean = true private var skipNextBackPressHandling: Boolean = false + private var collapsibleEventScrollPosition: CollapsibleEventScrollPosition? = null private val jumpAndPulseScrollStrategy = object : ScrollToPositionDelegate.ScrollStrategy { override fun performScroll(recyclerView: RecyclerView, layoutManager: LinearLayoutManager, position: Int, smooth: Boolean) { @@ -1147,6 +1148,13 @@ class ConversationFragment : doAfterFirstRender() } } + + if (collapsibleEventScrollPosition != null) { + val scrollState = collapsibleEventScrollPosition!! + val offset = binding.conversationItemRecycler.height - scrollState.top - scrollState.height + layoutManager.scrollToPositionWithOffset(scrollState.position, offset) + collapsibleEventScrollPosition = null + } } }) } @@ -3812,11 +3820,19 @@ class ConversationFragment : } } - override fun onExpandEvents(messageId: Long) { + override fun onExpandEvents(messageId: Long, itemView: View, collapsedSize: Int) { + val position = binding.conversationItemRecycler.getChildAdapterPosition(itemView) + if (position != RecyclerView.NO_POSITION && position != 0) { + collapsibleEventScrollPosition = CollapsibleEventScrollPosition(position = position + (collapsedSize - 1), top = itemView.top, height = itemView.height) + } viewModel.onExpandEvents(messageId) } - override fun onCollapseEvents(messageId: Long) { + override fun onCollapseEvents(messageId: Long, itemView: View, collapsedSize: Int) { + val position = binding.conversationItemRecycler.getChildAdapterPosition(itemView) + if (position != RecyclerView.NO_POSITION) { + collapsibleEventScrollPosition = CollapsibleEventScrollPosition(position = position - (collapsedSize - 1), top = itemView.top, height = itemView.height) + } viewModel.onCollapseEvents(messageId) } @@ -5241,4 +5257,9 @@ class ConversationFragment : override fun onDoubleTapEditEducationSheetNext(conversationMessage: ConversationMessage) { handleEditMessage(conversationMessage) } + + /** + * Tracks the scroll position so that after collapsing/expanding, we can restore it properly + */ + private data class CollapsibleEventScrollPosition(val position: Int, val top: Int, val height: Int) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageDetailsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageDetailsFragment.kt index 5173961fea..16e1438ce1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageDetailsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageDetailsFragment.kt @@ -417,11 +417,11 @@ class MessageDetailsFragment : Fragment(), MessageDetailsAdapter.Callbacks { Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show() } - override fun onExpandEvents(messageId: Long) { + override fun onExpandEvents(messageId: Long, itemView: View, collapsedSize: Int) { Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show() } - override fun onCollapseEvents(messageId: Long) { + override fun onCollapseEvents(messageId: Long, itemView: View, collapsedSize: Int) { Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/starred/StarredMessagesActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/starred/StarredMessagesActivity.kt index 53db9f8748..54f7c002f6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/starred/StarredMessagesActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/starred/StarredMessagesActivity.kt @@ -421,6 +421,6 @@ private class StarredMessageClickListener( override fun onUpdateSignalClicked() = Unit override fun onViewPollClicked(messageId: Long) = Unit override fun onViewPinnedMessage(messageId: Long) = Unit - override fun onCollapseEvents(messageId: Long) = Unit - override fun onExpandEvents(messageId: Long) = Unit + override fun onCollapseEvents(messageId: Long, itemView: View, collapsedSize: Int) = Unit + override fun onExpandEvents(messageId: Long, itemView: View, collapsedSize: Int) = Unit }