1
0
Fork 0
fenix/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt

315 lines
14 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.app.Activity
import android.content.Intent
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.view.View
import android.view.ViewGroup
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import androidx.navigation.NavOptions
import androidx.navigation.fragment.FragmentNavigator
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.concept.engine.EngineView
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.support.ktx.kotlin.isUrl
import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.BrowserFragment
import org.mozilla.fenix.browser.BrowserFragmentDirections
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
import org.mozilla.fenix.browser.readermode.ReaderModeController
import org.mozilla.fenix.collections.SaveCollectionStep
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.TabCollectionStorage
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.lib.Do
import org.mozilla.fenix.settings.deletebrowsingdata.deleteAndQuit
/**
* An interface that handles the view manipulation of the BrowserToolbar, triggered by the Interactor
*/
interface BrowserToolbarController {
fun handleToolbarPaste(text: String)
fun handleToolbarPasteAndGo(text: String)
fun handleToolbarItemInteraction(item: ToolbarMenu.Item)
fun handleToolbarClick()
fun handleTabCounterClick()
}
@Suppress("LargeClass")
class DefaultBrowserToolbarController(
private val store: BrowserFragmentStore,
private val activity: Activity,
private val snackbar: FenixSnackbar?,
private val navController: NavController,
private val readerModeController: ReaderModeController,
private val browsingModeManager: BrowsingModeManager,
private val sessionManager: SessionManager,
private val findInPageLauncher: () -> Unit,
private val browserLayout: ViewGroup,
private val engineView: EngineView,
private val adjustBackgroundAndNavigate: (NavDirections) -> Unit,
private val swipeRefresh: SwipeRefreshLayout,
private val customTabSession: Session?,
private val getSupportUrl: () -> String,
private val openInFenixIntent: Intent,
private val bookmarkTapped: (Session) -> Unit,
private val scope: LifecycleCoroutineScope,
private val tabCollectionStorage: TabCollectionStorage
) : BrowserToolbarController {
private val currentSession
get() = customTabSession ?: activity.components.core.sessionManager.selectedSession
override fun handleToolbarPaste(text: String) {
adjustBackgroundAndNavigate.invoke(
BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
sessionId = currentSession?.id,
pastedText = text
)
)
}
override fun handleToolbarPasteAndGo(text: String) {
if (text.isUrl()) {
activity.components.core.sessionManager.selectedSession?.searchTerms = ""
activity.components.useCases.sessionUseCases.loadUrl.invoke(text)
return
}
activity.components.core.sessionManager.selectedSession?.searchTerms = text
activity.components.useCases.searchUseCases.defaultSearch.invoke(text)
}
override fun handleToolbarClick() {
activity.components.analytics.metrics.track(
Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER)
)
adjustBackgroundAndNavigate.invoke(
BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(currentSession?.id)
)
}
override fun handleTabCounterClick() {
animateTabAndNavigateHome()
}
@ExperimentalCoroutinesApi
@SuppressWarnings("ComplexMethod", "LongMethod")
override fun handleToolbarItemInteraction(item: ToolbarMenu.Item) {
val sessionUseCases = activity.components.useCases.sessionUseCases
trackToolbarItemInteraction(item)
Do exhaustive when (item) {
ToolbarMenu.Item.Back -> sessionUseCases.goBack.invoke(currentSession)
ToolbarMenu.Item.Forward -> sessionUseCases.goForward.invoke(currentSession)
ToolbarMenu.Item.Reload -> sessionUseCases.reload.invoke(currentSession)
ToolbarMenu.Item.Stop -> sessionUseCases.stopLoading.invoke(currentSession)
ToolbarMenu.Item.Settings -> adjustBackgroundAndNavigate.invoke(
BrowserFragmentDirections.actionBrowserFragmentToSettingsFragment()
)
ToolbarMenu.Item.Library -> adjustBackgroundAndNavigate.invoke(
BrowserFragmentDirections.actionBrowserFragmentToLibraryFragment()
)
is ToolbarMenu.Item.RequestDesktop -> sessionUseCases.requestDesktopSite.invoke(
item.isChecked,
currentSession
)
ToolbarMenu.Item.AddToHomeScreen -> {
MainScope().launch {
with(activity.components.useCases.webAppUseCases) {
if (isInstallable()) {
addToHomescreen()
} else {
val directions =
BrowserFragmentDirections.actionBrowserFragmentToCreateShortcutFragment()
navController.navigate(directions)
}
}
}
}
ToolbarMenu.Item.Share -> {
val directions = NavGraphDirections.actionGlobalShareFragment(
data = arrayOf(ShareData(url = currentSession?.url, title = currentSession?.title)),
showPage = true
)
navController.navigate(directions)
}
ToolbarMenu.Item.NewTab -> {
val directions = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
sessionId = null
)
adjustBackgroundAndNavigate.invoke(directions)
browsingModeManager.mode = BrowsingMode.Normal
}
ToolbarMenu.Item.NewPrivateTab -> {
val directions = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
sessionId = null
)
adjustBackgroundAndNavigate.invoke(directions)
browsingModeManager.mode = BrowsingMode.Private
}
ToolbarMenu.Item.FindInPage -> {
findInPageLauncher()
activity.components.analytics.metrics.track(Event.FindInPageOpened)
}
ToolbarMenu.Item.ReportIssue -> {
val currentUrl = currentSession?.url
currentUrl?.apply {
val reportUrl = String.format(BrowserFragment.REPORT_SITE_ISSUE_URL, this)
activity.components.useCases.tabsUseCases.addTab.invoke(reportUrl)
}
}
ToolbarMenu.Item.Help -> {
activity.components.useCases.tabsUseCases.addTab.invoke(getSupportUrl())
}
ToolbarMenu.Item.SaveToCollection -> {
activity.components.analytics.metrics
.track(Event.CollectionSaveButtonPressed(TELEMETRY_BROWSER_IDENTIFIER))
currentSession?.let { currentSession ->
val directions = BrowserFragmentDirections.actionBrowserFragmentToCreateCollectionFragment(
previousFragmentId = R.id.browserFragment,
tabIds = arrayOf(currentSession.id),
selectedTabIds = arrayOf(currentSession.id),
saveCollectionStep = if (tabCollectionStorage.cachedTabCollections.isEmpty()) {
SaveCollectionStep.NameCollection
} else {
SaveCollectionStep.SelectCollection
}
)
navController.nav(R.id.browserFragment, directions)
}
}
ToolbarMenu.Item.OpenInFenix -> {
// Release the session from this view so that it can immediately be rendered by a different view
engineView.release()
// Strip the CustomTabConfig to turn this Session into a regular tab and then select it
customTabSession!!.customTabConfig = null
activity.components.core.sessionManager.select(customTabSession)
// Switch to the actual browser which should now display our new selected session
activity.startActivity(openInFenixIntent)
// Close this activity since it is no longer displaying any session
activity.finish()
}
ToolbarMenu.Item.Quit -> deleteAndQuit(activity, scope, snackbar)
is ToolbarMenu.Item.ReaderMode -> {
val enabled = currentSession?.readerMode
?: activity.components.core.sessionManager.selectedSession?.readerMode
?: false
if (enabled) {
readerModeController.hideReaderView()
} else {
readerModeController.showReaderView()
}
}
ToolbarMenu.Item.ReaderModeAppearance -> {
readerModeController.showControls()
}
ToolbarMenu.Item.OpenInApp -> {
val appLinksUseCases =
activity.components.useCases.appLinksUseCases
val getRedirect = appLinksUseCases.appLinkRedirect
sessionManager.selectedSession?.let {
val redirect = getRedirect.invoke(it.url)
redirect.appIntent?.flags = Intent.FLAG_ACTIVITY_NEW_TASK
appLinksUseCases.openAppLink.invoke(redirect.appIntent)
}
}
ToolbarMenu.Item.Bookmark -> {
sessionManager.selectedSession?.let {
bookmarkTapped(it)
}
}
}
}
private fun animateTabAndNavigateHome() {
// We need to dynamically add the options here because if you do it in XML it overwrites
val options = NavOptions.Builder().setPopUpTo(R.id.nav_graph, false)
.setEnterAnim(R.anim.fade_in).build()
val extras = FragmentNavigator.Extras.Builder().addSharedElement(
browserLayout,
"${TAB_ITEM_TRANSITION_NAME}${currentSession?.id}"
).build()
swipeRefresh.background = ColorDrawable(Color.TRANSPARENT)
engineView.asView().visibility = View.GONE
if (!navController.popBackStack(R.id.homeFragment, false)) {
navController.nav(
R.id.browserFragment,
R.id.action_browserFragment_to_homeFragment,
null,
options,
extras
)
}
}
@SuppressWarnings("ComplexMethod")
private fun trackToolbarItemInteraction(item: ToolbarMenu.Item) {
val eventItem = when (item) {
ToolbarMenu.Item.Back -> Event.BrowserMenuItemTapped.Item.BACK
ToolbarMenu.Item.Forward -> Event.BrowserMenuItemTapped.Item.FORWARD
ToolbarMenu.Item.Reload -> Event.BrowserMenuItemTapped.Item.RELOAD
ToolbarMenu.Item.Stop -> Event.BrowserMenuItemTapped.Item.STOP
ToolbarMenu.Item.Settings -> Event.BrowserMenuItemTapped.Item.SETTINGS
ToolbarMenu.Item.Library -> Event.BrowserMenuItemTapped.Item.LIBRARY
is ToolbarMenu.Item.RequestDesktop ->
if (item.isChecked) {
Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_ON
} else {
Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_OFF
}
ToolbarMenu.Item.NewPrivateTab -> Event.BrowserMenuItemTapped.Item.NEW_PRIVATE_TAB
ToolbarMenu.Item.FindInPage -> Event.BrowserMenuItemTapped.Item.FIND_IN_PAGE
ToolbarMenu.Item.ReportIssue -> Event.BrowserMenuItemTapped.Item.REPORT_SITE_ISSUE
ToolbarMenu.Item.Help -> Event.BrowserMenuItemTapped.Item.HELP
ToolbarMenu.Item.NewTab -> Event.BrowserMenuItemTapped.Item.NEW_TAB
ToolbarMenu.Item.OpenInFenix -> Event.BrowserMenuItemTapped.Item.OPEN_IN_FENIX
ToolbarMenu.Item.Share -> Event.BrowserMenuItemTapped.Item.SHARE
ToolbarMenu.Item.SaveToCollection -> Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION
ToolbarMenu.Item.AddToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN
ToolbarMenu.Item.Quit -> Event.BrowserMenuItemTapped.Item.QUIT
is ToolbarMenu.Item.ReaderMode ->
if (item.isChecked) {
Event.BrowserMenuItemTapped.Item.READER_MODE_ON
} else {
Event.BrowserMenuItemTapped.Item.READER_MODE_OFF
}
ToolbarMenu.Item.ReaderModeAppearance ->
Event.BrowserMenuItemTapped.Item.READER_MODE_APPEARANCE
ToolbarMenu.Item.OpenInApp -> Event.BrowserMenuItemTapped.Item.OPEN_IN_APP
ToolbarMenu.Item.Bookmark -> Event.BrowserMenuItemTapped.Item.BOOKMARK
}
activity.components.analytics.metrics.track(Event.BrowserMenuItemTapped(eventItem))
}
companion object {
@VisibleForTesting
const val TAB_ITEM_TRANSITION_NAME = "tab_item"
internal const val TELEMETRY_BROWSER_IDENTIFIER = "browserMenu"
}
}