1
0
Fork 0

Closes #875: Adds search shortcuts (#882)

* Closes #875: Adds shortcuts

* Refactor and clean up

* Remove TODO

* Removes local

* Fix nits

* Refactors to add ShortcutEngineManager
master
Sawyer Blatz 2019-03-29 13:49:50 -07:00 committed by GitHub
parent 4f67b7a26c
commit 36af5107c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 441 additions and 75 deletions

View File

@ -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()) {

View File

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

View File

@ -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()
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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