mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-23 12:38:33 +00:00
Fix back handling between tabs.
This commit is contained in:
committed by
Greyson Parrelli
parent
cf64f06c36
commit
cd5a3768eb
@@ -6,13 +6,16 @@ import android.view.Menu
|
|||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.activity.OnBackPressedCallback
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
import androidx.core.view.MenuProvider
|
import androidx.core.view.MenuProvider
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.rxjava3.kotlin.Observables
|
import io.reactivex.rxjava3.kotlin.Observables
|
||||||
|
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||||
import org.signal.core.util.DimensionUnit
|
import org.signal.core.util.DimensionUnit
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.components.Material3SearchToolbar
|
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.conversationlist.chatfilter.FilterPullState
|
||||||
import org.thoughtcrime.securesms.databinding.CallLogFragmentBinding
|
import org.thoughtcrime.securesms.databinding.CallLogFragmentBinding
|
||||||
import org.thoughtcrime.securesms.main.SearchBinder
|
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.LifecycleDisposable
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil
|
import org.thoughtcrime.securesms.util.ViewUtil
|
||||||
import org.thoughtcrime.securesms.util.fragments.requireListener
|
import org.thoughtcrime.securesms.util.fragments.requireListener
|
||||||
@@ -38,6 +43,10 @@ import java.util.Objects
|
|||||||
@SuppressLint("DiscouragedApi")
|
@SuppressLint("DiscouragedApi")
|
||||||
class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Callbacks, CallLogContextMenu.Callbacks {
|
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 viewModel: CallLogViewModel by viewModels()
|
||||||
private val binding: CallLogFragmentBinding by ViewBinderDelegate(CallLogFragmentBinding::bind)
|
private val binding: CallLogFragmentBinding by ViewBinderDelegate(CallLogFragmentBinding::bind)
|
||||||
private val disposables = LifecycleDisposable()
|
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 {
|
private val menuProvider = object : MenuProvider {
|
||||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||||
menuInflater.inflate(R.menu.calls_tab_menu, menu)
|
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
|
binding.recycler.adapter = adapter
|
||||||
|
|
||||||
initializePullToFilter()
|
initializePullToFilter()
|
||||||
|
initializeTapToScrollToTop()
|
||||||
|
|
||||||
|
requireActivity().onBackPressedDispatcher.addCallback(
|
||||||
|
viewLifecycleOwner,
|
||||||
|
object : OnBackPressedCallback(true) {
|
||||||
|
override fun handleOnBackPressed() {
|
||||||
|
if (!closeSearchIfOpen()) {
|
||||||
|
tabsViewModel.onChatsSelected()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
@@ -110,6 +133,19 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
|
|||||||
initializeSearchAction()
|
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() {
|
private fun initializeSearchAction() {
|
||||||
val searchBinder = requireListener<SearchBinder>()
|
val searchBinder = requireListener<SearchBinder>()
|
||||||
searchBinder.getSearchAction().setOnClickListener {
|
searchBinder.getSearchAction().setOnClickListener {
|
||||||
@@ -201,6 +237,15 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
|
|||||||
return isSearchVisible() || viewModel.hasSearchQuery
|
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 {
|
private fun isSearchVisible(): Boolean {
|
||||||
return requireListener<SearchBinder>().getSearchToolbar().resolved() &&
|
return requireListener<SearchBinder>().getSearchToolbar().resolved() &&
|
||||||
requireListener<SearchBinder>().getSearchToolbar().get().getVisibility() == View.VISIBLE
|
requireListener<SearchBinder>().getSearchToolbar().get().getVisibility() == View.VISIBLE
|
||||||
|
|||||||
@@ -142,22 +142,16 @@ class MainActivityListHostFragment : Fragment(R.layout.main_activity_list_host_f
|
|||||||
private fun goToStateFromCalling(state: ConversationListTabsState, navController: NavController) {
|
private fun goToStateFromCalling(state: ConversationListTabsState, navController: NavController) {
|
||||||
when (state.tab) {
|
when (state.tab) {
|
||||||
ConversationListTab.CALLS -> return
|
ConversationListTab.CALLS -> return
|
||||||
ConversationListTab.CHATS -> navController.popBackStack()
|
ConversationListTab.CHATS -> navController.popBackStack(R.id.conversationListFragment, false)
|
||||||
ConversationListTab.STORIES -> {
|
ConversationListTab.STORIES -> navController.navigate(R.id.action_callLogFragment_to_storiesLandingFragment)
|
||||||
navController.popBackStack()
|
|
||||||
goToStateFromConversationList(state, navController)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun goToStateFromStories(state: ConversationListTabsState, navController: NavController) {
|
private fun goToStateFromStories(state: ConversationListTabsState, navController: NavController) {
|
||||||
when (state.tab) {
|
when (state.tab) {
|
||||||
ConversationListTab.STORIES -> return
|
ConversationListTab.STORIES -> return
|
||||||
ConversationListTab.CHATS -> navController.popBackStack()
|
ConversationListTab.CHATS -> navController.popBackStack(R.id.conversationListFragment, false)
|
||||||
ConversationListTab.CALLS -> {
|
ConversationListTab.CALLS -> navController.navigate(R.id.action_storiesLandingFragment_to_callLogFragment)
|
||||||
navController.popBackStack()
|
|
||||||
goToStateFromConversationList(state, navController)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,18 @@ class ConversationListTabsFragment : Fragment(R.layout.conversation_list_tabs) {
|
|||||||
viewModel.onStoriesSelected()
|
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) {
|
viewModel.state.observe(viewLifecycleOwner) {
|
||||||
update(it, shouldBeImmediate)
|
update(it, shouldBeImmediate)
|
||||||
@@ -73,8 +84,10 @@ class ConversationListTabsFragment : Fragment(R.layout.conversation_list_tabs) {
|
|||||||
binding.chatsTabIcon.isSelected = state.tab == ConversationListTab.CHATS
|
binding.chatsTabIcon.isSelected = state.tab == ConversationListTab.CHATS
|
||||||
binding.chatsPill.isSelected = state.tab == ConversationListTab.CHATS
|
binding.chatsPill.isSelected = state.tab == ConversationListTab.CHATS
|
||||||
|
|
||||||
binding.callsTabIcon.isSelected = state.tab == ConversationListTab.CALLS
|
if (FeatureFlags.callsTab()) {
|
||||||
binding.callsPill.isSelected = state.tab == ConversationListTab.CALLS
|
binding.callsTabIcon.isSelected = state.tab == ConversationListTab.CALLS
|
||||||
|
binding.callsPill.isSelected = state.tab == ConversationListTab.CALLS
|
||||||
|
}
|
||||||
|
|
||||||
binding.storiesTabIcon.isSelected = state.tab == ConversationListTab.STORIES
|
binding.storiesTabIcon.isSelected = state.tab == ConversationListTab.STORIES
|
||||||
binding.storiesPill.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
|
val hasStateChange = state.tab != state.prevTab
|
||||||
if (immediate) {
|
if (immediate) {
|
||||||
binding.chatsTabIcon.pauseAnimation()
|
binding.chatsTabIcon.pauseAnimation()
|
||||||
binding.callsTabIcon.pauseAnimation()
|
|
||||||
binding.storiesTabIcon.pauseAnimation()
|
binding.storiesTabIcon.pauseAnimation()
|
||||||
|
|
||||||
binding.chatsTabIcon.progress = if (state.tab == ConversationListTab.CHATS) 1f else 0f
|
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
|
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) {
|
} else if (hasStateChange) {
|
||||||
runLottieAnimations(binding.chatsTabIcon, binding.callsTabIcon, binding.storiesTabIcon)
|
if (FeatureFlags.callsTab()) {
|
||||||
runPillAnimation(150, binding.chatsPill, binding.callsPill, binding.storiesPill)
|
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.chatsUnreadIndicator.text = formatCount(state.unreadMessagesCount)
|
||||||
|
|
||||||
binding.callsUnreadIndicator.alpha = if (state.unreadCallsCount > 0) 1f else 0f
|
binding.storiesUnreadIndicator.visible = state.unreadStoriesCount > 0
|
||||||
binding.callsUnreadIndicator.text = formatCount(state.unreadCallsCount)
|
|
||||||
|
|
||||||
binding.storiesUnreadIndicator.alpha = if (state.unreadStoriesCount > 0) 1f else 0f
|
|
||||||
binding.storiesUnreadIndicator.text = formatCount(state.unreadStoriesCount)
|
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()
|
requireView().visible = state.visibilityState.isVisible()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -230,16 +230,4 @@
|
|||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:text="99+" />
|
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>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -31,11 +31,19 @@
|
|||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/storiesLandingFragment"
|
android:id="@+id/storiesLandingFragment"
|
||||||
android:name="org.thoughtcrime.securesms.stories.landing.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
|
<fragment
|
||||||
android:id="@+id/callLogFragment"
|
android:id="@+id/callLogFragment"
|
||||||
android:name="org.thoughtcrime.securesms.calls.log.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>
|
</navigation>
|
||||||
Reference in New Issue
Block a user