Save search query to savedinstancestate.

This commit is contained in:
Alex Hart
2025-05-01 13:53:42 -03:00
committed by Cody Henthorne
parent 46ca979e59
commit 524ffd9d79
5 changed files with 96 additions and 14 deletions

View File

@@ -252,16 +252,23 @@ class MainActivity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner
)
)
if (SignalStore.internal.largeScreenUi) {
LaunchedEffect(scaffoldNavigator.currentDestination) {
if (scaffoldNavigator.currentDestination?.pane == ThreePaneScaffoldRole.Secondary) {
mainNavigationViewModel.goTo(MainNavigationDetailLocation.Empty)
}
}
}
LaunchedEffect(detailLocation) {
if (detailLocation is MainNavigationDetailLocation.Conversation) {
if (SignalStore.internal.largeScreenUi) {
scaffoldNavigator.navigateTo(ThreePaneScaffoldRole.Primary, detailLocation)
} else {
startActivity((detailLocation as MainNavigationDetailLocation.Conversation).intent)
mainNavigationViewModel.goTo(MainNavigationDetailLocation.Empty)
}
}
mainNavigationViewModel.goTo(MainNavigationDetailLocation.Empty)
}
AppScaffold(

View File

@@ -126,6 +126,8 @@ class ContactSearchMediator(
}
}
fun getFilter(): String? = viewModel.getQuery()
fun onConversationFilterRequestChanged(conversationFilterRequest: ConversationFilterRequest) {
viewModel.setConversationFilterRequest(conversationFilterRequest)
}

View File

@@ -1,9 +1,10 @@
package org.thoughtcrime.securesms.contacts.paged
import androidx.lifecycle.AbstractSavedStateViewModelFactory
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.map
import androidx.lifecycle.switchMap
import io.reactivex.rxjava3.core.Observable
@@ -26,6 +27,7 @@ import org.whispersystems.signalservice.api.util.Preconditions
* Simple, reusable view model that manages a ContactSearchPagedDataSource as well as filter and expansion state.
*/
class ContactSearchViewModel(
private val savedStateHandle: SavedStateHandle,
private val selectionLimits: SelectionLimits,
private val contactSearchRepository: ContactSearchRepository,
private val performSafetyNumberChecks: Boolean,
@@ -34,6 +36,10 @@ class ContactSearchViewModel(
private val contactSearchPagedDataSourceRepository: ContactSearchPagedDataSourceRepository
) : ViewModel() {
companion object {
private const val QUERY = "query"
}
private val safetyNumberRepository: SafetyNumberRepository by lazy { SafetyNumberRepository() }
private val disposables = CompositeDisposable()
@@ -45,7 +51,7 @@ class ContactSearchViewModel(
.build()
private val pagedData = MutableLiveData<LivePagedData<ContactSearchKey, ContactSearchData>>()
private val configurationStore = Store(ContactSearchState())
private val configurationStore = Store(ContactSearchState(query = savedStateHandle[QUERY]))
private val selectionStore = Store<Set<ContactSearchKey>>(emptySet())
private val errorEvents = PublishSubject.create<ContactSearchError>()
@@ -73,7 +79,10 @@ class ContactSearchViewModel(
pagedData.value = PagedData.createForLiveData(pagedDataSource, pagingConfig)
}
fun getQuery(): String? = savedStateHandle[QUERY]
fun setQuery(query: String?) {
savedStateHandle[QUERY] = query
configurationStore.update { it.copy(query = query) }
}
@@ -169,10 +178,11 @@ class ContactSearchViewModel(
private val arbitraryRepository: ArbitraryRepository?,
private val searchRepository: SearchRepository,
private val contactSearchPagedDataSourceRepository: ContactSearchPagedDataSourceRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
) : AbstractSavedStateViewModelFactory() {
override fun <T : ViewModel> create(key: String, modelClass: Class<T>, handle: SavedStateHandle): T {
return modelClass.cast(
ContactSearchViewModel(
savedStateHandle = handle,
selectionLimits = selectionLimits,
contactSearchRepository = repository,
performSafetyNumberChecks = performSafetyNumberChecks,

View File

@@ -389,6 +389,12 @@ public class ConversationListFragment extends MainFragment implements ActionMode
initializeVoiceNotePlayer();
initializeBanners();
maybeScheduleRefreshProfileJob();
ConversationListFragmentExtensionsKt.listenToEventBusWhileResumed(this, mainNavigationViewModel.getDetailLocation());
String query = contactSearchMediator.getFilter();
if (query != null) {
onSearchQueryUpdated(query);
}
RatingManager.showRatingDialogIfNecessary(requireContext());
@@ -473,7 +479,6 @@ public class ConversationListFragment extends MainFragment implements ActionMode
initializeSearchListener();
initializeFilterListener();
EventBus.getDefault().register(this);
itemAnimator.disable();
SpoilerAnnotation.resetRevealedSpoilers();
@@ -539,13 +544,6 @@ public class ConversationListFragment extends MainFragment implements ActionMode
itemAnimator.disable();
}
@Override
public void onPause() {
super.onPause();
EventBus.getDefault().unregister(this);
}
@Override
public void onStop() {
super.onStop();

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.conversationlist
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.eventFlow
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import org.thoughtcrime.securesms.main.MainNavigationDetailLocation
import org.thoughtcrime.securesms.window.WindowSizeClass.Companion.getWindowSizeClass
/**
* When the user searches for a conversation and then enters a message, we should clear
* the search. This is driven by an event bus, which we want to subscribe to only when
* the screen has been resumed.
*
* On COMPACT form factor specifically, we also need to wait until we are in the EMPTY
* detail location, to avoid weird predictive back animation issues.
*
* On other screen types, since we are in a multi-pane mode, we can subscribe immediately.
*/
fun Fragment.listenToEventBusWhileResumed(
detailLocation: Flow<MainNavigationDetailLocation>
) {
lifecycleScope.launch {
detailLocation
.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.RESUMED)
.collectLatest {
if (resources.getWindowSizeClass().isCompact()) {
when (it) {
is MainNavigationDetailLocation.Conversation -> unsubscribe()
MainNavigationDetailLocation.Empty -> subscribe()
}
} else {
subscribe()
}
}
}
lifecycleScope.launch {
lifecycle.eventFlow.filter { it == Lifecycle.Event.ON_PAUSE }
.collectLatest { unsubscribe() }
}
}
private fun Fragment.subscribe() {
if (!EventBus.getDefault().isRegistered(this)) {
EventBus.getDefault().register(this)
}
}
private fun Fragment.unsubscribe() {
if (EventBus.getDefault().isRegistered(this)) {
EventBus.getDefault().unregister(this)
}
}