diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyFragment.kt index 72b716ee4..befe66157 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/group/StoryGroupReplyFragment.kt @@ -44,6 +44,7 @@ import org.thoughtcrime.securesms.util.DeleteDialog import org.thoughtcrime.securesms.util.FragmentDialogs.displayInDialogAboveAnchor import org.thoughtcrime.securesms.util.LifecycleDisposable import org.thoughtcrime.securesms.util.ServiceUtil +import org.thoughtcrime.securesms.util.SnapToTopDataObserver import org.thoughtcrime.securesms.util.adapter.mapping.PagingMappingAdapter import org.thoughtcrime.securesms.util.fragments.findListener import org.thoughtcrime.securesms.util.fragments.requireListener @@ -97,6 +98,8 @@ class StoryGroupReplyFragment : get() = requireArguments().getParcelable(ARG_GROUP_RECIPIENT_ID)!! private lateinit var recyclerView: RecyclerView + private lateinit var adapter: PagingMappingAdapter + private lateinit var snapToTopDataObserver: SnapToTopDataObserver private lateinit var composer: StoryReplyComposer private var currentChild: StoryViewsAndRepliesPagerParent.Child? = null @@ -112,7 +115,7 @@ class StoryGroupReplyFragment : val emptyNotice: View = requireView().findViewById(R.id.empty_notice) - val adapter = PagingMappingAdapter() + adapter = PagingMappingAdapter() val layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, true) recyclerView.layoutManager = layoutManager recyclerView.adapter = adapter @@ -133,15 +136,24 @@ class StoryGroupReplyFragment : } viewModel.pageData.observe(viewLifecycleOwner) { pageData -> - adapter.submitList(getConfiguration(pageData).toMappingModelList()) { - recyclerView.post { - if (recyclerView.canScrollVertically(1)) { - recyclerView.smoothScrollToPosition(0) - } - } - } + adapter.submitList(getConfiguration(pageData).toMappingModelList()) } + snapToTopDataObserver = SnapToTopDataObserver( + recyclerView, + object : SnapToTopDataObserver.ScrollRequestValidator { + override fun isPositionStillValid(position: Int): Boolean { + return position >= 0 && position < adapter.itemCount + } + + override fun isItemAtPositionLoaded(position: Int): Boolean { + return adapter.hasItem(position) + } + }, + null + ) + adapter.registerAdapterDataObserver(snapToTopDataObserver) + initializeMentions() } @@ -364,6 +376,9 @@ class StoryGroupReplyFragment : Toast.makeText(context, R.string.message_details_recipient__failed_to_send, Toast.LENGTH_SHORT).show() } } + }, + onComplete = { + snapToTopDataObserver.requestScrollPosition(0) } ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/adapter/mapping/PagingMappingAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/util/adapter/mapping/PagingMappingAdapter.java index a162474e9..3b624dabe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/adapter/mapping/PagingMappingAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/adapter/mapping/PagingMappingAdapter.java @@ -55,6 +55,10 @@ public class PagingMappingAdapter extends MappingAdapter { throw new AssertionError("No view holder factory for type: " + item.getClass()); } + public boolean hasItem(int position) { + return getItem(position) != null; + } + private static class Placeholder implements MappingModel { @Override public boolean areItemsTheSame(@NonNull Placeholder newItem) { diff --git a/app/src/main/res/layout/stories_group_text_reply_item.xml b/app/src/main/res/layout/stories_group_text_reply_item.xml index f6eb29304..3a9f92226 100644 --- a/app/src/main/res/layout/stories_group_text_reply_item.xml +++ b/app/src/main/res/layout/stories_group_text_reply_item.xml @@ -38,6 +38,7 @@ android:layout_height="wrap_content" android:layout_marginStart="12dp" android:layout_marginTop="7dp" + android:layout_marginEnd="12dp" android:textAppearance="@style/TextAppearance.Signal.Subtitle.Bold" app:layout_constrainedWidth="true" app:layout_constraintEnd_toEndOf="parent"