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

295 lines
12 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.graphics.drawable.BitmapDrawable
import android.os.Bundle
import android.text.style.StyleSpan
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.fragment.findNavController
2019-08-08 00:41:52 +02:00
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 kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi
import mozilla.components.concept.storage.HistoryStorage
2019-04-19 23:12:42 +02:00
import mozilla.components.feature.qr.QrFeature
import mozilla.components.lib.state.ext.consumeFrom
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.hasCamera
import mozilla.components.support.ktx.android.content.isPermissionGranted
import org.jetbrains.anko.backgroundDrawable
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.components.StoreProvider
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.getColorFromAttr
import org.mozilla.fenix.ext.getSpannable
2019-02-20 17:58:42 +01:00
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.search.awesomebar.AwesomeBarView
import org.mozilla.fenix.search.toolbar.ToolbarView
import org.mozilla.fenix.utils.Settings
@Suppress("TooManyFunctions", "LargeClass")
class SearchFragment : Fragment(), BackHandler {
private lateinit var toolbarView: ToolbarView
private lateinit var awesomeBarView: AwesomeBarView
2019-04-19 23:12:42 +02:00
private val qrFeature = ViewBoundFeatureWrapper<QrFeature>()
private var permissionDidUpdate = false
private lateinit var searchStore: SearchStore
private lateinit var searchInteractor: SearchInteractor
2019-01-24 21:50:30 +01:00
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
2019-08-08 00:41:52 +02:00
postponeEnterTransition()
sharedElementEnterTransition =
TransitionInflater.from(context).inflateTransition(android.R.transition.move)
.setDuration(
SHARED_TRANSITION_MS
)
requireComponents.analytics.metrics.track(Event.InteractWithSearchURLArea)
}
override fun onCreateView(
2019-01-30 17:36:14 +01:00
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val session = arguments
?.let(SearchFragmentArgs.Companion::fromBundle)
?.let { it.sessionId }
?.let(requireComponents.core.sessionManager::findSessionById)
val displayShortcutEnginePicker = arguments
?.let(SearchFragmentArgs.Companion::fromBundle)
?.let { it.showShortcutEnginePicker } ?: false
val view = inflater.inflate(R.layout.fragment_search, container, false)
val url = session?.url ?: ""
val currentSearchEngine = SearchEngineSource.Default(
requireComponents.search.searchEngineManager.getDefaultSearchEngine(requireContext())
)
2019-02-20 17:58:42 +01:00
searchStore = StoreProvider.get(this) {
SearchStore(
SearchState(
query = url,
showShortcutEnginePicker = displayShortcutEnginePicker,
searchEngineSource = currentSearchEngine,
defaultEngineSource = currentSearchEngine,
showSuggestions = Settings.getInstance(requireContext()).showSearchSuggestions,
showVisitedSitesBookmarks = Settings.getInstance(requireContext()).shouldShowVisitedSitesBookmarks,
session = session
)
)
}
2019-03-25 22:13:22 +01:00
searchInteractor = SearchInteractor(
activity as HomeActivity,
findNavController(),
searchStore
)
awesomeBarView = AwesomeBarView(view.search_layout, searchInteractor)
toolbarView = ToolbarView(
view.toolbar_component_wrapper,
searchInteractor,
historyStorageProvider(),
2019-08-07 22:02:08 +02:00
(activity as HomeActivity).browsingModeManager.mode.isPrivate
)
2019-08-08 00:41:52 +02:00
startPostponedEnterTransition()
return view
}
@ObsoleteCoroutinesApi
@ExperimentalCoroutinesApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
searchScanButton.visibility = if (context?.hasCamera() == true) View.VISIBLE else View.GONE
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 ->
searchScanButton.isChecked = false
activity?.let {
AlertDialog.Builder(it).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 = searchStore.state.session == 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.searchScanButton.setOnClickListener {
toolbarView.view.clearFocus()
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
view.toolbar_wrapper.clipToOutline = false
2019-03-25 22:13:22 +01:00
searchShortcutsButton.setOnClickListener {
val isOpen = searchStore.state.showShortcutEnginePicker
searchStore.dispatch(SearchAction.ShowSearchShortcutEnginePicker(!isOpen))
2019-03-26 22:45:23 +01:00
if (isOpen) {
requireComponents.analytics.metrics.track(Event.SearchShortcutMenuClosed)
} else {
requireComponents.analytics.metrics.track(Event.SearchShortcutMenuOpened)
}
2019-07-11 19:24:58 +02:00
searchInteractor.turnOnStartedTyping()
2019-03-25 22:13:22 +01:00
}
consumeFrom(searchStore) {
awesomeBarView.update(it)
toolbarView.update(it)
updateSearchEngineIcon(it)
updateSearchShortuctsIcon(it)
updateSearchWithLabel(it)
}
startPostponedEnterTransition()
}
override fun onResume() {
super.onResume()
// The user has the option to go to 'Shortcuts' -> 'Search engine settings' to modify the default search engine.
// When returning from that settings screen we need to update it to account for any changes.
2019-08-08 00:41:52 +02:00
val currentDefaultEngine =
requireComponents.search.searchEngineManager.getDefaultSearchEngine(requireContext())
if (searchStore.state.defaultEngineSource.searchEngine != currentDefaultEngine) {
searchStore.dispatch(
SearchAction.SelectNewDefaultSearchEngine
(currentDefaultEngine)
)
}
if (!permissionDidUpdate) {
toolbarView.view.requestFocus()
}
permissionDidUpdate = false
(activity as AppCompatActivity).supportActionBar?.hide()
}
override fun onPause() {
super.onPause()
toolbarView.view.clearFocus()
}
override fun onBackPressed(): Boolean {
return when {
qrFeature.onBackPressed() -> {
view?.searchScanButton?.isChecked = false
toolbarView.view.requestFocus()
true
}
else -> false
}
}
private fun updateSearchEngineIcon(searchState: SearchState) {
val searchIcon = searchState.searchEngineSource.searchEngine.icon
val draw = BitmapDrawable(resources, searchIcon)
val iconSize = resources.getDimension(R.dimen.preference_icon_drawable_size).toInt()
draw.setBounds(0, 0, iconSize, iconSize)
searchEngineIcon?.backgroundDrawable = draw
}
2019-01-31 00:51:49 +01:00
private fun updateSearchWithLabel(searchState: SearchState) {
2019-08-08 00:41:52 +02:00
searchWithShortcuts.visibility =
if (searchState.showShortcutEnginePicker) View.VISIBLE else View.GONE
}
private fun updateSearchShortuctsIcon(searchState: SearchState) {
with(requireContext()) {
val showShortcuts = searchState.showShortcutEnginePicker
searchShortcutsButton?.isChecked = showShortcuts
2019-04-17 01:31:35 +02:00
val color = if (showShortcuts) R.attr.contrastText else R.attr.primaryText
2019-04-17 01:31:35 +02:00
searchShortcutsButton.compoundDrawables[0]?.setTint(getColorFromAttr(color))
}
2019-04-17 01:31:35 +02:00
}
2019-08-08 00:41:52 +02:00
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 {
view?.searchScanButton?.isChecked = false
}
}
}
else -> super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
}
private fun historyStorageProvider(): HistoryStorage? {
return if (Settings.getInstance(requireContext()).shouldShowVisitedSitesBookmarks) {
requireComponents.core.historyStorage
} else null
}
2019-04-19 23:12:42 +02:00
companion object {
2019-08-08 00:41:52 +02:00
private const val SHARED_TRANSITION_MS = 200L
2019-04-19 23:12:42 +02:00
private const val REQUEST_CODE_CAMERA_PERMISSIONS = 1
}
}