From 77f8489e51faa911d5ec116fa7ed694a751d5042 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Mon, 16 May 2022 16:15:07 -0300 Subject: [PATCH] Scroll to top on chat press when already on that tab. --- .../ConversationListFragment.java | 22 +++++++++++++++++++ .../stories/landing/StoriesLandingFragment.kt | 17 ++++++++++++++ .../tabs/ConversationListTabsViewModel.kt | 8 +++++++ 3 files changed, 47 insertions(+) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java index 48fbb337d..18516d86c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListFragment.java @@ -137,11 +137,14 @@ import org.thoughtcrime.securesms.search.SearchResult; import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.storage.StorageSyncHelper; +import org.thoughtcrime.securesms.stories.tabs.ConversationListTab; +import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsViewModel; import org.thoughtcrime.securesms.util.AppForegroundObserver; import org.thoughtcrime.securesms.util.AppStartup; import org.thoughtcrime.securesms.util.BottomSheetUtil; import org.thoughtcrime.securesms.util.ConversationUtil; import org.thoughtcrime.securesms.util.FeatureFlags; +import org.thoughtcrime.securesms.util.LifecycleDisposable; import org.thoughtcrime.securesms.util.PlayStoreUtil; import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.SignalLocalMetrics; @@ -185,6 +188,8 @@ public class ConversationListFragment extends MainFragment implements ActionMode public static final short MESSAGE_REQUESTS_REQUEST_CODE_CREATE_NAME = 32562; public static final short SMS_ROLE_REQUEST_CODE = 32563; + private static final int LIST_SMOOTH_SCROLL_TO_TOP_THRESHOLD = 25; + private static final String TAG = Log.tag(ConversationListFragment.class); private static final int MAXIMUM_PINNED_CONVERSATIONS = 4; @@ -213,10 +218,12 @@ public class ConversationListFragment extends MainFragment implements ActionMode private VoiceNotePlayerView voiceNotePlayerView; private SignalBottomActionBar bottomActionBar; private SignalContextMenu activeContextMenu; + private LifecycleDisposable lifecycleDisposable; protected ConversationListArchiveItemDecoration archiveDecoration; protected ConversationListItemAnimator itemAnimator; private Stopwatch startupStopwatch; + private ConversationListTabsViewModel conversationListTabsViewModel; public static ConversationListFragment newInstance() { return new ConversationListFragment(); @@ -317,6 +324,21 @@ public class ConversationListFragment extends MainFragment implements ActionMode } } }); + + lifecycleDisposable = new LifecycleDisposable(); + conversationListTabsViewModel = new ViewModelProvider(requireActivity()).get(ConversationListTabsViewModel.class); + + lifecycleDisposable.bindTo(getViewLifecycleOwner()); + lifecycleDisposable.add(conversationListTabsViewModel.getTabClickEvents().filter(tab -> tab == ConversationListTab.CHATS) + .subscribe(unused -> { + LinearLayoutManager layoutManager = (LinearLayoutManager) list.getLayoutManager(); + int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition(); + if (firstVisibleItemPosition <= LIST_SMOOTH_SCROLL_TO_TOP_THRESHOLD) { + list.smoothScrollToPosition(0); + } else { + list.scrollToPosition(0); + } + })); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingFragment.kt index 2c7db44d5..795ab139e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/landing/StoriesLandingFragment.kt @@ -16,6 +16,7 @@ import androidx.core.app.ActivityOptionsCompat import androidx.core.app.SharedElementCallback import androidx.core.view.ViewCompat import androidx.fragment.app.viewModels +import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.snackbar.BaseTransientBottomBar @@ -43,6 +44,7 @@ import org.thoughtcrime.securesms.stories.dialogs.StoryContextMenu import org.thoughtcrime.securesms.stories.dialogs.StoryDialogs import org.thoughtcrime.securesms.stories.my.MyStoriesActivity import org.thoughtcrime.securesms.stories.settings.StorySettingsActivity +import org.thoughtcrime.securesms.stories.tabs.ConversationListTab import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsViewModel import org.thoughtcrime.securesms.stories.viewer.StoryViewerActivity import org.thoughtcrime.securesms.util.LifecycleDisposable @@ -54,6 +56,10 @@ import java.util.concurrent.TimeUnit */ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_landing_fragment) { + companion object { + private const val LIST_SMOOTH_SCROLL_TO_TOP_THRESHOLD = 25 + } + private lateinit var emptyNotice: View private lateinit var cameraFab: FloatingActionButton @@ -137,6 +143,17 @@ class StoriesLandingFragment : DSLSettingsFragment(layoutId = R.layout.stories_l } } ) + + lifecycleDisposable += tabsViewModel.tabClickEvents + .filter { it == ConversationListTab.STORIES } + .subscribeBy(onNext = { + val layoutManager = recyclerView?.layoutManager as? LinearLayoutManager ?: return@subscribeBy + if (layoutManager.findFirstVisibleItemPosition() <= LIST_SMOOTH_SCROLL_TO_TOP_THRESHOLD) { + recyclerView?.smoothScrollToPosition(0) + } else { + recyclerView?.scrollToPosition(0) + } + }) } private fun getConfiguration(state: StoriesLandingState): DSLConfiguration { diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/tabs/ConversationListTabsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/tabs/ConversationListTabsViewModel.kt index b6dbc05fc..e8a0f8a13 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/tabs/ConversationListTabsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/tabs/ConversationListTabsViewModel.kt @@ -3,8 +3,11 @@ package org.thoughtcrime.securesms.stories.tabs import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign +import io.reactivex.rxjava3.subjects.PublishSubject +import io.reactivex.rxjava3.subjects.Subject import org.thoughtcrime.securesms.util.livedata.Store class ConversationListTabsViewModel(repository: ConversationListTabRepository) : ViewModel() { @@ -14,6 +17,9 @@ class ConversationListTabsViewModel(repository: ConversationListTabRepository) : val state: LiveData = store.stateLiveData val disposables = CompositeDisposable() + private val internalTabClickEvents: Subject = PublishSubject.create() + val tabClickEvents: Observable = internalTabClickEvents + init { disposables += repository.getNumberOfUnreadConversations().subscribe { unreadChats -> store.update { it.copy(unreadChatsCount = unreadChats) } @@ -29,10 +35,12 @@ class ConversationListTabsViewModel(repository: ConversationListTabRepository) : } fun onChatsSelected() { + internalTabClickEvents.onNext(ConversationListTab.CHATS) store.update { it.copy(tab = ConversationListTab.CHATS) } } fun onStoriesSelected() { + internalTabClickEvents.onNext(ConversationListTab.STORIES) store.update { it.copy(tab = ConversationListTab.STORIES) } }