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/components/toolbar/BrowserToolbarView.kt

297 lines
11 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.components.toolbar
import android.view.HapticFeedbackConstants
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.annotation.VisibleForTesting
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner
import kotlinx.android.extensions.LayoutContainer
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.domains.autocomplete.ShippedDomainsProvider
import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.state.CustomTabSessionState
import mozilla.components.browser.state.state.ExternalAppType
import mozilla.components.browser.toolbar.BrowserToolbar
import mozilla.components.browser.toolbar.behavior.BrowserToolbarBehavior
import mozilla.components.browser.toolbar.display.DisplayToolbar
import mozilla.components.support.utils.URLStringUtils
import mozilla.components.ui.tabcounter.TabCounterMenu
import org.mozilla.fenix.R
import org.mozilla.fenix.customtabs.CustomTabToolbarIntegration
import org.mozilla.fenix.customtabs.CustomTabToolbarMenu
import org.mozilla.fenix.ext.bookmarkStorage
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.theme.ThemeManager
import org.mozilla.fenix.utils.ToolbarPopupWindow
import java.lang.ref.WeakReference
import mozilla.components.browser.toolbar.behavior.ToolbarPosition as MozacToolbarPosition
interface BrowserToolbarViewInteractor {
fun onBrowserToolbarPaste(text: String)
fun onBrowserToolbarPasteAndGo(text: String)
fun onBrowserToolbarClicked()
fun onBrowserToolbarMenuItemTapped(item: ToolbarMenu.Item)
fun onTabCounterClicked()
fun onTabCounterMenuItemTapped(item: TabCounterMenu.Item)
fun onScrolled(offset: Int)
fun onReaderModePressed(enabled: Boolean)
}
@ExperimentalCoroutinesApi
@SuppressWarnings("LargeClass")
class BrowserToolbarView(
private val container: ViewGroup,
private val toolbarPosition: ToolbarPosition,
private val interactor: BrowserToolbarViewInteractor,
private val customTabSession: CustomTabSessionState?,
private val lifecycleOwner: LifecycleOwner
) : LayoutContainer {
override val containerView: View?
get() = container
private val settings = container.context.settings()
@LayoutRes
private val toolbarLayout = when (settings.toolbarPosition) {
ToolbarPosition.BOTTOM -> R.layout.component_bottom_browser_toolbar
ToolbarPosition.TOP -> R.layout.component_browser_top_toolbar
}
private val layout = LayoutInflater.from(container.context)
.inflate(toolbarLayout, container, true)
@VisibleForTesting
internal var view: BrowserToolbar = layout
.findViewById(R.id.toolbar)
val toolbarIntegration: ToolbarIntegration
@VisibleForTesting
internal val isPwaTabOrTwaTab: Boolean
get() = customTabSession?.config?.externalAppType == ExternalAppType.PROGRESSIVE_WEB_APP ||
customTabSession?.config?.externalAppType == ExternalAppType.TRUSTED_WEB_ACTIVITY
init {
val isCustomTabSession = customTabSession != null
view.display.setOnUrlLongClickListener {
ToolbarPopupWindow.show(
WeakReference(view),
customTabSession,
interactor::onBrowserToolbarPasteAndGo,
interactor::onBrowserToolbarPaste
)
true
}
with(container.context) {
val isPinningSupported = components.useCases.webAppUseCases.isPinningSupported()
view.apply {
setToolbarBehavior()
elevation = resources.getDimension(R.dimen.browser_fragment_toolbar_elevation)
if (!isCustomTabSession) {
display.setUrlBackground(getDrawable(R.drawable.search_url_background))
}
display.onUrlClicked = {
interactor.onBrowserToolbarClicked()
false
}
display.progressGravity = when (toolbarPosition) {
ToolbarPosition.BOTTOM -> DisplayToolbar.Gravity.TOP
ToolbarPosition.TOP -> DisplayToolbar.Gravity.BOTTOM
}
val primaryTextColor = ContextCompat.getColor(
container.context,
ThemeManager.resolveAttribute(R.attr.primaryText, container.context)
)
val secondaryTextColor = ContextCompat.getColor(
container.context,
ThemeManager.resolveAttribute(R.attr.secondaryText, container.context)
)
val separatorColor = ContextCompat.getColor(
container.context,
ThemeManager.resolveAttribute(R.attr.toolbarDivider, container.context)
)
display.urlFormatter =
if (context.settings().shouldStripUrl) {
url -> URLStringUtils.toDisplayUrl(url)
} else {
url -> url
}
display.colors = display.colors.copy(
text = primaryTextColor,
securityIconSecure = primaryTextColor,
securityIconInsecure = primaryTextColor,
menu = primaryTextColor,
hint = secondaryTextColor,
separator = separatorColor,
trackingProtection = primaryTextColor,
highlight = ContextCompat.getColor(
context,
R.color.whats_new_notification_color
)
)
display.hint = context.getString(R.string.search_hint)
}
val menuToolbar: ToolbarMenu
if (isCustomTabSession) {
menuToolbar = CustomTabToolbarMenu(
context = this,
store = components.core.store,
sessionId = customTabSession?.id,
shouldReverseItems = toolbarPosition == ToolbarPosition.TOP,
onItemTapped = {
it.performHapticIfNeeded(view)
interactor.onBrowserToolbarMenuItemTapped(it)
}
)
} else {
menuToolbar = DefaultToolbarMenu(
context = this,
hasAccountProblem = components.backgroundServices.accountManager.accountNeedsReauth(),
shouldReverseItems = toolbarPosition == ToolbarPosition.TOP,
onItemTapped = {
it.performHapticIfNeeded(view)
interactor.onBrowserToolbarMenuItemTapped(it)
},
lifecycleOwner = lifecycleOwner,
store = components.core.store,
bookmarksStorage = bookmarkStorage,
isPinningSupported = isPinningSupported
)
view.display.setMenuDismissAction {
view.invalidateActions()
}
}
toolbarIntegration = if (customTabSession != null) {
CustomTabToolbarIntegration(
this,
view,
menuToolbar,
customTabSession.id,
isPrivate = customTabSession.content.private
)
} else {
DefaultToolbarIntegration(
this,
view,
menuToolbar,
ShippedDomainsProvider().also { it.initialize(this) },
components.core.historyStorage,
lifecycleOwner,
sessionId = null,
isPrivate = components.core.store.state.selectedTab?.content?.private ?: false,
interactor = interactor,
engine = components.core.engine
)
}
}
}
fun expand() {
// expand only for normal tabs and custom tabs not for PWA or TWA
if (isPwaTabOrTwaTab) {
return
}
(view.layoutParams as? CoordinatorLayout.LayoutParams)?.apply {
(behavior as? BrowserToolbarBehavior)?.forceExpand(view)
}
}
fun collapse() {
// collapse only for normal tabs and custom tabs not for PWA or TWA. Mirror expand()
if (isPwaTabOrTwaTab) {
return
}
(view.layoutParams as? CoordinatorLayout.LayoutParams)?.apply {
(behavior as? BrowserToolbarBehavior)?.forceCollapse(view)
}
}
fun dismissMenu() {
view.dismissMenu()
}
/**
* Sets whether the toolbar will have a dynamic behavior (to be scrolled) or not.
*
* This will intrinsically check and disable the dynamic behavior if
* - this is disabled in app settings
* - toolbar is placed at the bottom and tab shows a PWA or TWA
*
* Also if the user has not explicitly set a toolbar position and has a screen reader enabled
* the toolbar will be placed at the top and in a fixed position.
*
* @param shouldDisableScroll force disable of the dynamic behavior irrespective of the intrinsic checks.
*/
fun setToolbarBehavior(shouldDisableScroll: Boolean = false) {
when (settings.toolbarPosition) {
ToolbarPosition.BOTTOM -> {
if (settings.isDynamicToolbarEnabled && !isPwaTabOrTwaTab && !settings.shouldUseFixedTopToolbar) {
setDynamicToolbarBehavior(MozacToolbarPosition.BOTTOM)
} else {
expandToolbarAndMakeItFixed()
}
}
ToolbarPosition.TOP -> {
if (settings.shouldUseFixedTopToolbar ||
!settings.isDynamicToolbarEnabled ||
shouldDisableScroll
) {
expandToolbarAndMakeItFixed()
} else {
setDynamicToolbarBehavior(MozacToolbarPosition.TOP)
}
}
}
}
@VisibleForTesting
internal fun expandToolbarAndMakeItFixed() {
expand()
(view.layoutParams as? CoordinatorLayout.LayoutParams)?.apply {
behavior = null
}
}
@VisibleForTesting
internal fun setDynamicToolbarBehavior(toolbarPosition: MozacToolbarPosition) {
(view.layoutParams as? CoordinatorLayout.LayoutParams)?.apply {
behavior = BrowserToolbarBehavior(view.context, null, toolbarPosition)
}
}
@Suppress("ComplexCondition")
private fun ToolbarMenu.Item.performHapticIfNeeded(view: View) {
if (this is ToolbarMenu.Item.Reload && this.bypassCache ||
this is ToolbarMenu.Item.Back && this.viewHistory ||
this is ToolbarMenu.Item.Forward && this.viewHistory
) {
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
}
}
}