You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
128 lines
5.2 KiB
Kotlin
128 lines
5.2 KiB
Kotlin
/* 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.toolbar
|
|
|
|
import android.content.Context
|
|
import android.graphics.Bitmap
|
|
import android.graphics.drawable.BitmapDrawable
|
|
import android.view.View
|
|
import android.view.ViewGroup
|
|
import androidx.annotation.VisibleForTesting
|
|
import kotlinx.coroutines.Job
|
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
import kotlinx.coroutines.flow.filterNotNull
|
|
import kotlinx.coroutines.flow.map
|
|
import kotlinx.coroutines.launch
|
|
import mozilla.components.browser.state.search.SearchEngine
|
|
import mozilla.components.concept.menu.Orientation
|
|
import mozilla.components.concept.toolbar.Toolbar
|
|
import mozilla.components.lib.state.ext.flow
|
|
import mozilla.components.support.ktx.android.content.getColorFromAttr
|
|
import mozilla.components.support.ktx.android.content.res.resolveAttribute
|
|
import mozilla.components.support.ktx.android.view.toScope
|
|
import mozilla.telemetry.glean.private.NoExtras
|
|
import org.mozilla.fenix.GleanMetrics.UnifiedSearch
|
|
import org.mozilla.fenix.R
|
|
import org.mozilla.fenix.ext.settings
|
|
import org.mozilla.fenix.search.SearchDialogFragmentStore
|
|
|
|
/**
|
|
* A [Toolbar.Action] implementation that shows a [SearchSelector].
|
|
*
|
|
* @property store [SearchDialogFragmentStore] containing the complete state of the search dialog.
|
|
* @property defaultSearchEngine The user selected or default [SearchEngine].
|
|
* @property menu An instance of [SearchSelectorMenu] to display a popup menu for the search
|
|
* selections.
|
|
*/
|
|
class SearchSelectorToolbarAction(
|
|
private val store: SearchDialogFragmentStore,
|
|
private val defaultSearchEngine: SearchEngine?,
|
|
private val menu: SearchSelectorMenu,
|
|
) : Toolbar.Action {
|
|
private var updateIconJob: Job? = null
|
|
|
|
override fun createView(parent: ViewGroup): View {
|
|
val context = parent.context
|
|
|
|
// Only search engines with type APPLICATION (tabs, history, bookmarks) will have a valid icon at this time.
|
|
// For other search engines show the icon of the default search engine which should be shown in the selector.
|
|
val initialSearchEngine = store.state.searchEngineSource.searchEngine ?: defaultSearchEngine
|
|
return SearchSelector(context).apply {
|
|
initialSearchEngine?.let {
|
|
this.setIcon(
|
|
icon = initialSearchEngine.getScaledIcon(this.context),
|
|
contentDescription = initialSearchEngine.name,
|
|
)
|
|
}
|
|
|
|
setOnClickListener {
|
|
val orientation = if (context.settings().shouldUseBottomToolbar) {
|
|
Orientation.UP
|
|
} else {
|
|
Orientation.DOWN
|
|
}
|
|
|
|
UnifiedSearch.searchMenuTapped.record(NoExtras())
|
|
menu.menuController.show(
|
|
anchor = it.findViewById(R.id.search_selector),
|
|
orientation = orientation,
|
|
)
|
|
}
|
|
|
|
val topPadding = resources.getDimensionPixelSize(R.dimen.search_engine_engine_icon_top_margin)
|
|
setPadding(0, topPadding, 0, 0)
|
|
|
|
setBackgroundResource(
|
|
context.theme.resolveAttribute(android.R.attr.selectableItemBackgroundBorderless),
|
|
)
|
|
}
|
|
}
|
|
|
|
override fun bind(view: View) {
|
|
// It may happen that this View is binded multiple times.
|
|
// Prevent launching new coroutines for every time this is binded and only update the icon once.
|
|
if (updateIconJob?.isActive != true) {
|
|
updateIconJob = (view as? SearchSelector)?.toScope()?.launch {
|
|
store.flow()
|
|
.map { state -> state.searchEngineSource.searchEngine }
|
|
.filterNotNull()
|
|
.distinctUntilChanged()
|
|
.collect { searchEngine ->
|
|
view.setIcon(
|
|
icon = searchEngine.getScaledIcon(view.context).apply {
|
|
// Setting tint manually for icons that were converted from Drawable
|
|
// to Bitmap. Search Engine icons are stored as Bitmaps, hence
|
|
// theming/attribute mechanism won't work.
|
|
if (searchEngine.type == SearchEngine.Type.APPLICATION) {
|
|
setTint(view.context.getColorFromAttr(R.attr.textPrimary))
|
|
}
|
|
},
|
|
contentDescription = searchEngine.name,
|
|
)
|
|
}
|
|
}.also {
|
|
it?.start()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the search engine icon appropriately scaled to be shown in the selector.
|
|
*/
|
|
@VisibleForTesting
|
|
internal fun SearchEngine.getScaledIcon(context: Context): BitmapDrawable {
|
|
val iconSize =
|
|
context.resources.getDimensionPixelSize(R.dimen.preference_icon_drawable_size)
|
|
val scaledIcon = Bitmap.createScaledBitmap(
|
|
icon,
|
|
iconSize,
|
|
iconSize,
|
|
true,
|
|
)
|
|
|
|
return BitmapDrawable(context.resources, scaledIcon)
|
|
}
|