For #13766 - Adds voice search

pull/35/head
Jeff Boek 4 years ago
parent 12d8d17116
commit 5f56e02ac3

@ -4,10 +4,13 @@
package org.mozilla.fenix.searchdialog
import android.app.Activity
import android.app.Dialog
import android.content.DialogInterface
import android.content.Intent
import android.graphics.Typeface
import android.os.Bundle
import android.speech.RecognizerIntent
import android.text.style.StyleSpan
import android.view.LayoutInflater
import android.view.View
@ -15,11 +18,11 @@ import android.view.ViewGroup
import android.view.ViewStub
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintProperties.BOTTOM
import androidx.constraintlayout.widget.ConstraintProperties.PARENT_ID
import androidx.constraintlayout.widget.ConstraintProperties.TOP
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
@ -33,6 +36,7 @@ import kotlinx.android.synthetic.main.fragment_search_dialog.view.qr_scan_button
import kotlinx.android.synthetic.main.fragment_search_dialog.view.toolbar
import kotlinx.android.synthetic.main.search_suggestions_onboarding.view.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.toolbar.BrowserToolbar
import mozilla.components.feature.qr.QrFeature
import mozilla.components.lib.state.ext.consumeFrom
import mozilla.components.support.base.feature.UserInteractionHandler
@ -55,12 +59,13 @@ import org.mozilla.fenix.search.awesomebar.AwesomeBarView
import org.mozilla.fenix.search.createInitialSearchFragmentState
import org.mozilla.fenix.search.toolbar.ToolbarView
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.widget.VoiceSearchActivity
typealias SearchDialogFragmentStore = SearchFragmentStore
typealias SearchDialogInteractor = SearchInteractor
@SuppressWarnings("LargeClass", "TooManyFunctions")
class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
private lateinit var interactor: SearchDialogInteractor
private lateinit var store: SearchDialogFragmentStore
private lateinit var toolbarView: ToolbarView
@ -68,6 +73,7 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
private var firstUpdate = true
private val qrFeature = ViewBoundFeatureWrapper<QrFeature>()
private val speechIntent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -122,7 +128,7 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
false,
view.toolbar,
requireComponents.core.engine
)
).also(::addSearchButton)
awesomeBarView = AwesomeBarView(
requireContext(),
@ -214,6 +220,16 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
if (requestCode == VoiceSearchActivity.SPEECH_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
intent?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)?.first()?.also {
toolbarView.view.edit.updateUrl(url = it, shouldHighlight = true)
interactor.onTextChanged(it)
toolbarView.view.edit.focus()
}
}
}
override fun onBackPressed(): Boolean {
return when {
qrFeature.onBackPressed() -> {
@ -293,6 +309,38 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
}
}
private fun addSearchButton(toolbarView: ToolbarView) {
toolbarView.view.addEditAction(
BrowserToolbar.Button(
ContextCompat.getDrawable(requireContext(), R.drawable.ic_microphone)!!,
requireContext().getString(R.string.voice_search_content_description),
visible = {
store.state.searchEngineSource.searchEngine.identifier.contains("google") &&
isSpeechAvailable() &&
requireContext().settings().shouldShowVoiceSearch
},
listener = ::launchVoiceSearch
)
)
}
private fun launchVoiceSearch() {
// Note if a user disables speech while the app is on the search fragment
// the voice button will still be available and *will* cause a crash if tapped,
// since the `visible` call is only checked on create. In order to avoid extra complexity
// around such a small edge case, we make the button have no functionality in this case.
if (!isSpeechAvailable()) { return }
requireComponents.analytics.metrics.track(Event.VoiceSearchTapped)
speechIntent.apply {
putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
putExtra(RecognizerIntent.EXTRA_PROMPT, requireContext().getString(R.string.voice_search_explainer))
}
startActivityForResult(speechIntent, VoiceSearchActivity.SPEECH_REQUEST_CODE)
}
private fun isSpeechAvailable(): Boolean = speechIntent.resolveActivity(requireContext().packageManager) != null
companion object {
private const val REQUEST_CODE_CAMERA_PERMISSIONS = 1
}

Loading…
Cancel
Save