[fenix] SearchDialogFragment: Start to split consumeFrom() into independent observers that trigger less often

pull/600/head
Sebastian Kaspari 3 years ago committed by mergify[bot]
parent 3e01e9df25
commit e487bc6f59

@ -52,6 +52,7 @@ import mozilla.components.support.ktx.android.content.hasCamera
import mozilla.components.support.ktx.android.content.isPermissionGranted
import mozilla.components.support.ktx.android.content.res.getSpanned
import mozilla.components.support.ktx.android.view.hideKeyboard
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import mozilla.components.ui.autocomplete.InlineAutocompleteEditText
import org.mozilla.fenix.BrowserDirection
@ -66,6 +67,7 @@ import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.search.awesomebar.AwesomeBarView
import org.mozilla.fenix.search.awesomebar.toSearchProviderState
import org.mozilla.fenix.search.toolbar.ToolbarView
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.widget.VoiceSearchActivity
@ -82,7 +84,6 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
private lateinit var store: SearchDialogFragmentStore
private lateinit var toolbarView: ToolbarView
private lateinit var awesomeBarView: AwesomeBarView
private var firstUpdate = true
private val qrFeature = ViewBoundFeatureWrapper<QrFeature>()
private val speechIntent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
@ -229,8 +230,8 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
return binding.root
}
@ExperimentalCoroutinesApi
@SuppressWarnings("LongMethod")
@ExperimentalCoroutinesApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -340,26 +341,65 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
updateAccessibilityTraversalOrder()
}
observeClipboardState()
observeAwesomeBarState()
observeShortcutsState()
observeSuggestionProvidersState()
consumeFrom(store) {
/*
* firstUpdate is used to make sure we keep the awesomebar hidden on the first run
* of the searchFragmentDialog. We only turn it false after the user has changed the
* query as consumeFrom may run several times on fragment start due to state updates.
* */
if (it.url != it.query) firstUpdate = false
binding.awesomeBar.visibility = if (shouldShowAwesomebar(it)) View.VISIBLE else View.INVISIBLE
updateSearchSuggestionsHintVisibility(it)
updateClipboardSuggestion(it)
updateToolbarContentDescription(it)
updateSearchShortcutsIcon(it)
updateToolbarContentDescription(it.searchEngineSource)
toolbarView.update(it)
awesomeBarView.update(it)
addVoiceSearchButton(it)
}
}
private fun shouldShowAwesomebar(searchFragmentState: SearchFragmentState) =
!firstUpdate && searchFragmentState.query.isNotBlank() || searchFragmentState.showSearchShortcuts
@ExperimentalCoroutinesApi
private fun observeSuggestionProvidersState() = consumeFlow(store) { flow ->
flow.map { state -> state.toSearchProviderState() }
.ifChanged()
.collect { state -> awesomeBarView.updateSuggestionProvidersVisibility(state) }
}
@ExperimentalCoroutinesApi
private fun observeShortcutsState() = consumeFlow(store) { flow ->
flow.ifAnyChanged { state -> arrayOf(state.areShortcutsAvailable, state.showSearchShortcuts) }
.collect { state -> updateSearchShortcutsIcon(state.areShortcutsAvailable, state.showSearchShortcuts) }
}
@ExperimentalCoroutinesApi
private fun observeAwesomeBarState() = consumeFlow(store) { flow ->
/*
* firstUpdate is used to make sure we keep the awesomebar hidden on the first run
* of the searchFragmentDialog. We only turn it false after the user has changed the
* query as consumeFrom may run several times on fragment start due to state updates.
* */
flow.map { state -> state.url != state.query && state.query.isNotBlank() || state.showSearchShortcuts }
.ifChanged()
.collect { shouldShowAwesomebar ->
binding.awesomeBar.visibility = if (shouldShowAwesomebar) {
View.VISIBLE
} else {
View.INVISIBLE
}
}
}
@ExperimentalCoroutinesApi
private fun observeClipboardState() = consumeFlow(store) { flow ->
flow.map { state ->
val shouldShowView = state.showClipboardSuggestions &&
state.query.isEmpty() &&
!state.clipboardUrl.isNullOrEmpty() && !state.showSearchShortcuts
Pair(shouldShowView, state.clipboardUrl)
}
.ifChanged()
.collect { (shouldShowView, clipboardUrl) ->
updateClipboardSuggestion(shouldShowView, clipboardUrl)
}
}
private fun updateAccessibilityTraversalOrder() {
val searchWrapperId = binding.searchWrapper.id
@ -602,11 +642,10 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
private fun isSpeechAvailable(): Boolean = speechIntent.resolveActivity(requireContext().packageManager) != null
private fun updateClipboardSuggestion(searchState: SearchFragmentState) {
val shouldShowView = searchState.showClipboardSuggestions &&
searchState.query.isEmpty() &&
!searchState.clipboardUrl.isNullOrEmpty() && !searchState.showSearchShortcuts
private fun updateClipboardSuggestion(
shouldShowView: Boolean,
clipboardUrl: String?
) {
binding.fillLinkFromClipboard.isVisible = shouldShowView
binding.fillLinkDivider.isVisible = shouldShowView
binding.pillWrapperDivider.isVisible =
@ -615,32 +654,33 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
binding.clipboardTitle.isVisible = shouldShowView
binding.linkIcon.isVisible = shouldShowView
binding.clipboardUrl.text = searchState.clipboardUrl
binding.clipboardUrl.text = clipboardUrl
binding.fillLinkFromClipboard.contentDescription =
"${binding.clipboardTitle.text}, ${binding.clipboardUrl.text}."
if (searchState.clipboardUrl != null && !((activity as HomeActivity).browsingModeManager.mode.isPrivate)) {
requireComponents.core.engine.speculativeConnect(searchState.clipboardUrl)
if (clipboardUrl != null && !((activity as HomeActivity).browsingModeManager.mode.isPrivate)) {
requireComponents.core.engine.speculativeConnect(clipboardUrl)
}
}
private fun updateToolbarContentDescription(searchState: SearchFragmentState) {
private fun updateToolbarContentDescription(source: SearchEngineSource) {
val urlView = toolbarView.view
.findViewById<InlineAutocompleteEditText>(R.id.mozac_browser_toolbar_edit_url_view)
searchState.searchEngineSource.searchEngine?.let { engine ->
source.searchEngine?.let { engine ->
toolbarView.view.contentDescription = engine.name + ", " + urlView.hint
}
urlView?.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
}
private fun updateSearchShortcutsIcon(searchState: SearchFragmentState) {
private fun updateSearchShortcutsIcon(
areShortcutsAvailable: Boolean,
showShortcuts: Boolean
) {
view?.apply {
binding.searchEnginesShortcutButton.isVisible = searchState.areShortcutsAvailable
val showShortcuts = searchState.showSearchShortcuts
binding.searchEnginesShortcutButton.isVisible = areShortcutsAvailable
binding.searchEnginesShortcutButton.isChecked = showShortcuts
val color = if (showShortcuts) R.attr.contrastText else R.attr.primaryText

@ -202,8 +202,6 @@ class AwesomeBarView(
}
fun update(state: SearchFragmentState) {
updateSuggestionProvidersVisibility(state)
// Do not make suggestions based on user's current URL unless it's a search shortcut
if (state.query.isNotEmpty() && state.query == state.url && !state.showSearchShortcuts) {
return
@ -212,7 +210,9 @@ class AwesomeBarView(
view.onInputChanged(state.query)
}
private fun updateSuggestionProvidersVisibility(state: SearchFragmentState) {
fun updateSuggestionProvidersVisibility(
state: SearchProviderState
) {
if (state.showSearchShortcuts) {
handleDisplayShortcutsProviders()
return
@ -244,7 +244,9 @@ class AwesomeBarView(
}
@Suppress("ComplexMethod")
private fun getProvidersToAdd(state: SearchFragmentState): MutableSet<AwesomeBar.SuggestionProvider> {
private fun getProvidersToAdd(
state: SearchProviderState
): MutableSet<AwesomeBar.SuggestionProvider> {
val providersToAdd = mutableSetOf<AwesomeBar.SuggestionProvider>()
if (state.showHistorySuggestions) {
@ -276,7 +278,7 @@ class AwesomeBarView(
return providersToAdd
}
private fun getProvidersToRemove(state: SearchFragmentState): MutableSet<AwesomeBar.SuggestionProvider> {
private fun getProvidersToRemove(state: SearchProviderState): MutableSet<AwesomeBar.SuggestionProvider> {
val providersToRemove = mutableSetOf<AwesomeBar.SuggestionProvider>()
providersToRemove.add(shortcutsEnginePickerProvider)
@ -308,7 +310,7 @@ class AwesomeBarView(
return providersToRemove
}
private fun getSelectedSearchSuggestionProvider(state: SearchFragmentState): List<AwesomeBar.SuggestionProvider> {
private fun getSelectedSearchSuggestionProvider(state: SearchProviderState): List<AwesomeBar.SuggestionProvider> {
return when (state.searchEngineSource) {
is SearchEngineSource.Default -> listOf(
defaultSearchActionProvider,
@ -367,8 +369,26 @@ class AwesomeBarView(
}
}
data class SearchProviderState(
val showSearchShortcuts: Boolean,
val showHistorySuggestions: Boolean,
val showBookmarkSuggestions: Boolean,
val showSearchSuggestions: Boolean,
val showSyncedTabsSuggestions: Boolean,
val searchEngineSource: SearchEngineSource
)
companion object {
// Maximum number of suggestions returned from the history metadata storage.
const val METADATA_SUGGESTION_LIMIT = 3
}
}
fun SearchFragmentState.toSearchProviderState() = AwesomeBarView.SearchProviderState(
showSearchShortcuts,
showHistorySuggestions,
showBookmarkSuggestions,
showSearchSuggestions,
showSyncedTabsSuggestions,
searchEngineSource
)

Loading…
Cancel
Save