1
0
Fork 0
fenix/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt

325 lines
14 KiB
Kotlin
Raw Normal View History

/* This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.search
import android.Manifest
import android.content.Context
import android.content.DialogInterface
import android.graphics.Typeface.BOLD
import android.graphics.Typeface.ITALIC
import android.os.Bundle
import android.text.style.StyleSpan
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.navigation.Navigation
import androidx.transition.TransitionInflater
2019-03-25 22:13:22 +01:00
import kotlinx.android.synthetic.main.fragment_search.*
import kotlinx.android.synthetic.main.fragment_search.view.*
import mozilla.components.browser.search.SearchEngine
2019-04-19 23:12:42 +02:00
import mozilla.components.feature.qr.QrFeature
import mozilla.components.support.base.feature.BackHandler
2019-04-19 23:12:42 +02:00
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import mozilla.components.support.ktx.android.content.isPermissionGranted
import mozilla.components.support.ktx.kotlin.isUrl
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.FenixViewModelProvider
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.toolbar.SearchAction
import org.mozilla.fenix.components.toolbar.SearchChange
import org.mozilla.fenix.components.toolbar.SearchState
import org.mozilla.fenix.components.toolbar.ToolbarComponent
import org.mozilla.fenix.components.toolbar.ToolbarUIView
import org.mozilla.fenix.components.toolbar.ToolbarViewModel
import org.mozilla.fenix.ext.getSpannable
2019-02-20 17:58:42 +01:00
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.mvi.ActionBusFactory
import org.mozilla.fenix.mvi.getAutoDisposeObservable
import org.mozilla.fenix.mvi.getManagedEmitter
2019-01-31 00:51:49 +01:00
import org.mozilla.fenix.search.awesomebar.AwesomeBarAction
import org.mozilla.fenix.search.awesomebar.AwesomeBarChange
import org.mozilla.fenix.search.awesomebar.AwesomeBarComponent
import org.mozilla.fenix.search.awesomebar.AwesomeBarState
import org.mozilla.fenix.search.awesomebar.AwesomeBarUIView
import org.mozilla.fenix.search.awesomebar.AwesomeBarViewModel
@Suppress("TooManyFunctions")
class SearchFragment : Fragment(), BackHandler {
2019-01-31 00:51:49 +01:00
private lateinit var toolbarComponent: ToolbarComponent
private lateinit var awesomeBarComponent: AwesomeBarComponent
private var sessionId: String? = null
private var isPrivate = false
2019-04-19 23:12:42 +02:00
private val qrFeature = ViewBoundFeatureWrapper<QrFeature>()
private var permissionDidUpdate = false
2019-01-24 21:50:30 +01:00
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
postponeEnterTransition()
sharedElementEnterTransition = TransitionInflater.from(context).inflateTransition(android.R.transition.move)
}
override fun onCreateView(
2019-01-30 17:36:14 +01:00
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
sessionId = SearchFragmentArgs.fromBundle(arguments!!).sessionId
isPrivate = (activity as HomeActivity).browsingModeManager.isPrivate
val session = sessionId?.let { requireComponents.core.sessionManager.findSessionById(it) }
val view = inflater.inflate(R.layout.fragment_search, container, false)
val url = session?.url ?: ""
2019-02-20 17:58:42 +01:00
toolbarComponent = ToolbarComponent(
2019-03-25 22:13:22 +01:00
view.toolbar_component_wrapper,
ActionBusFactory.get(this),
sessionId,
isPrivate,
true,
view.search_engine_icon,
FenixViewModelProvider.create(
this,
ToolbarViewModel::class.java
) {
ToolbarViewModel(SearchState(url, session?.searchTerms ?: "", isEditing = true))
}
)
2019-03-25 22:13:22 +01:00
awesomeBarComponent = AwesomeBarComponent(
view.search_layout,
ActionBusFactory.get(this),
FenixViewModelProvider.create(
this,
AwesomeBarViewModel::class.java
) {
AwesomeBarViewModel(AwesomeBarState("", false))
}
)
2019-01-31 07:49:41 +01:00
ActionBusFactory.get(this).logMergedObservables()
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
layoutComponents(view.search_layout)
2019-04-19 23:12:42 +02:00
qrFeature.set(
QrFeature(
requireContext(),
fragmentManager = requireFragmentManager(),
onNeedToRequestPermissions = { permissions ->
requestPermissions(permissions, REQUEST_CODE_CAMERA_PERMISSIONS)
},
onScanResult = { result ->
search_scan_button.isChecked = false
activity?.let {
AlertDialog.Builder(
ContextThemeWrapper(
it,
R.style.DialogStyle
)
).apply {
val spannable = resources.getSpannable(
R.string.qr_scanner_confirmation_dialog_message,
listOf(
getString(R.string.app_name) to listOf(StyleSpan(BOLD)),
result to listOf(StyleSpan(ITALIC))
)
)
setMessage(spannable)
setNegativeButton(R.string.qr_scanner_dialog_negative) { dialog: DialogInterface, _ ->
requireComponents.analytics.metrics.track(Event.QRScannerNavigationDenied)
dialog.cancel()
}
setPositiveButton(R.string.qr_scanner_dialog_positive) { dialog: DialogInterface, _ ->
requireComponents.analytics.metrics.track(Event.QRScannerNavigationAllowed)
(activity as HomeActivity)
.openToBrowserAndLoad(
searchTermOrURL = result,
newTab = sessionId == null,
from = BrowserDirection.FromSearch
)
dialog.dismiss()
}
create()
}.show()
requireComponents.analytics.metrics.track(Event.QRScannerPromptDisplayed)
}
2019-04-19 23:12:42 +02:00
}),
owner = this,
view = view
)
view.search_scan_button.setOnClickListener {
getManagedEmitter<SearchChange>().onNext(SearchChange.ToolbarClearedFocus)
requireComponents.analytics.metrics.track(Event.QRScannerOpened)
2019-04-19 23:12:42 +02:00
qrFeature.get()?.scan(R.id.container)
}
2019-01-31 00:51:49 +01:00
lifecycle.addObserver((toolbarComponent.uiView as ToolbarUIView).toolbarIntegration)
2019-01-31 00:51:49 +01:00
view.toolbar_wrapper.clipToOutline = false
2019-03-25 22:13:22 +01:00
search_shortcuts_button.setOnClickListener {
2019-03-26 22:45:23 +01:00
val isOpen = (awesomeBarComponent.uiView as AwesomeBarUIView).state?.showShortcutEnginePicker ?: false
getManagedEmitter<AwesomeBarChange>().onNext(AwesomeBarChange.SearchShortcutEnginePicker(!isOpen))
if (isOpen) {
requireComponents.analytics.metrics.track(Event.SearchShortcutMenuClosed)
} else {
requireComponents.analytics.metrics.track(Event.SearchShortcutMenuOpened)
}
2019-03-25 22:13:22 +01:00
}
startPostponedEnterTransition()
}
override fun onResume() {
super.onResume()
if (!permissionDidUpdate) {
getManagedEmitter<SearchChange>().onNext(SearchChange.ToolbarRequestedFocus)
}
permissionDidUpdate = false
(activity as AppCompatActivity).supportActionBar?.hide()
}
override fun onPause() {
super.onPause()
getManagedEmitter<SearchChange>().onNext(SearchChange.ToolbarClearedFocus)
}
override fun onStart() {
super.onStart()
subscribeToSearchActions()
subscribeToAwesomeBarActions()
}
override fun onBackPressed(): Boolean {
return when {
qrFeature.onBackPressed() -> {
view?.search_scan_button?.isChecked = false
getManagedEmitter<SearchChange>().onNext(SearchChange.ToolbarRequestedFocus)
true
}
else -> false
}
}
private fun subscribeToSearchActions() {
getAutoDisposeObservable<SearchAction>()
2019-01-31 00:51:49 +01:00
.subscribe {
when (it) {
is SearchAction.UrlCommitted -> {
2019-02-24 13:28:10 +01:00
if (it.url.isNotBlank()) {
2019-04-19 23:12:42 +02:00
(activity as HomeActivity).openToBrowserAndLoad(
searchTermOrURL = it.url,
newTab = sessionId == null,
from = BrowserDirection.FromSearch,
engine = it.engine
2019-04-19 23:12:42 +02:00
)
val event = if (it.url.isUrl()) {
Event.EnteredUrl(false)
} else {
val engine = it.engine ?: requireComponents
.search.searchEngineManager.getDefaultSearchEngine(requireContext())
2019-04-17 01:31:35 +02:00
createSearchEvent(engine, false)
}
requireComponents.analytics.metrics.track(event)
2019-02-24 13:28:10 +01:00
}
}
2019-01-31 07:49:41 +01:00
is SearchAction.TextChanged -> {
getManagedEmitter<AwesomeBarChange>().onNext(AwesomeBarChange.UpdateQuery(it.query))
2019-01-31 07:49:41 +01:00
}
is SearchAction.EditingCanceled -> {
Navigation.findNavController(toolbar_wrapper).navigateUp()
}
2019-01-31 00:51:49 +01:00
}
}
}
2019-01-31 00:51:49 +01:00
private fun subscribeToAwesomeBarActions() {
getAutoDisposeObservable<AwesomeBarAction>()
2019-01-31 00:51:49 +01:00
.subscribe {
when (it) {
is AwesomeBarAction.URLTapped -> {
(activity as HomeActivity).openToBrowserAndLoad(
searchTermOrURL = it.url,
newTab = sessionId == null,
from = BrowserDirection.FromSearch
)
requireComponents.analytics.metrics.track(Event.EnteredUrl(false))
}
is AwesomeBarAction.SearchTermsTapped -> {
(activity as HomeActivity).openToBrowserAndLoad(
searchTermOrURL = it.searchTerms,
newTab = sessionId == null,
from = BrowserDirection.FromSearch,
engine = it.engine,
forceSearch = true
)
2019-03-26 22:45:23 +01:00
val engine = it.engine ?: requireComponents
.search.searchEngineManager.getDefaultSearchEngine(requireContext())
val event = createSearchEvent(engine, true)
2019-04-17 01:31:35 +02:00
requireComponents.analytics.metrics.track(event)
}
is AwesomeBarAction.SearchShortcutEngineSelected -> {
getManagedEmitter<AwesomeBarChange>()
.onNext(AwesomeBarChange.SearchShortcutEngineSelected(it.engine))
getManagedEmitter<SearchChange>()
.onNext(SearchChange.SearchShortcutEngineSelected(it.engine))
2019-03-26 22:45:23 +01:00
requireComponents.analytics.metrics.track(Event.SearchShortcutSelected(it.engine.name))
}
2019-01-31 00:51:49 +01:00
}
}
}
2019-04-17 01:31:35 +02:00
private fun createSearchEvent(engine: SearchEngine, isSuggestion: Boolean): Event.PerformedSearch {
val isShortcut = engine != requireComponents.search.searchEngineManager.defaultSearchEngine
val engineSource =
if (isShortcut) Event.PerformedSearch.EngineSource.Shortcut(engine)
else Event.PerformedSearch.EngineSource.Default(engine)
val source =
if (isSuggestion) Event.PerformedSearch.EventSource.Suggestion(engineSource)
else Event.PerformedSearch.EventSource.Action(engineSource)
return Event.PerformedSearch(source)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
when (requestCode) {
REQUEST_CODE_CAMERA_PERMISSIONS -> qrFeature.withFeature {
it.onPermissionsResult(permissions, grantResults)
context?.let { context: Context ->
if (context.isPermissionGranted(Manifest.permission.CAMERA)) {
permissionDidUpdate = true
}
}
}
else -> super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
}
2019-04-19 23:12:42 +02:00
companion object {
private const val REQUEST_CODE_CAMERA_PERMISSIONS = 1
}
}