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