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.
iceraven-browser/app/src/main/java/org/mozilla/fenix/search/toolbar/SearchSelectorToolbarAction.kt

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