From cd5a3768ebbf502eea144f47b08be2639d997788 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Wed, 15 Mar 2023 13:21:54 -0300 Subject: [PATCH] Fix back handling between tabs. --- .../securesms/calls/log/CallLogFragment.kt | 45 +++++++++++++++++ .../main/MainActivityListHostFragment.kt | 14 ++---- .../tabs/ConversationListTabsFragment.kt | 50 ++++++++++++++----- .../res/layout/conversation_list_tabs.xml | 12 ----- .../res/navigation/main_activity_list.xml | 12 ++++- 5 files changed, 96 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogFragment.kt index ca3165250f..115cdb8c9e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/log/CallLogFragment.kt @@ -6,13 +6,16 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View +import androidx.activity.OnBackPressedCallback import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.view.MenuProvider import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.appbar.AppBarLayout import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.kotlin.Observables +import io.reactivex.rxjava3.kotlin.subscribeBy import org.signal.core.util.DimensionUnit import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.Material3SearchToolbar @@ -27,6 +30,8 @@ import org.thoughtcrime.securesms.conversationlist.chatfilter.FilterLerp import org.thoughtcrime.securesms.conversationlist.chatfilter.FilterPullState import org.thoughtcrime.securesms.databinding.CallLogFragmentBinding import org.thoughtcrime.securesms.main.SearchBinder +import org.thoughtcrime.securesms.stories.tabs.ConversationListTab +import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsViewModel import org.thoughtcrime.securesms.util.LifecycleDisposable import org.thoughtcrime.securesms.util.ViewUtil import org.thoughtcrime.securesms.util.fragments.requireListener @@ -38,6 +43,10 @@ import java.util.Objects @SuppressLint("DiscouragedApi") class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Callbacks, CallLogContextMenu.Callbacks { + companion object { + private const val LIST_SMOOTH_SCROLL_TO_TOP_THRESHOLD = 25 + } + private val viewModel: CallLogViewModel by viewModels() private val binding: CallLogFragmentBinding by ViewBinderDelegate(CallLogFragmentBinding::bind) private val disposables = LifecycleDisposable() @@ -49,6 +58,8 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal } ) + private val tabsViewModel: ConversationListTabsViewModel by viewModels(ownerProducer = { requireActivity() }) + private val menuProvider = object : MenuProvider { override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { menuInflater.inflate(R.menu.calls_tab_menu, menu) @@ -103,6 +114,18 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal binding.recycler.adapter = adapter initializePullToFilter() + initializeTapToScrollToTop() + + requireActivity().onBackPressedDispatcher.addCallback( + viewLifecycleOwner, + object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + if (!closeSearchIfOpen()) { + tabsViewModel.onChatsSelected() + } + } + } + ) } override fun onResume() { @@ -110,6 +133,19 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal initializeSearchAction() } + private fun initializeTapToScrollToTop() { + disposables += tabsViewModel.tabClickEvents + .filter { it == ConversationListTab.CALLS } + .subscribeBy(onNext = { + val layoutManager = binding.recycler.layoutManager as? LinearLayoutManager ?: return@subscribeBy + if (layoutManager.findFirstVisibleItemPosition() <= LIST_SMOOTH_SCROLL_TO_TOP_THRESHOLD) { + binding.recycler.smoothScrollToPosition(0) + } else { + binding.recycler.scrollToPosition(0) + } + }) + } + private fun initializeSearchAction() { val searchBinder = requireListener() searchBinder.getSearchAction().setOnClickListener { @@ -201,6 +237,15 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal return isSearchVisible() || viewModel.hasSearchQuery } + private fun closeSearchIfOpen(): Boolean { + if (isSearchOpen()) { + requireListener().getSearchToolbar().get().collapse() + requireListener().onSearchClosed() + return true + } + return false + } + private fun isSearchVisible(): Boolean { return requireListener().getSearchToolbar().resolved() && requireListener().getSearchToolbar().get().getVisibility() == View.VISIBLE diff --git a/app/src/main/java/org/thoughtcrime/securesms/main/MainActivityListHostFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/main/MainActivityListHostFragment.kt index 7f0f324c10..e8bf262be3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/main/MainActivityListHostFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/main/MainActivityListHostFragment.kt @@ -142,22 +142,16 @@ class MainActivityListHostFragment : Fragment(R.layout.main_activity_list_host_f private fun goToStateFromCalling(state: ConversationListTabsState, navController: NavController) { when (state.tab) { ConversationListTab.CALLS -> return - ConversationListTab.CHATS -> navController.popBackStack() - ConversationListTab.STORIES -> { - navController.popBackStack() - goToStateFromConversationList(state, navController) - } + ConversationListTab.CHATS -> navController.popBackStack(R.id.conversationListFragment, false) + ConversationListTab.STORIES -> navController.navigate(R.id.action_callLogFragment_to_storiesLandingFragment) } } private fun goToStateFromStories(state: ConversationListTabsState, navController: NavController) { when (state.tab) { ConversationListTab.STORIES -> return - ConversationListTab.CHATS -> navController.popBackStack() - ConversationListTab.CALLS -> { - navController.popBackStack() - goToStateFromConversationList(state, navController) - } + ConversationListTab.CHATS -> navController.popBackStack(R.id.conversationListFragment, false) + ConversationListTab.CALLS -> navController.navigate(R.id.action_storiesLandingFragment_to_callLogFragment) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/tabs/ConversationListTabsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/tabs/ConversationListTabsFragment.kt index 90d54dd217..6fb857c69c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/tabs/ConversationListTabsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/tabs/ConversationListTabsFragment.kt @@ -61,7 +61,18 @@ class ConversationListTabsFragment : Fragment(R.layout.conversation_list_tabs) { viewModel.onStoriesSelected() } - binding.callsTabGroup.visible = FeatureFlags.callsTab() + if (!FeatureFlags.callsTab()) { + listOf( + binding.callsPill, + binding.callsTabIcon, + binding.callsTabContainer, + binding.callsTabLabel, + binding.callsUnreadIndicator, + binding.callsTabTouchPoint + ).forEach { + it.visible = false + } + } viewModel.state.observe(viewLifecycleOwner) { update(it, shouldBeImmediate) @@ -73,8 +84,10 @@ class ConversationListTabsFragment : Fragment(R.layout.conversation_list_tabs) { binding.chatsTabIcon.isSelected = state.tab == ConversationListTab.CHATS binding.chatsPill.isSelected = state.tab == ConversationListTab.CHATS - binding.callsTabIcon.isSelected = state.tab == ConversationListTab.CALLS - binding.callsPill.isSelected = state.tab == ConversationListTab.CALLS + if (FeatureFlags.callsTab()) { + binding.callsTabIcon.isSelected = state.tab == ConversationListTab.CALLS + binding.callsPill.isSelected = state.tab == ConversationListTab.CALLS + } binding.storiesTabIcon.isSelected = state.tab == ConversationListTab.STORIES binding.storiesPill.isSelected = state.tab == ConversationListTab.STORIES @@ -82,28 +95,39 @@ class ConversationListTabsFragment : Fragment(R.layout.conversation_list_tabs) { val hasStateChange = state.tab != state.prevTab if (immediate) { binding.chatsTabIcon.pauseAnimation() - binding.callsTabIcon.pauseAnimation() binding.storiesTabIcon.pauseAnimation() binding.chatsTabIcon.progress = if (state.tab == ConversationListTab.CHATS) 1f else 0f - binding.callsTabIcon.progress = if (state.tab == ConversationListTab.CALLS) 1f else 0f binding.storiesTabIcon.progress = if (state.tab == ConversationListTab.STORIES) 1f else 0f - runPillAnimation(0, binding.chatsPill, binding.callsPill, binding.storiesPill) + if (FeatureFlags.callsTab()) { + binding.callsTabIcon.pauseAnimation() + binding.callsTabIcon.progress = if (state.tab == ConversationListTab.CALLS) 1f else 0f + runPillAnimation(0, binding.callsPill, binding.chatsPill, binding.storiesPill) + } else { + runPillAnimation(0, binding.chatsPill, binding.storiesPill) + } } else if (hasStateChange) { - runLottieAnimations(binding.chatsTabIcon, binding.callsTabIcon, binding.storiesTabIcon) - runPillAnimation(150, binding.chatsPill, binding.callsPill, binding.storiesPill) + if (FeatureFlags.callsTab()) { + runLottieAnimations(binding.callsTabIcon, binding.chatsTabIcon, binding.storiesTabIcon) + runPillAnimation(150, binding.callsPill, binding.chatsPill, binding.storiesPill) + } else { + runLottieAnimations(binding.chatsTabIcon, binding.storiesTabIcon) + runPillAnimation(150, binding.chatsPill, binding.storiesPill) + } } - binding.chatsUnreadIndicator.alpha = if (state.unreadMessagesCount > 0) 1f else 0f + binding.chatsUnreadIndicator.visible = state.unreadMessagesCount > 0 binding.chatsUnreadIndicator.text = formatCount(state.unreadMessagesCount) - binding.callsUnreadIndicator.alpha = if (state.unreadCallsCount > 0) 1f else 0f - binding.callsUnreadIndicator.text = formatCount(state.unreadCallsCount) - - binding.storiesUnreadIndicator.alpha = if (state.unreadStoriesCount > 0) 1f else 0f + binding.storiesUnreadIndicator.visible = state.unreadStoriesCount > 0 binding.storiesUnreadIndicator.text = formatCount(state.unreadStoriesCount) + if (FeatureFlags.callsTab()) { + binding.callsUnreadIndicator.visible = state.unreadCallsCount > 0 + binding.callsUnreadIndicator.text = formatCount(state.unreadCallsCount) + } + requireView().visible = state.visibilityState.isVisible() } diff --git a/app/src/main/res/layout/conversation_list_tabs.xml b/app/src/main/res/layout/conversation_list_tabs.xml index ef197d349c..9475fd3669 100644 --- a/app/src/main/res/layout/conversation_list_tabs.xml +++ b/app/src/main/res/layout/conversation_list_tabs.xml @@ -230,16 +230,4 @@ app:layout_constraintTop_toTopOf="parent" tools:text="99+" /> - - - - \ No newline at end of file diff --git a/app/src/main/res/navigation/main_activity_list.xml b/app/src/main/res/navigation/main_activity_list.xml index 3e5ceabe1a..469e332fef 100644 --- a/app/src/main/res/navigation/main_activity_list.xml +++ b/app/src/main/res/navigation/main_activity_list.xml @@ -31,11 +31,19 @@ + android:label="stories_landing_fragment" > + + + android:label="call_log_fragment" > + + \ No newline at end of file