195 lines
7.1 KiB
Kotlin
195 lines
7.1 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.graphics.Bitmap
|
|
import android.graphics.drawable.BitmapDrawable
|
|
import android.view.LayoutInflater
|
|
import android.view.View
|
|
import android.view.ViewGroup
|
|
import androidx.annotation.LayoutRes
|
|
import androidx.appcompat.content.res.AppCompatResources
|
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
import androidx.core.content.ContextCompat
|
|
import com.google.android.material.appbar.AppBarLayout
|
|
import com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS
|
|
import com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED
|
|
import com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL
|
|
import com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP
|
|
import kotlinx.android.extensions.LayoutContainer
|
|
import mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider
|
|
import mozilla.components.browser.toolbar.BrowserToolbar
|
|
import mozilla.components.concept.storage.HistoryStorage
|
|
import mozilla.components.feature.toolbar.ToolbarAutocompleteFeature
|
|
import mozilla.components.support.ktx.android.util.dpToPx
|
|
import org.mozilla.fenix.R
|
|
import org.mozilla.fenix.ext.getColorFromAttr
|
|
import org.mozilla.fenix.ext.settings
|
|
import org.mozilla.fenix.search.SearchFragmentState
|
|
import org.mozilla.fenix.theme.ThemeManager
|
|
|
|
/**
|
|
* Interface for the Toolbar Interactor. This interface is implemented by objects that want
|
|
* to respond to user interaction on the [BrowserToolbarView]
|
|
*/
|
|
interface ToolbarInteractor {
|
|
|
|
/**
|
|
* Called when a user hits the return key while [BrowserToolbarView] has focus.
|
|
* @param url the text inside the [BrowserToolbarView] when committed
|
|
*/
|
|
fun onUrlCommitted(url: String)
|
|
|
|
/**
|
|
* Called when a user removes focus from the [BrowserToolbarView]
|
|
*/
|
|
fun onEditingCanceled()
|
|
|
|
/**
|
|
* Called whenever the text inside the [BrowserToolbarView] changes
|
|
* @param text the current text displayed by [BrowserToolbarView]
|
|
*/
|
|
fun onTextChanged(text: String)
|
|
}
|
|
|
|
/**
|
|
* View that contains and configures the BrowserToolbar to only be used in its editing mode.
|
|
*/
|
|
class ToolbarView(
|
|
private val container: ViewGroup,
|
|
private val interactor: ToolbarInteractor,
|
|
private val historyStorage: HistoryStorage?,
|
|
private val isPrivate: Boolean
|
|
) : LayoutContainer {
|
|
|
|
override val containerView: View?
|
|
get() = container
|
|
|
|
private val settings = container.context.settings()
|
|
|
|
@LayoutRes
|
|
private val toolbarLayout = when {
|
|
settings.shouldUseBottomToolbar -> R.layout.component_bottom_browser_toolbar
|
|
else -> R.layout.component_browser_top_toolbar
|
|
}
|
|
|
|
val view: BrowserToolbar = LayoutInflater.from(container.context)
|
|
.inflate(toolbarLayout, container, true)
|
|
.findViewById(R.id.toolbar)
|
|
|
|
private var isInitialized = false
|
|
private var hasBeenCanceled = false
|
|
|
|
init {
|
|
view.apply {
|
|
editMode()
|
|
|
|
setScrollFlagsForTopToolbar()
|
|
|
|
elevation = TOOLBAR_ELEVATION_IN_DP.dpToPx(resources.displayMetrics).toFloat()
|
|
|
|
setOnUrlCommitListener {
|
|
interactor.onUrlCommitted(it)
|
|
false
|
|
}
|
|
|
|
background =
|
|
AppCompatResources.getDrawable(
|
|
container.context, ThemeManager.resolveAttribute(R.attr.foundation, context)
|
|
)
|
|
|
|
layoutParams.height = CoordinatorLayout.LayoutParams.MATCH_PARENT
|
|
|
|
edit.hint = context.getString(R.string.search_hint)
|
|
|
|
edit.colors = edit.colors.copy(
|
|
text = container.context.getColorFromAttr(R.attr.primaryText),
|
|
hint = container.context.getColorFromAttr(R.attr.secondaryText),
|
|
suggestionBackground = ContextCompat.getColor(
|
|
container.context,
|
|
R.color.suggestion_highlight_color
|
|
),
|
|
clear = container.context.getColorFromAttr(R.attr.primaryText)
|
|
)
|
|
|
|
edit.setUrlBackground(
|
|
AppCompatResources.getDrawable(container.context, R.drawable.search_url_background))
|
|
|
|
private = isPrivate
|
|
|
|
setOnEditListener(object : mozilla.components.concept.toolbar.Toolbar.OnEditListener {
|
|
override fun onCancelEditing(): Boolean {
|
|
// For some reason, this can be triggered twice on one back press. This only leads to
|
|
// navigateUp, so let's make sure we only call it once
|
|
if (!hasBeenCanceled) interactor.onEditingCanceled()
|
|
hasBeenCanceled = true
|
|
// We need to return false to not show display mode
|
|
return false
|
|
}
|
|
override fun onTextChanged(text: String) {
|
|
url = text
|
|
this@ToolbarView.interactor.onTextChanged(text)
|
|
}
|
|
})
|
|
}
|
|
|
|
ToolbarAutocompleteFeature(view).apply {
|
|
addDomainProvider(ShippedDomainsProvider().also { it.initialize(view.context) })
|
|
historyStorage?.also(::addHistoryStorageProvider)
|
|
}
|
|
}
|
|
|
|
fun update(searchState: SearchFragmentState) {
|
|
if (!isInitialized) {
|
|
view.url = searchState.pastedText ?: searchState.query
|
|
|
|
/* Only set the search terms if pasted text is null so that the search term doesn't
|
|
overwrite pastedText when view enters `editMode` */
|
|
if (searchState.pastedText.isNullOrEmpty()) {
|
|
view.setSearchTerms(searchState.session?.searchTerms.orEmpty())
|
|
}
|
|
|
|
view.editMode()
|
|
isInitialized = true
|
|
}
|
|
|
|
val iconSize = container.resources.getDimensionPixelSize(R.dimen.preference_icon_drawable_size)
|
|
|
|
val scaledIcon = Bitmap.createScaledBitmap(
|
|
searchState.searchEngineSource.searchEngine.icon,
|
|
iconSize,
|
|
iconSize,
|
|
true)
|
|
|
|
val icon = BitmapDrawable(container.resources, scaledIcon)
|
|
|
|
view.edit.setIcon(icon, searchState.searchEngineSource.searchEngine.name)
|
|
}
|
|
|
|
companion object {
|
|
private const val TOOLBAR_ELEVATION_IN_DP = 16
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dynamically sets scroll flags for the top toolbar when the user does not have a screen reader enabled
|
|
* Note that the bottom toolbar is currently fixed and will never have scroll flags set
|
|
*/
|
|
fun BrowserToolbar.setScrollFlagsForTopToolbar() {
|
|
// Don't set scroll flags for bottom toolbar
|
|
if (context.settings().shouldUseBottomToolbar) {
|
|
return
|
|
}
|
|
|
|
val params = layoutParams as AppBarLayout.LayoutParams
|
|
params.scrollFlags = when (context.settings().shouldUseFixedTopToolbar) {
|
|
true -> 0
|
|
false -> {
|
|
SCROLL_FLAG_SCROLL or SCROLL_FLAG_ENTER_ALWAYS or SCROLL_FLAG_SNAP or
|
|
SCROLL_FLAG_EXIT_UNTIL_COLLAPSED
|
|
}
|
|
}
|
|
}
|