* Closes #875: Adds shortcuts * Refactor and clean up * Remove TODO * Removes local * Fix nits * Refactors to add ShortcutEngineManagermaster
parent
4f67b7a26c
commit
36af5107c6
|
@ -16,6 +16,7 @@ import androidx.appcompat.widget.Toolbar
|
|||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.ui.AppBarConfiguration
|
||||
import androidx.navigation.ui.NavigationUI
|
||||
import mozilla.components.browser.search.SearchEngine
|
||||
import mozilla.components.browser.session.Session
|
||||
import mozilla.components.concept.engine.EngineView
|
||||
import mozilla.components.feature.intent.IntentProcessor
|
||||
|
@ -148,9 +149,14 @@ open class HomeActivity : AppCompatActivity() {
|
|||
openToBrowser(SafeIntent(intent).getStringExtra(IntentProcessor.ACTIVE_SESSION_ID), BrowserDirection.FromGlobal)
|
||||
}
|
||||
|
||||
fun openToBrowserAndLoad(text: String, sessionId: String? = null, from: BrowserDirection) {
|
||||
fun openToBrowserAndLoad(
|
||||
text: String,
|
||||
sessionId: String? = null,
|
||||
engine: SearchEngine? = null,
|
||||
from: BrowserDirection
|
||||
) {
|
||||
openToBrowser(sessionId, from)
|
||||
load(text, sessionId)
|
||||
load(text, sessionId, engine)
|
||||
}
|
||||
|
||||
fun openToBrowser(sessionId: String?, from: BrowserDirection) {
|
||||
|
@ -165,7 +171,7 @@ open class HomeActivity : AppCompatActivity() {
|
|||
navHost.navController.navigate(directions)
|
||||
}
|
||||
|
||||
private fun load(text: String, sessionId: String?) {
|
||||
private fun load(text: String, sessionId: String?, engine: SearchEngine?) {
|
||||
val isPrivate = this.browsingModeManager.isPrivate
|
||||
|
||||
val loadUrlUseCase = if (sessionId == null) {
|
||||
|
@ -179,8 +185,8 @@ open class HomeActivity : AppCompatActivity() {
|
|||
val searchUseCase: (String) -> Unit = { searchTerms ->
|
||||
if (sessionId == null) {
|
||||
components.useCases.searchUseCases.newTabSearch
|
||||
.invoke(searchTerms, Session.Source.USER_ENTERED, true, isPrivate)
|
||||
} else components.useCases.searchUseCases.defaultSearch.invoke(searchTerms)
|
||||
.invoke(searchTerms, Session.Source.USER_ENTERED, true, isPrivate, searchEngine = engine)
|
||||
} else components.useCases.searchUseCases.defaultSearch.invoke(searchTerms, engine)
|
||||
}
|
||||
|
||||
if (text.isUrl()) {
|
||||
|
|
|
@ -21,6 +21,7 @@ import androidx.navigation.Navigation
|
|||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.android.synthetic.main.component_search.*
|
||||
import kotlinx.android.synthetic.main.fragment_browser.view.*
|
||||
import kotlinx.android.synthetic.main.fragment_search.*
|
||||
import mozilla.components.browser.toolbar.behavior.BrowserToolbarBottomBehavior
|
||||
import mozilla.components.feature.contextmenu.ContextMenuCandidate
|
||||
import mozilla.components.feature.contextmenu.ContextMenuFeature
|
||||
|
@ -90,7 +91,8 @@ class BrowserFragment : Fragment(), BackHandler {
|
|||
view.browserLayout,
|
||||
ActionBusFactory.get(this), sessionId,
|
||||
(activity as HomeActivity).browsingModeManager.isPrivate,
|
||||
SearchState("", isEditing = false)
|
||||
SearchState("", isEditing = false),
|
||||
search_engine_icon
|
||||
)
|
||||
|
||||
toolbarComponent.uiView.view.apply {
|
||||
|
|
|
@ -5,8 +5,10 @@
|
|||
package org.mozilla.fenix.components.toolbar
|
||||
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.core.content.ContextCompat
|
||||
import kotlinx.android.synthetic.main.component_search.*
|
||||
import mozilla.components.browser.search.SearchEngine
|
||||
import mozilla.components.browser.toolbar.BrowserToolbar
|
||||
import org.mozilla.fenix.DefaultThemeManager
|
||||
import org.mozilla.fenix.R
|
||||
|
@ -22,7 +24,8 @@ class ToolbarComponent(
|
|||
bus: ActionBusFactory,
|
||||
private val sessionId: String?,
|
||||
private val isPrivate: Boolean,
|
||||
override var initialState: SearchState = SearchState("", false)
|
||||
override var initialState: SearchState = SearchState("", false),
|
||||
private val engineIconView: ImageView? = null
|
||||
) :
|
||||
UIComponent<SearchState, SearchAction, SearchChange>(
|
||||
bus.getManagedEmitter(SearchAction::class.java),
|
||||
|
@ -34,10 +37,19 @@ class ToolbarComponent(
|
|||
override val reducer: Reducer<SearchState, SearchChange> = { state, change ->
|
||||
when (change) {
|
||||
is SearchChange.QueryChanged -> state.copy(query = change.query)
|
||||
is SearchChange.SearchShortcutEngineSelected ->
|
||||
state.copy(engine = change.engine)
|
||||
}
|
||||
}
|
||||
|
||||
override fun initView() = ToolbarUIView(sessionId, isPrivate, container, actionEmitter, changesObservable)
|
||||
override fun initView() = ToolbarUIView(
|
||||
sessionId,
|
||||
isPrivate,
|
||||
container,
|
||||
actionEmitter,
|
||||
changesObservable,
|
||||
engineIconView
|
||||
)
|
||||
|
||||
init {
|
||||
render(reducer)
|
||||
|
@ -60,10 +72,14 @@ class ToolbarComponent(
|
|||
}
|
||||
}
|
||||
|
||||
data class SearchState(val query: String, val isEditing: Boolean) : ViewState
|
||||
data class SearchState(
|
||||
val query: String,
|
||||
val isEditing: Boolean,
|
||||
val engine: SearchEngine? = null
|
||||
) : ViewState
|
||||
|
||||
sealed class SearchAction : Action {
|
||||
data class UrlCommitted(val url: String, val session: String?) : SearchAction()
|
||||
data class UrlCommitted(val url: String, val session: String?, val engine: SearchEngine? = null) : SearchAction()
|
||||
data class TextChanged(val query: String) : SearchAction()
|
||||
object ToolbarTapped : SearchAction()
|
||||
data class ToolbarMenuItemTapped(val item: ToolbarMenu.Item) : SearchAction()
|
||||
|
@ -72,4 +88,5 @@ sealed class SearchAction : Action {
|
|||
|
||||
sealed class SearchChange : Change {
|
||||
data class QueryChanged(val query: String) : SearchChange()
|
||||
data class SearchShortcutEngineSelected(val engine: SearchEngine) : SearchChange()
|
||||
}
|
||||
|
|
|
@ -4,8 +4,10 @@
|
|||
|
||||
package org.mozilla.fenix.components.toolbar
|
||||
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.core.content.ContextCompat
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Observer
|
||||
|
@ -13,6 +15,7 @@ import io.reactivex.functions.Consumer
|
|||
import mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider
|
||||
import mozilla.components.browser.toolbar.BrowserToolbar
|
||||
import mozilla.components.support.ktx.android.content.res.pxToDp
|
||||
import org.jetbrains.anko.backgroundDrawable
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.mvi.UIView
|
||||
|
@ -22,11 +25,14 @@ class ToolbarUIView(
|
|||
isPrivate: Boolean,
|
||||
container: ViewGroup,
|
||||
actionEmitter: Observer<SearchAction>,
|
||||
changesObservable: Observable<SearchChange>
|
||||
changesObservable: Observable<SearchChange>,
|
||||
private val engineIconView: ImageView? = null
|
||||
) :
|
||||
UIView<SearchState, SearchAction, SearchChange>(container, actionEmitter, changesObservable) {
|
||||
|
||||
val toolbarIntegration: ToolbarIntegration
|
||||
var state: SearchState? = null
|
||||
private set
|
||||
|
||||
override val view: BrowserToolbar = LayoutInflater.from(container.context)
|
||||
.inflate(R.layout.component_search, container, true)
|
||||
|
@ -38,7 +44,7 @@ class ToolbarUIView(
|
|||
init {
|
||||
view.apply {
|
||||
setOnUrlCommitListener {
|
||||
actionEmitter.onNext(SearchAction.UrlCommitted(it, sessionId))
|
||||
actionEmitter.onNext(SearchAction.UrlCommitted(it, sessionId, state?.engine))
|
||||
false
|
||||
}
|
||||
onUrlClicked = {
|
||||
|
@ -87,14 +93,63 @@ class ToolbarUIView(
|
|||
}
|
||||
|
||||
override fun updateView() = Consumer<SearchState> {
|
||||
if (it.isEditing) {
|
||||
view.url = it.query
|
||||
if (shouldUpdateEngineIcon(it)) {
|
||||
updateEngineIcon(it)
|
||||
}
|
||||
|
||||
if (shouldClearSearchURL(it)) {
|
||||
clearSearchURL()
|
||||
}
|
||||
|
||||
if (shouldUpdateEditingState(it)) {
|
||||
updateEditingState(it)
|
||||
}
|
||||
|
||||
state = it
|
||||
}
|
||||
|
||||
private fun shouldUpdateEngineIcon(newState: SearchState): Boolean {
|
||||
return newState.isEditing && (engineDidChange(newState) || state == null)
|
||||
}
|
||||
|
||||
private fun updateEngineIcon(newState: SearchState) {
|
||||
with(view.context) {
|
||||
val defaultEngineIcon = components.search.searchEngineManager.defaultSearchEngine?.icon
|
||||
val searchIcon = newState.engine?.icon ?: defaultEngineIcon
|
||||
val draw = BitmapDrawable(searchIcon)
|
||||
val iconSize =
|
||||
containerView?.context!!.resources.getDimension(R.dimen.preference_icon_drawable_size).toInt()
|
||||
draw.setBounds(0, 0, iconSize, iconSize)
|
||||
engineIconView?.backgroundDrawable = draw
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldClearSearchURL(newState: SearchState): Boolean {
|
||||
return newState.engine != state?.engine && view.url == newState.query
|
||||
}
|
||||
|
||||
private fun clearSearchURL() {
|
||||
view.url = ""
|
||||
view.editMode()
|
||||
}
|
||||
|
||||
private fun shouldUpdateEditingState(newState: SearchState): Boolean {
|
||||
return !engineDidChange(newState)
|
||||
}
|
||||
|
||||
private fun updateEditingState(newState: SearchState) {
|
||||
if (newState.isEditing) {
|
||||
view.url = newState.query
|
||||
view.editMode()
|
||||
} else {
|
||||
view.displayMode()
|
||||
}
|
||||
}
|
||||
|
||||
private fun engineDidChange(newState: SearchState): Boolean {
|
||||
return newState.engine != state?.engine
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val browserActionMarginDp = 8
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
package org.mozilla.fenix.search
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
@ -17,13 +16,13 @@ import kotlinx.android.synthetic.main.fragment_search.view.*
|
|||
import mozilla.components.feature.search.SearchUseCases
|
||||
import mozilla.components.feature.session.SessionUseCases
|
||||
import mozilla.components.support.ktx.kotlin.isUrl
|
||||
import org.jetbrains.anko.backgroundDrawable
|
||||
import org.mozilla.fenix.BrowserDirection
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
import org.mozilla.fenix.utils.ItsNotBrokenSnack
|
||||
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
|
||||
|
@ -35,6 +34,7 @@ import org.mozilla.fenix.mvi.getManagedEmitter
|
|||
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.AwesomeBarUIView
|
||||
|
||||
class SearchFragment : Fragment() {
|
||||
private lateinit var toolbarComponent: ToolbarComponent
|
||||
|
@ -65,7 +65,8 @@ class SearchFragment : Fragment() {
|
|||
ActionBusFactory.get(this),
|
||||
sessionId,
|
||||
isPrivate,
|
||||
SearchState(url, isEditing = true)
|
||||
SearchState(url, isEditing = true),
|
||||
view.search_engine_icon
|
||||
)
|
||||
|
||||
awesomeBarComponent = AwesomeBarComponent(view.search_layout, ActionBusFactory.get(this))
|
||||
|
@ -82,15 +83,11 @@ class SearchFragment : Fragment() {
|
|||
|
||||
view.toolbar_wrapper.clipToOutline = false
|
||||
|
||||
val searchIcon = requireComponents.search.searchEngineManager.getDefaultSearchEngine(
|
||||
requireContext()
|
||||
).let {
|
||||
BitmapDrawable(resources, it.icon)
|
||||
search_shortcuts_button.setOnClickListener {
|
||||
getManagedEmitter<AwesomeBarChange>().onNext(AwesomeBarChange
|
||||
.SearchShortcutEnginePicker(!(
|
||||
(awesomeBarComponent.uiView as AwesomeBarUIView).state?.showShortcutEnginePicker ?: true)))
|
||||
}
|
||||
|
||||
val iconSize = resources.getDimension(R.dimen.preference_icon_drawable_size).toInt()
|
||||
searchIcon.setBounds(0, 0, iconSize, iconSize)
|
||||
search_engine_icon.backgroundDrawable = searchIcon
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
@ -100,13 +97,17 @@ class SearchFragment : Fragment() {
|
|||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
subscribeToSearchActions()
|
||||
subscribeToAwesomeBarActions()
|
||||
}
|
||||
|
||||
private fun subscribeToSearchActions() {
|
||||
getAutoDisposeObservable<SearchAction>()
|
||||
.subscribe {
|
||||
when (it) {
|
||||
is SearchAction.UrlCommitted -> {
|
||||
if (it.url.isNotBlank()) {
|
||||
(activity as HomeActivity).openToBrowserAndLoad(it.url, it.session,
|
||||
(activity as HomeActivity).openToBrowserAndLoad(it.url, it.session, it.engine,
|
||||
BrowserDirection.FromSearch)
|
||||
|
||||
val event = if (it.url.isUrl()) {
|
||||
|
@ -126,7 +127,9 @@ class SearchFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeToAwesomeBarActions() {
|
||||
getAutoDisposeObservable<AwesomeBarAction>()
|
||||
.subscribe {
|
||||
when (it) {
|
||||
|
@ -141,6 +144,12 @@ class SearchFragment : Fragment() {
|
|||
(activity as HomeActivity).openToBrowser(sessionId, BrowserDirection.FromSearch)
|
||||
requireComponents.analytics.metrics.track(Event.PerformedSearch(true))
|
||||
}
|
||||
is AwesomeBarAction.SearchShortcutEngineSelected -> {
|
||||
getManagedEmitter<AwesomeBarChange>()
|
||||
.onNext(AwesomeBarChange.SearchShortcutEngineSelected(it.engine))
|
||||
getManagedEmitter<SearchChange>()
|
||||
.onNext(SearchChange.SearchShortcutEngineSelected(it.engine))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,10 +63,15 @@ internal fun SearchFragment.setOutOfExperimentConstraints(layout: ConstraintLayo
|
|||
BOTTOM to TOP of UNSET
|
||||
)
|
||||
}
|
||||
search_with_shortcuts {
|
||||
connect(
|
||||
TOP to BOTTOM of toolbar_wrapper
|
||||
)
|
||||
}
|
||||
awesomeBar {
|
||||
connect(
|
||||
TOP to TOP of UNSET,
|
||||
TOP to BOTTOM of toolbar_wrapper,
|
||||
TOP to BOTTOM of search_with_shortcuts,
|
||||
BOTTOM to TOP of pill_wrapper
|
||||
)
|
||||
}
|
||||
|
|
|
@ -12,27 +12,38 @@ import org.mozilla.fenix.mvi.Reducer
|
|||
import org.mozilla.fenix.mvi.UIComponent
|
||||
import org.mozilla.fenix.mvi.ViewState
|
||||
|
||||
data class AwesomeBarState(val query: String) : ViewState
|
||||
data class AwesomeBarState(
|
||||
val query: String,
|
||||
val showShortcutEnginePicker: Boolean,
|
||||
val suggestionEngine: SearchEngine? = null
|
||||
) : ViewState
|
||||
|
||||
sealed class AwesomeBarAction : Action {
|
||||
data class URLTapped(val url: String) : AwesomeBarAction()
|
||||
data class SearchTermsTapped(val searchTerms: String, val engine: SearchEngine?) : AwesomeBarAction()
|
||||
data class SearchTermsTapped(val searchTerms: String, val engine: SearchEngine? = null) : AwesomeBarAction()
|
||||
data class SearchShortcutEngineSelected(val engine: SearchEngine) : AwesomeBarAction()
|
||||
}
|
||||
|
||||
sealed class AwesomeBarChange : Change {
|
||||
data class SearchShortcutEngineSelected(val engine: SearchEngine) : AwesomeBarChange()
|
||||
data class SearchShortcutEnginePicker(val show: Boolean) : AwesomeBarChange()
|
||||
data class UpdateQuery(val query: String) : AwesomeBarChange()
|
||||
}
|
||||
|
||||
class AwesomeBarComponent(
|
||||
private val container: ViewGroup,
|
||||
bus: ActionBusFactory,
|
||||
override var initialState: AwesomeBarState = AwesomeBarState("")
|
||||
override var initialState: AwesomeBarState = AwesomeBarState("", false)
|
||||
) : UIComponent<AwesomeBarState, AwesomeBarAction, AwesomeBarChange>(
|
||||
bus.getManagedEmitter(AwesomeBarAction::class.java),
|
||||
bus.getSafeManagedObservable(AwesomeBarChange::class.java)
|
||||
) {
|
||||
override val reducer: Reducer<AwesomeBarState, AwesomeBarChange> = { state, change ->
|
||||
when (change) {
|
||||
is AwesomeBarChange.SearchShortcutEngineSelected ->
|
||||
state.copy(suggestionEngine = change.engine, showShortcutEnginePicker = false)
|
||||
is AwesomeBarChange.SearchShortcutEnginePicker ->
|
||||
state.copy(showShortcutEnginePicker = change.show)
|
||||
is AwesomeBarChange.UpdateQuery -> state.copy(query = change.query)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ package org.mozilla.fenix.search.awesomebar
|
|||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Observer
|
||||
import io.reactivex.functions.Consumer
|
||||
|
@ -24,7 +25,7 @@ import org.mozilla.fenix.mvi.UIView
|
|||
import org.mozilla.fenix.utils.Settings
|
||||
|
||||
class AwesomeBarUIView(
|
||||
container: ViewGroup,
|
||||
private val container: ViewGroup,
|
||||
actionEmitter: Observer<AwesomeBarAction>,
|
||||
changesObservable: Observable<AwesomeBarChange>
|
||||
) :
|
||||
|
@ -37,56 +38,134 @@ class AwesomeBarUIView(
|
|||
.inflate(R.layout.component_awesomebar, container, true)
|
||||
.findViewById(R.id.awesomeBar)
|
||||
|
||||
var state: AwesomeBarState? = null
|
||||
private set
|
||||
|
||||
private var clipboardSuggestionProvider: ClipboardSuggestionProvider? = null
|
||||
private var sessionProvider: SessionSuggestionProvider? = null
|
||||
private var historyStorageProvider: HistoryStorageSuggestionProvider? = null
|
||||
private var shortcutsEnginePickerProvider: ShortcutsSuggestionProvider? = null
|
||||
|
||||
private val searchSuggestionProvider: SearchSuggestionProvider?
|
||||
get() = searchSuggestionFromShortcutProvider ?: defaultSearchSuggestionProvider!!
|
||||
|
||||
private var defaultSearchSuggestionProvider: SearchSuggestionProvider? = null
|
||||
private var searchSuggestionFromShortcutProvider: SearchSuggestionProvider? = null
|
||||
|
||||
private val shortcutEngineManager by lazy {
|
||||
ShortcutEngineManager(
|
||||
this,
|
||||
actionEmitter,
|
||||
::setShortcutEngine,
|
||||
::showSuggestionProviders,
|
||||
::showSearchSuggestionProvider
|
||||
)
|
||||
}
|
||||
|
||||
private val loadUrlUseCase = object : SessionUseCases.LoadUrlUseCase {
|
||||
override fun invoke(url: String) {
|
||||
actionEmitter.onNext(AwesomeBarAction.URLTapped(url))
|
||||
}
|
||||
}
|
||||
|
||||
private val searchUseCase = object : SearchUseCases.SearchUseCase {
|
||||
override fun invoke(searchTerms: String, searchEngine: SearchEngine?) {
|
||||
actionEmitter.onNext(AwesomeBarAction.SearchTermsTapped(searchTerms, searchEngine))
|
||||
}
|
||||
}
|
||||
|
||||
private val shortcutSearchUseCase = object : SearchUseCases.SearchUseCase {
|
||||
override fun invoke(searchTerms: String, searchEngine: SearchEngine?) {
|
||||
actionEmitter.onNext(AwesomeBarAction.SearchTermsTapped(searchTerms, state?.suggestionEngine))
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
val loadUrlUseCase = object : SessionUseCases.LoadUrlUseCase {
|
||||
override fun invoke(url: String) {
|
||||
actionEmitter.onNext(AwesomeBarAction.URLTapped(url))
|
||||
}
|
||||
}
|
||||
|
||||
val searchUseCase = object : SearchUseCases.SearchUseCase {
|
||||
override fun invoke(searchTerms: String, searchEngine: SearchEngine?) {
|
||||
actionEmitter.onNext(AwesomeBarAction.SearchTermsTapped(searchTerms, searchEngine))
|
||||
}
|
||||
}
|
||||
|
||||
with(container.context) {
|
||||
view.addProviders(ClipboardSuggestionProvider(
|
||||
clipboardSuggestionProvider = ClipboardSuggestionProvider(
|
||||
this,
|
||||
loadUrlUseCase,
|
||||
getDrawable(R.drawable.ic_link)!!.toBitmap(),
|
||||
getString(R.string.awesomebar_clipboard_title)
|
||||
)
|
||||
)
|
||||
|
||||
if (Settings.getInstance(container.context).showSearchSuggestions()) {
|
||||
view.addProviders(
|
||||
SearchSuggestionProvider(
|
||||
searchEngine = components.search.searchEngineManager.getDefaultSearchEngine(this),
|
||||
searchUseCase = searchUseCase,
|
||||
fetchClient = components.core.client,
|
||||
mode = SearchSuggestionProvider.Mode.MULTIPLE_SUGGESTIONS,
|
||||
limit = 3
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
view.addProviders(
|
||||
sessionProvider =
|
||||
SessionSuggestionProvider(
|
||||
components.core.sessionManager,
|
||||
components.useCases.tabsUseCases.selectTab,
|
||||
components.utils.icons
|
||||
),
|
||||
)
|
||||
|
||||
historyStorageProvider =
|
||||
HistoryStorageSuggestionProvider(
|
||||
components.core.historyStorage,
|
||||
loadUrlUseCase,
|
||||
components.utils.icons
|
||||
)
|
||||
)
|
||||
|
||||
if (Settings.getInstance(container.context).showSearchSuggestions()) {
|
||||
val draw = getDrawable(R.drawable.ic_search)
|
||||
draw?.setTint(ContextCompat.getColor(this, R.color.search_text))
|
||||
|
||||
defaultSearchSuggestionProvider =
|
||||
SearchSuggestionProvider(
|
||||
searchEngine = components.search.searchEngineManager.getDefaultSearchEngine(this),
|
||||
searchUseCase = searchUseCase,
|
||||
fetchClient = components.core.client,
|
||||
mode = SearchSuggestionProvider.Mode.MULTIPLE_SUGGESTIONS,
|
||||
limit = 3,
|
||||
icon = draw?.toBitmap()
|
||||
)
|
||||
}
|
||||
|
||||
shortcutsEnginePickerProvider =
|
||||
ShortcutsSuggestionProvider(
|
||||
components.search.searchEngineManager,
|
||||
this,
|
||||
shortcutEngineManager::selectShortcutEngine,
|
||||
shortcutEngineManager::selectShortcutEngineSettings)
|
||||
|
||||
shortcutEngineManager.shortcutsEnginePickerProvider = shortcutsEnginePickerProvider
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSuggestionProviders() {
|
||||
if (Settings.getInstance(container.context).showSearchSuggestions()) {
|
||||
view.addProviders(searchSuggestionProvider!!)
|
||||
}
|
||||
|
||||
view.addProviders(
|
||||
clipboardSuggestionProvider!!,
|
||||
historyStorageProvider!!,
|
||||
sessionProvider!!
|
||||
)
|
||||
}
|
||||
|
||||
private fun showSearchSuggestionProvider() {
|
||||
view.addProviders(searchSuggestionProvider!!)
|
||||
}
|
||||
|
||||
private fun setShortcutEngine(engine: SearchEngine) {
|
||||
with(container.context) {
|
||||
val draw = getDrawable(R.drawable.ic_search)
|
||||
draw?.setTint(androidx.core.content.ContextCompat.getColor(this, R.color.search_text))
|
||||
|
||||
searchSuggestionFromShortcutProvider =
|
||||
SearchSuggestionProvider(
|
||||
components.search.searchEngineManager.getDefaultSearchEngine(this, engine.name),
|
||||
shortcutSearchUseCase,
|
||||
components.core.client,
|
||||
mode = SearchSuggestionProvider.Mode.MULTIPLE_SUGGESTIONS,
|
||||
icon = draw?.toBitmap()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateView() = Consumer<AwesomeBarState> {
|
||||
shortcutEngineManager.updateSelectedEngineIfNecessary(it)
|
||||
shortcutEngineManager.updateEnginePickerVisibilityIfNecessary(it)
|
||||
|
||||
view.onInputChanged(it.query)
|
||||
state = it
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
package org.mozilla.fenix.search.awesomebar
|
||||
|
||||
/* 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/. */
|
||||
|
||||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.navigation.Navigation
|
||||
import io.reactivex.Observer
|
||||
import kotlinx.android.synthetic.main.fragment_search.*
|
||||
import mozilla.components.browser.search.SearchEngine
|
||||
import org.jetbrains.anko.textColor
|
||||
import org.mozilla.fenix.DefaultThemeManager
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.search.SearchFragmentDirections
|
||||
|
||||
class ShortcutEngineManager(
|
||||
private val awesomeBarUIView: AwesomeBarUIView,
|
||||
private val actionEmitter: Observer<AwesomeBarAction>,
|
||||
private val setShortcutEngine: (newEngine: SearchEngine) -> Unit,
|
||||
private val showSuggestionProviders: () -> Unit,
|
||||
private val showSearchSuggestionProvider: () -> Unit
|
||||
) {
|
||||
|
||||
var shortcutsEnginePickerProvider: ShortcutsSuggestionProvider? = null
|
||||
val context = awesomeBarUIView.containerView?.context!!
|
||||
|
||||
fun updateSelectedEngineIfNecessary(newState: AwesomeBarState) {
|
||||
if (engineDidChange(newState)) {
|
||||
newState.suggestionEngine?.let { newEngine ->
|
||||
setShortcutEngine(newEngine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateEnginePickerVisibilityIfNecessary(newState: AwesomeBarState) {
|
||||
if (shouldUpdateShortcutEnginePickerVisibility(newState)) {
|
||||
if (newState.showShortcutEnginePicker) {
|
||||
showShortcutEnginePicker()
|
||||
updateSearchWithVisibility(true)
|
||||
} else {
|
||||
hideShortcutEnginePicker()
|
||||
updateSearchWithVisibility(false)
|
||||
newState.suggestionEngine?.also { showSearchSuggestionProvider() } ?: showSuggestionProviders()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun selectShortcutEngine(engine: SearchEngine) {
|
||||
actionEmitter.onNext(AwesomeBarAction.SearchShortcutEngineSelected(engine))
|
||||
}
|
||||
|
||||
fun selectShortcutEngineSettings() {
|
||||
val directions = SearchFragmentDirections.actionSearchFragmentToSearchEngineFragment()
|
||||
Navigation.findNavController(awesomeBarUIView.view).navigate(directions)
|
||||
}
|
||||
|
||||
private fun engineDidChange(newState: AwesomeBarState): Boolean {
|
||||
return awesomeBarUIView.state?.suggestionEngine != newState.suggestionEngine
|
||||
}
|
||||
|
||||
private fun shouldUpdateShortcutEnginePickerVisibility(newState: AwesomeBarState): Boolean {
|
||||
return awesomeBarUIView.state?.showShortcutEnginePicker != newState.showShortcutEnginePicker
|
||||
}
|
||||
|
||||
private fun showShortcutEnginePicker() {
|
||||
with(context) {
|
||||
awesomeBarUIView.search_shortcuts_button.background = getDrawable(R.drawable.search_pill_background)
|
||||
|
||||
awesomeBarUIView.search_shortcuts_button.compoundDrawables[0].setTint(ContextCompat.getColor(this,
|
||||
DefaultThemeManager.resolveAttribute(R.attr.pillWrapperBackground, this)))
|
||||
|
||||
awesomeBarUIView.search_shortcuts_button.textColor = ContextCompat.getColor(this,
|
||||
DefaultThemeManager.resolveAttribute(R.attr.pillWrapperBackground, this))
|
||||
|
||||
awesomeBarUIView.view.removeAllProviders()
|
||||
awesomeBarUIView.view.addProviders(shortcutsEnginePickerProvider!!)
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideShortcutEnginePicker() {
|
||||
with(context) {
|
||||
awesomeBarUIView.search_shortcuts_button.setBackgroundColor(ContextCompat.getColor(this,
|
||||
DefaultThemeManager.resolveAttribute(R.attr.pillWrapperBackground, this)))
|
||||
|
||||
awesomeBarUIView.search_shortcuts_button.compoundDrawables[0].setTint(ContextCompat.getColor(this,
|
||||
DefaultThemeManager.resolveAttribute(R.attr.searchShortcutsTextColor, this)))
|
||||
|
||||
awesomeBarUIView.search_shortcuts_button.textColor = ContextCompat.getColor(this,
|
||||
DefaultThemeManager.resolveAttribute(R.attr.searchShortcutsTextColor, this))
|
||||
|
||||
awesomeBarUIView.view.removeProviders(shortcutsEnginePickerProvider!!)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateSearchWithVisibility(visible: Boolean) {
|
||||
awesomeBarUIView.search_with_shortcuts.visibility = if (visible) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package org.mozilla.fenix.search.awesomebar
|
||||
|
||||
import android.content.Context
|
||||
import mozilla.components.browser.search.SearchEngine
|
||||
import mozilla.components.browser.search.SearchEngineManager
|
||||
import mozilla.components.concept.awesomebar.AwesomeBar
|
||||
import mozilla.components.support.ktx.android.graphics.drawable.toBitmap
|
||||
import org.mozilla.fenix.R
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* A [AwesomeBar.SuggestionProvider] implementation that provides search engine suggestions.
|
||||
*/
|
||||
class ShortcutsSuggestionProvider(
|
||||
private val searchEngineManager: SearchEngineManager,
|
||||
private val context: Context,
|
||||
private val selectShortcutEngine: (engine: SearchEngine) -> Unit,
|
||||
private val selectShortcutEngineSettings: () -> Unit
|
||||
) : AwesomeBar.SuggestionProvider {
|
||||
override val id: String = UUID.randomUUID().toString()
|
||||
|
||||
override val shouldClearSuggestions: Boolean
|
||||
get() = false
|
||||
|
||||
override suspend fun onInputChanged(text: String): List<AwesomeBar.Suggestion> {
|
||||
val suggestions = mutableListOf<AwesomeBar.Suggestion>()
|
||||
|
||||
searchEngineManager.getSearchEngines(context).forEach {
|
||||
suggestions.add(
|
||||
AwesomeBar.Suggestion(
|
||||
provider = this,
|
||||
id = id,
|
||||
icon = { _, _ ->
|
||||
it.icon
|
||||
},
|
||||
title = it.name,
|
||||
onSuggestionClicked = {
|
||||
selectShortcutEngine(it)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
suggestions.add(
|
||||
AwesomeBar.Suggestion(
|
||||
provider = this,
|
||||
id = id,
|
||||
icon = { _, _ ->
|
||||
context.getDrawable(R.drawable.ic_settings)?.toBitmap()
|
||||
},
|
||||
title = context.getString(R.string.search_shortcuts_engine_settings),
|
||||
onSuggestionClicked = {
|
||||
selectShortcutEngineSettings()
|
||||
})
|
||||
)
|
||||
return suggestions
|
||||
}
|
||||
}
|
|
@ -3,10 +3,10 @@
|
|||
- 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/. -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/searchPillBackground"/>
|
||||
<solid android:color="?attr/pillWrapperSelectedBackground"/>
|
||||
|
||||
<stroke android:width="1dp"
|
||||
android:color="@color/searchPillPrimary"/>
|
||||
android:color="?attr/pillWrapperSelectedBackground"/>
|
||||
|
||||
<corners android:radius="16dp"/>
|
||||
</shape>
|
|
@ -10,7 +10,6 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:padding="4dp"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar_wrapper"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
|
|
@ -40,8 +40,25 @@
|
|||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/search_engine_icon"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/search_with_shortcuts"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="15dp"
|
||||
android:visibility="gone"
|
||||
android:fontFamily="Inter UI"
|
||||
android:textAllCaps="true"
|
||||
android:textStyle="bold"
|
||||
android:textSize="12sp"
|
||||
android:textColor="?attr/awesomeBarDescriptionTextColor"
|
||||
android:letterSpacing="0.15"
|
||||
android:text="@string/search_shortcuts_search_with"
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar_wrapper"
|
||||
app:layout_constraintStart_toStartOf="@id/toolbar_wrapper"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/pill_wrapper"
|
||||
android:background="?attr/pillWrapperBackground"
|
||||
|
@ -64,16 +81,14 @@
|
|||
android:drawableStart="@drawable/ic_qr"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:textColor="?attr/searchShortcutsTextColor"
|
||||
android:background="?attr/pillWrapperBackground"
|
||||
android:drawableTint="?attr/searchShortcutsTextColor"/>
|
||||
android:background="?attr/pillWrapperBackground"/>
|
||||
|
||||
<Button
|
||||
style="@style/search_pill"
|
||||
android:id="@+id/search_shortcuts_button"
|
||||
android:text="@string/search_shortcuts_button"
|
||||
android:drawableStart="@drawable/ic_shortcuts"
|
||||
android:drawableStart="@drawable/ic_search"
|
||||
android:textColor="?attr/searchShortcutsTextColor"
|
||||
android:background="?attr/pillWrapperBackground"
|
||||
android:drawableTint="?attr/searchShortcutsTextColor"/>
|
||||
android:background="?attr/pillWrapperBackground"/>
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -44,6 +44,8 @@
|
|||
app:destination="@id/browserFragment"
|
||||
app:popUpTo="@id/homeFragment" />
|
||||
<argument android:name="session_id" app:argType="string" app:nullable="true"/>
|
||||
<action android:id="@+id/action_searchFragment_to_searchEngineFragment"
|
||||
app:destination="@id/searchEngineFragment" app:popUpTo="@+id/searchFragment"/>
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
|
|
|
@ -30,10 +30,11 @@
|
|||
<attr name="sessionBorderColor" format="reference" />
|
||||
|
||||
<!-- Search fragment -->
|
||||
<attr name="searchBackground" format="reference" />
|
||||
<attr name="searchShortcutsTextColor" format="reference" />
|
||||
<attr name="pillWrapperBackground" format="reference" />
|
||||
<attr name="suggestionBackground" format="reference" />
|
||||
<attr name="searchBackground" format="reference"/>
|
||||
<attr name="searchShortcutsTextColor" format="reference"/>
|
||||
<attr name="pillWrapperBackground" format="reference"/>
|
||||
<attr name="pillWrapperSelectedBackground" format="reference"/>
|
||||
<attr name="suggestionBackground" format="reference"/>
|
||||
|
||||
<!-- Browser fragment -->
|
||||
<attr name="browserUrlBarBackground" format="reference" />
|
||||
|
|
|
@ -48,8 +48,10 @@
|
|||
<color name="private_browsing_top_gradient">#242251</color>
|
||||
<color name="private_browsing_bottom_gradient">#393862</color>
|
||||
|
||||
<color name="searchPillBackground">#FAFAFC</color>
|
||||
<color name="searchPillPrimary">#202340</color>
|
||||
<color name="search_pill_background">#FAFAFC</color>
|
||||
<color name="search_pill_selected_background">#2f2c61</color>
|
||||
<color name="search_pill_private_selected_background">#080639</color>
|
||||
<color name="search_pill_primary">#202340</color>
|
||||
|
||||
<color name="library_sessions_icon_background">#B9F0FD</color>
|
||||
<color name="library_sessions_icon">#0E214A</color>
|
||||
|
|
|
@ -76,6 +76,10 @@
|
|||
<string name="search_scan_button">Scan</string>
|
||||
<!-- Button in the search view that lets a user search by using a shortcut -->
|
||||
<string name="search_shortcuts_button">Shortcuts</string>
|
||||
<!-- Button in the search view when shortcuts are displayed that takes a user to the search engine settings -->
|
||||
<string name="search_shortcuts_engine_settings">Search engine settings</string>
|
||||
<!-- Header displayed when selecting a shortcut search engine -->
|
||||
<string name="search_shortcuts_search_with">Search with</string>
|
||||
<!-- Button in the search view that lets a user navigate to the site in their clipboard -->
|
||||
<string name="awesomebar_clipboard_title">Fill link from clipboard</string>
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
<item name="searchBackground">@color/off_white</item>
|
||||
<item name="searchShortcutsTextColor">@color/awesome_bar_title_color</item>
|
||||
<item name="pillWrapperBackground">@color/off_white</item>
|
||||
<item name="pillWrapperSelectedBackground">@color/search_pill_selected_background</item>
|
||||
<item name="awesomeBarTitleTextColor">@color/awesome_bar_title_color</item>
|
||||
<item name="awesomeBarDescriptionTextColor">@color/awesome_bar_description_color</item>
|
||||
<item name="suggestionBackground">@color/photonBlue50</item>
|
||||
|
@ -103,6 +104,7 @@
|
|||
<item name="searchBackground">@color/private_browsing_bottom_gradient</item>
|
||||
<item name="searchShortcutsTextColor">@color/off_white</item>
|
||||
<item name="pillWrapperBackground">@color/private_browsing_top_gradient</item>
|
||||
<item name="pillWrapperSelectedBackground">@color/off_white</item>
|
||||
<item name="awesomeBarTitleTextColor">@color/off_white</item>
|
||||
<item name="awesomeBarDescriptionTextColor">@color/photonGrey40</item>
|
||||
<item name="suggestionBackground">@color/private_browsing_primary</item>
|
||||
|
@ -137,7 +139,7 @@
|
|||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:textAllCaps">false</item>
|
||||
<item name="android:textSize">14sp</item>
|
||||
<item name="android:textColor">@color/searchPillPrimary</item>
|
||||
<item name="android:textColor">@color/search_pill_primary</item>
|
||||
<item name="android:layout_gravity">center_vertical</item>
|
||||
<item name="android:gravity">center_vertical</item>
|
||||
<item name="android:singleLine">true</item>
|
||||
|
|
Loading…
Reference in New Issue