|
|
|
/* 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.awesomebar
|
|
|
|
|
|
|
|
import android.view.ViewGroup
|
|
|
|
import androidx.appcompat.content.res.AppCompatResources.getDrawable
|
|
|
|
import androidx.core.graphics.BlendModeColorFilterCompat.createBlendModeColorFilterCompat
|
|
|
|
import androidx.core.graphics.BlendModeCompat.SRC_IN
|
|
|
|
import androidx.core.graphics.drawable.toBitmap
|
|
|
|
import mozilla.components.browser.awesomebar.BrowserAwesomeBar
|
|
|
|
import mozilla.components.browser.search.SearchEngine
|
|
|
|
import mozilla.components.browser.session.Session
|
|
|
|
import mozilla.components.concept.awesomebar.AwesomeBar
|
|
|
|
import mozilla.components.concept.engine.EngineSession
|
|
|
|
import mozilla.components.feature.awesomebar.provider.BookmarksStorageSuggestionProvider
|
|
|
|
import mozilla.components.feature.awesomebar.provider.HistoryStorageSuggestionProvider
|
|
|
|
import mozilla.components.feature.awesomebar.provider.SearchActionProvider
|
|
|
|
import mozilla.components.feature.awesomebar.provider.SearchSuggestionProvider
|
|
|
|
import mozilla.components.feature.awesomebar.provider.SessionSuggestionProvider
|
|
|
|
import mozilla.components.feature.search.SearchUseCases
|
|
|
|
import mozilla.components.feature.session.SessionUseCases
|
|
|
|
import mozilla.components.feature.tabs.TabsUseCases
|
|
|
|
import mozilla.components.support.ktx.android.content.getColorFromAttr
|
|
|
|
import org.mozilla.fenix.HomeActivity
|
|
|
|
import org.mozilla.fenix.R
|
|
|
|
import org.mozilla.fenix.ext.asActivity
|
|
|
|
import org.mozilla.fenix.ext.components
|
|
|
|
import org.mozilla.fenix.search.SearchEngineSource
|
|
|
|
import org.mozilla.fenix.search.SearchFragmentState
|
|
|
|
|
|
|
|
/**
|
|
|
|
* View that contains and configures the BrowserAwesomeBar
|
|
|
|
*/
|
|
|
|
class AwesomeBarView(
|
|
|
|
private val container: ViewGroup,
|
|
|
|
val interactor: AwesomeBarInteractor,
|
|
|
|
val view: BrowserAwesomeBar
|
|
|
|
) {
|
|
|
|
|
|
|
|
private val sessionProvider: SessionSuggestionProvider
|
|
|
|
private val historyStorageProvider: HistoryStorageSuggestionProvider
|
|
|
|
private val shortcutsEnginePickerProvider: ShortcutsSuggestionProvider
|
|
|
|
private val bookmarksStorageSuggestionProvider: BookmarksStorageSuggestionProvider
|
|
|
|
private val defaultSearchSuggestionProvider: SearchSuggestionProvider
|
|
|
|
private val defaultSearchActionProvider: SearchActionProvider
|
|
|
|
private val searchSuggestionProviderMap: MutableMap<SearchEngine, List<AwesomeBar.SuggestionProvider>>
|
|
|
|
private var providersInUse = mutableSetOf<AwesomeBar.SuggestionProvider>()
|
|
|
|
|
|
|
|
private val loadUrlUseCase = object : SessionUseCases.LoadUrlUseCase {
|
|
|
|
override fun invoke(
|
|
|
|
url: String,
|
|
|
|
flags: EngineSession.LoadUrlFlags,
|
|
|
|
additionalHeaders: Map<String, String>?
|
|
|
|
) {
|
|
|
|
interactor.onUrlTapped(url)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private val searchUseCase = object : SearchUseCases.SearchUseCase {
|
|
|
|
override fun invoke(
|
|
|
|
searchTerms: String,
|
|
|
|
searchEngine: SearchEngine?,
|
|
|
|
parentSession: Session?
|
|
|
|
) {
|
|
|
|
interactor.onSearchTermsTapped(searchTerms)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private val shortcutSearchUseCase = object : SearchUseCases.SearchUseCase {
|
|
|
|
override fun invoke(
|
|
|
|
searchTerms: String,
|
|
|
|
searchEngine: SearchEngine?,
|
|
|
|
parentSession: Session?
|
|
|
|
) {
|
|
|
|
interactor.onSearchTermsTapped(searchTerms)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private val selectTabUseCase = object : TabsUseCases.SelectTabUseCase {
|
|
|
|
override fun invoke(session: Session) {
|
|
|
|
interactor.onExistingSessionSelected(session)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun invoke(tabId: String) {
|
|
|
|
interactor.onExistingSessionSelected(tabId)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
init {
|
|
|
|
view.itemAnimator = null
|
|
|
|
|
|
|
|
val context = container.context
|
|
|
|
val components = context.components
|
|
|
|
val primaryTextColor = context.getColorFromAttr(R.attr.primaryText)
|
|
|
|
|
|
|
|
val draw = getDrawable(context, R.drawable.ic_link)!!
|
|
|
|
draw.colorFilter = createBlendModeColorFilterCompat(primaryTextColor, SRC_IN)
|
|
|
|
|
|
|
|
val engineForSpeculativeConnects =
|
|
|
|
if (!isBrowsingModePrivate()) components.core.engine else null
|
|
|
|
sessionProvider =
|
|
|
|
SessionSuggestionProvider(
|
|
|
|
context.resources,
|
|
|
|
components.core.store,
|
|
|
|
selectTabUseCase,
|
|
|
|
components.core.icons,
|
|
|
|
excludeSelectedSession = true
|
|
|
|
)
|
|
|
|
|
|
|
|
historyStorageProvider =
|
|
|
|
HistoryStorageSuggestionProvider(
|
|
|
|
components.core.historyStorage,
|
|
|
|
loadUrlUseCase,
|
|
|
|
components.core.icons,
|
|
|
|
engineForSpeculativeConnects
|
|
|
|
)
|
|
|
|
|
|
|
|
bookmarksStorageSuggestionProvider =
|
|
|
|
BookmarksStorageSuggestionProvider(
|
|
|
|
bookmarksStorage = components.core.bookmarksStorage,
|
|
|
|
loadUrlUseCase = loadUrlUseCase,
|
|
|
|
icons = components.core.icons,
|
|
|
|
engine = engineForSpeculativeConnects
|
|
|
|
)
|
|
|
|
|
|
|
|
val searchBitmap = getDrawable(context, R.drawable.ic_search)!!.apply {
|
|
|
|
colorFilter = createBlendModeColorFilterCompat(primaryTextColor, SRC_IN)
|
|
|
|
}.toBitmap()
|
|
|
|
|
|
|
|
defaultSearchSuggestionProvider =
|
|
|
|
SearchSuggestionProvider(
|
|
|
|
context = context,
|
|
|
|
searchEngineManager = components.search.searchEngineManager,
|
|
|
|
searchUseCase = searchUseCase,
|
|
|
|
fetchClient = components.core.client,
|
|
|
|
mode = SearchSuggestionProvider.Mode.MULTIPLE_SUGGESTIONS,
|
|
|
|
limit = 3,
|
|
|
|
icon = searchBitmap,
|
|
|
|
showDescription = false,
|
|
|
|
engine = engineForSpeculativeConnects,
|
|
|
|
filterExactMatch = true
|
|
|
|
)
|
|
|
|
|
|
|
|
defaultSearchActionProvider =
|
|
|
|
SearchActionProvider(
|
|
|
|
searchEngineGetter = suspend {
|
|
|
|
components.search.searchEngineManager.getDefaultSearchEngineAsync(context)
|
|
|
|
},
|
|
|
|
searchUseCase = searchUseCase,
|
|
|
|
icon = searchBitmap,
|
|
|
|
showDescription = false
|
|
|
|
)
|
|
|
|
|
|
|
|
shortcutsEnginePickerProvider =
|
|
|
|
ShortcutsSuggestionProvider(
|
|
|
|
searchEngineProvider = components.search.provider,
|
|
|
|
context = context,
|
|
|
|
selectShortcutEngine = interactor::onSearchShortcutEngineSelected,
|
|
|
|
selectShortcutEngineSettings = interactor::onClickSearchEngineSettings
|
|
|
|
)
|
|
|
|
|
|
|
|
searchSuggestionProviderMap = HashMap()
|
|
|
|
}
|
|
|
|
|
|
|
|
fun update(state: SearchFragmentState) {
|
|
|
|
updateSuggestionProvidersVisibility(state)
|
|
|
|
|
|
|
|
// Do not make suggestions based on user's current URL unless it's a search shortcut
|
|
|
|
if (state.query == state.url && !state.showSearchShortcuts) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
view.onInputChanged(state.query)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun updateSuggestionProvidersVisibility(state: SearchFragmentState) {
|
|
|
|
if (state.showSearchShortcuts) {
|
|
|
|
handleDisplayShortcutsProviders()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
val providersToAdd = getProvidersToAdd(state)
|
|
|
|
val providersToRemove = getProvidersToRemove(state)
|
|
|
|
|
|
|
|
performProviderListChanges(providersToAdd, providersToRemove)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun performProviderListChanges(
|
|
|
|
providersToAdd: MutableSet<AwesomeBar.SuggestionProvider>,
|
|
|
|
providersToRemove: MutableSet<AwesomeBar.SuggestionProvider>
|
|
|
|
) {
|
|
|
|
for (provider in providersToAdd) {
|
|
|
|
if (providersInUse.find { it.id == provider.id } == null) {
|
|
|
|
providersInUse.add(provider)
|
|
|
|
view.addProviders(provider)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (provider in providersToRemove) {
|
|
|
|
if (providersInUse.find { it.id == provider.id } != null) {
|
|
|
|
providersInUse.remove(provider)
|
|
|
|
view.removeProviders(provider)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun getProvidersToAdd(state: SearchFragmentState): MutableSet<AwesomeBar.SuggestionProvider> {
|
|
|
|
val providersToAdd = mutableSetOf<AwesomeBar.SuggestionProvider>()
|
|
|
|
|
|
|
|
if (state.showHistorySuggestions) {
|
|
|
|
providersToAdd.add(historyStorageProvider)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state.showBookmarkSuggestions) {
|
|
|
|
providersToAdd.add(bookmarksStorageSuggestionProvider)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state.showSearchSuggestions) {
|
|
|
|
providersToAdd.addAll(getSelectedSearchSuggestionProvider(state))
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isBrowsingModePrivate()) {
|
|
|
|
providersToAdd.add(sessionProvider)
|
|
|
|
}
|
|
|
|
|
|
|
|
return providersToAdd
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun getProvidersToRemove(state: SearchFragmentState): MutableSet<AwesomeBar.SuggestionProvider> {
|
|
|
|
val providersToRemove = mutableSetOf<AwesomeBar.SuggestionProvider>()
|
|
|
|
|
|
|
|
providersToRemove.add(shortcutsEnginePickerProvider)
|
|
|
|
|
|
|
|
if (!state.showHistorySuggestions) {
|
|
|
|
providersToRemove.add(historyStorageProvider)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!state.showBookmarkSuggestions) {
|
|
|
|
providersToRemove.add(bookmarksStorageSuggestionProvider)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!state.showSearchSuggestions) {
|
|
|
|
providersToRemove.addAll(getSelectedSearchSuggestionProvider(state))
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isBrowsingModePrivate()) {
|
|
|
|
providersToRemove.add(sessionProvider)
|
|
|
|
}
|
|
|
|
|
|
|
|
return providersToRemove
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun isBrowsingModePrivate(): Boolean {
|
|
|
|
return (container.context.asActivity() as? HomeActivity)?.browsingModeManager?.mode?.isPrivate
|
|
|
|
?: false
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun getSelectedSearchSuggestionProvider(state: SearchFragmentState): List<AwesomeBar.SuggestionProvider> {
|
|
|
|
return when (state.searchEngineSource) {
|
|
|
|
is SearchEngineSource.Default -> listOf(
|
|
|
|
defaultSearchActionProvider,
|
|
|
|
defaultSearchSuggestionProvider
|
|
|
|
)
|
|
|
|
is SearchEngineSource.Shortcut -> getSuggestionProviderForEngine(
|
|
|
|
state.searchEngineSource.searchEngine
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun handleDisplayShortcutsProviders() {
|
|
|
|
view.removeAllProviders()
|
|
|
|
providersInUse.clear()
|
|
|
|
providersInUse.add(shortcutsEnginePickerProvider)
|
|
|
|
view.addProviders(shortcutsEnginePickerProvider)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun getSuggestionProviderForEngine(engine: SearchEngine): List<AwesomeBar.SuggestionProvider> {
|
|
|
|
return searchSuggestionProviderMap.getOrPut(engine) {
|
|
|
|
val context = container.context
|
|
|
|
val components = context.components
|
|
|
|
val primaryTextColor = context.getColorFromAttr(R.attr.primaryText)
|
|
|
|
|
|
|
|
val searchBitmap = getDrawable(context, R.drawable.ic_search)?.apply {
|
|
|
|
colorFilter = createBlendModeColorFilterCompat(primaryTextColor, SRC_IN)
|
|
|
|
}?.toBitmap()
|
|
|
|
|
|
|
|
val engineForSpeculativeConnects =
|
|
|
|
if (!isBrowsingModePrivate()) components.core.engine else null
|
|
|
|
val searchEngine =
|
|
|
|
components.search.provider.installedSearchEngines(context).list.find { it.name == engine.name }
|
|
|
|
?: components.search.provider.getDefaultEngine(context)
|
|
|
|
|
|
|
|
listOf(
|
|
|
|
SearchActionProvider(
|
|
|
|
searchEngineGetter = suspend { searchEngine },
|
|
|
|
searchUseCase = shortcutSearchUseCase,
|
|
|
|
icon = searchBitmap
|
|
|
|
),
|
|
|
|
SearchSuggestionProvider(
|
|
|
|
searchEngine,
|
|
|
|
shortcutSearchUseCase,
|
|
|
|
components.core.client,
|
|
|
|
limit = 3,
|
|
|
|
mode = SearchSuggestionProvider.Mode.MULTIPLE_SUGGESTIONS,
|
|
|
|
icon = searchBitmap,
|
|
|
|
engine = engineForSpeculativeConnects,
|
|
|
|
filterExactMatch = true
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|