Fix back handling between tabs.

This commit is contained in:
Alex Hart
2023-03-15 13:21:54 -03:00
committed by Greyson Parrelli
parent cf64f06c36
commit cd5a3768eb
5 changed files with 96 additions and 37 deletions

View File

@@ -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>()
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<SearchBinder>().getSearchToolbar().get().collapse()
requireListener<SearchBinder>().onSearchClosed()
return true
}
return false
}
private fun isSearchVisible(): Boolean {
return requireListener<SearchBinder>().getSearchToolbar().resolved() &&
requireListener<SearchBinder>().getSearchToolbar().get().getVisibility() == View.VISIBLE

View File

@@ -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)
}
}

View File

@@ -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()
}

View File

@@ -230,16 +230,4 @@
app:layout_constraintTop_toTopOf="parent"
tools:text="99+" />
<androidx.constraintlayout.widget.Group
android:id="@+id/calls_tab_group"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:constraint_referenced_ids="calls_pill,calls_tab_container,calls_tab_icon,calls_tab_label,calls_tab_touch_point,calls_unread_indicator" />
<androidx.constraintlayout.widget.Group
android:id="@+id/stories_tab_group"
app:constraint_referenced_ids="stories_pill,stories_tab_container,stories_tab_icon,stories_tab_label,stories_tab_touch_point,stories_unread_indicator"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -31,11 +31,19 @@
<fragment
android:id="@+id/storiesLandingFragment"
android:name="org.thoughtcrime.securesms.stories.landing.StoriesLandingFragment"
android:label="stories_landing_fragment" />
android:label="stories_landing_fragment" >
<action
android:id="@+id/action_storiesLandingFragment_to_callLogFragment"
app:destination="@id/callLogFragment" />
</fragment>
<fragment
android:id="@+id/callLogFragment"
android:name="org.thoughtcrime.securesms.calls.log.CallLogFragment"
android:label="call_log_fragment" />
android:label="call_log_fragment" >
<action
android:id="@+id/action_callLogFragment_to_storiesLandingFragment"
app:destination="@id/storiesLandingFragment" />
</fragment>
</navigation>