1
0
Fork 0
fenix/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlController.kt

414 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.home.sessioncontrol
import android.view.View
import androidx.navigation.NavController
import androidx.navigation.fragment.FragmentNavigator
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import mozilla.components.browser.session.SessionManager
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.feature.media.ext.pauseIfPlaying
import mozilla.components.feature.media.ext.playIfPaused
import mozilla.components.feature.media.state.MediaStateMachine
import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.top.sites.TopSite
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
import org.mozilla.fenix.collections.SaveCollectionStep
import org.mozilla.fenix.components.TabCollectionStorage
import org.mozilla.fenix.components.TopSiteStorage
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.sessionsOfType
import org.mozilla.fenix.home.HomeFragment
import org.mozilla.fenix.home.HomeFragmentAction
import org.mozilla.fenix.home.HomeFragmentDirections
import org.mozilla.fenix.home.HomeFragmentStore
import org.mozilla.fenix.home.Tab
import org.mozilla.fenix.settings.SupportUtils
import mozilla.components.feature.tab.collections.Tab as ComponentTab
/**
* [HomeFragment] controller. An interface that handles the view manipulation of the Tabs triggered
* by the Interactor.
*/
@SuppressWarnings("TooManyFunctions")
interface SessionControlController {
/**
* @see [TabSessionInteractor.onCloseTab]
*/
fun handleCloseTab(sessionId: String)
/**
* @see [TabSessionInteractor.onCloseAllTabs]
*/
fun handleCloseAllTabs(isPrivateMode: Boolean)
/**
* @see [CollectionInteractor.onCollectionAddTabTapped]
*/
fun handleCollectionAddTabTapped(collection: TabCollection)
/**
* @see [CollectionInteractor.onCollectionOpenTabClicked]
*/
fun handleCollectionOpenTabClicked(tab: ComponentTab)
/**
* @see [CollectionInteractor.onCollectionOpenTabsTapped]
*/
fun handleCollectionOpenTabsTapped(collection: TabCollection)
/**
* @see [CollectionInteractor.onCollectionRemoveTab]
*/
fun handleCollectionRemoveTab(collection: TabCollection, tab: ComponentTab)
/**
* @see [CollectionInteractor.onCollectionShareTabsClicked]
*/
fun handleCollectionShareTabsClicked(collection: TabCollection)
/**
* @see [CollectionInteractor.onDeleteCollectionTapped]
*/
fun handleDeleteCollectionTapped(collection: TabCollection)
/**
* @see [TopSiteInteractor.onOpenInPrivateTabClicked]
*/
fun handleOpenInPrivateTabClicked(topSite: TopSite)
/**
* @see [TabSessionInteractor.onPauseMediaClicked]
*/
fun handlePauseMediaClicked()
/**
* @see [TabSessionInteractor.onPlayMediaClicked]
*/
fun handlePlayMediaClicked()
/**
* @see [TabSessionInteractor.onPrivateBrowsingLearnMoreClicked]
*/
fun handlePrivateBrowsingLearnMoreClicked()
/**
* @see [TopSiteInteractor.onRemoveTopSiteClicked]
*/
fun handleRemoveTopSiteClicked(topSite: TopSite)
/**
* @see [CollectionInteractor.onRenameCollectionTapped]
*/
fun handleRenameCollectionTapped(collection: TabCollection)
/**
* @see [TabSessionInteractor.onSaveToCollection]
*/
fun handleSaveTabToCollection(selectedTabId: String?)
/**
* @see [TabSessionInteractor.onSelectTab]
*/
fun handleSelectTab(tabView: View, sessionId: String)
/**
* @see [TopSiteInteractor.onSelectTopSite]
*/
fun handleSelectTopSite(url: String)
/**
* @see [TabSessionInteractor.onShareTabs]
*/
fun handleShareTabs()
/**
* @see [OnboardingInteractor.onStartBrowsingClicked]
*/
fun handleStartBrowsingClicked()
/**
* @see [OnboardingInteractor.onOpenSettingsClicked]
*/
fun handleOpenSettingsClicked()
/**
* @see [CollectionInteractor.onToggleCollectionExpanded]
*/
fun handleToggleCollectionExpanded(collection: TabCollection, expand: Boolean)
}
@SuppressWarnings("TooManyFunctions", "LargeClass")
class DefaultSessionControlController(
private val activity: HomeActivity,
private val store: HomeFragmentStore,
private val navController: NavController,
private val browsingModeManager: BrowsingModeManager,
private val lifecycleScope: CoroutineScope,
private val closeTab: (sessionId: String) -> Unit,
private val closeAllTabs: (isPrivateMode: Boolean) -> Unit,
private val getListOfTabs: () -> List<Tab>,
private val hideOnboarding: () -> Unit,
private val invokePendingDeleteJobs: () -> Unit,
private val registerCollectionStorageObserver: () -> Unit,
private val scrollToTheTop: () -> Unit,
private val showDeleteCollectionPrompt: (tabCollection: TabCollection) -> Unit,
private val openSettingsScreen: () -> Unit
) : SessionControlController {
private val metrics: MetricController
get() = activity.components.analytics.metrics
private val sessionManager: SessionManager
get() = activity.components.core.sessionManager
private val tabCollectionStorage: TabCollectionStorage
get() = activity.components.core.tabCollectionStorage
private val topSiteStorage: TopSiteStorage
get() = activity.components.core.topSiteStorage
override fun handleCloseTab(sessionId: String) {
closeTab.invoke(sessionId)
}
override fun handleCloseAllTabs(isPrivateMode: Boolean) {
closeAllTabs.invoke(isPrivateMode)
}
override fun handleCollectionAddTabTapped(collection: TabCollection) {
metrics.track(Event.CollectionAddTabPressed)
showCollectionCreationFragment(
step = SaveCollectionStep.SelectTabs,
selectedTabCollectionId = collection.id
)
}
override fun handleCollectionOpenTabClicked(tab: ComponentTab) {
invokePendingDeleteJobs()
val session = tab.restore(
context = activity,
engine = activity.components.core.engine,
tab = tab,
restoreSessionId = false
)
if (session == null) {
// We were unable to create a snapshot, so just load the tab instead
activity.openToBrowserAndLoad(
searchTermOrURL = tab.url,
newTab = true,
from = BrowserDirection.FromHome
)
} else {
sessionManager.add(
session,
true
)
activity.openToBrowser(BrowserDirection.FromHome)
}
metrics.track(Event.CollectionTabRestored)
}
override fun handleCollectionOpenTabsTapped(collection: TabCollection) {
invokePendingDeleteJobs()
collection.tabs.reversed().forEach {
val session = it.restore(
context = activity,
engine = activity.components.core.engine,
tab = it,
restoreSessionId = false
)
if (session == null) {
// We were unable to create a snapshot, so just load the tab instead
activity.components.useCases.tabsUseCases.addTab.invoke(it.url)
} else {
sessionManager.add(
session,
activity.components.core.sessionManager.selectedSession == null
)
}
}
scrollToTheTop()
metrics.track(Event.CollectionAllTabsRestored)
}
override fun handleCollectionRemoveTab(collection: TabCollection, tab: ComponentTab) {
metrics.track(Event.CollectionTabRemoved)
lifecycleScope.launch(Dispatchers.IO) {
tabCollectionStorage.removeTabFromCollection(collection, tab)
}
}
override fun handleCollectionShareTabsClicked(collection: TabCollection) {
showShareFragment(collection.tabs.map { ShareData(url = it.url, title = it.title) })
metrics.track(Event.CollectionShared)
}
override fun handleDeleteCollectionTapped(collection: TabCollection) {
showDeleteCollectionPrompt(collection)
}
override fun handleOpenInPrivateTabClicked(topSite: TopSite) {
metrics.track(Event.TopSiteOpenInPrivateTab)
with(activity) {
browsingModeManager.mode = BrowsingMode.Private
openToBrowserAndLoad(
searchTermOrURL = topSite.url,
newTab = true,
from = BrowserDirection.FromHome
)
}
}
override fun handlePauseMediaClicked() {
MediaStateMachine.state.pauseIfPlaying()
}
override fun handlePlayMediaClicked() {
MediaStateMachine.state.playIfPaused()
}
override fun handlePrivateBrowsingLearnMoreClicked() {
activity.openToBrowserAndLoad(
searchTermOrURL = SupportUtils.getGenericSumoURLForTopic
(SupportUtils.SumoTopic.PRIVATE_BROWSING_MYTHS),
newTab = true,
from = BrowserDirection.FromHome
)
}
override fun handleRemoveTopSiteClicked(topSite: TopSite) {
metrics.track(Event.TopSiteRemoved)
if (topSite.url == SupportUtils.POCKET_TRENDING_URL) {
metrics.track(Event.PocketTopSiteRemoved)
}
lifecycleScope.launch(Dispatchers.IO) {
topSiteStorage.removeTopSite(topSite)
}
}
override fun handleRenameCollectionTapped(collection: TabCollection) {
showCollectionCreationFragment(
step = SaveCollectionStep.RenameCollection,
selectedTabCollectionId = collection.id
)
metrics.track(Event.CollectionRenamePressed)
}
override fun handleSaveTabToCollection(selectedTabId: String?) {
if (browsingModeManager.mode.isPrivate) return
invokePendingDeleteJobs()
val tabs = getListOfTabs()
val step = when {
// Show the SelectTabs fragment if there are multiple opened tabs to select which tabs
// you want to save to a collection.
tabs.size > 1 -> SaveCollectionStep.SelectTabs
// If there is an existing tab collection, show the SelectCollection fragment to save
// the selected tab to a collection of your choice.
tabCollectionStorage.cachedTabCollections.isNotEmpty() -> SaveCollectionStep.SelectCollection
// Show the NameCollection fragment to create a new collection for the selected tab.
else -> SaveCollectionStep.NameCollection
}
showCollectionCreationFragment(step, selectedTabId?.let { arrayOf(it) })
}
override fun handleSelectTab(tabView: View, sessionId: String) {
invokePendingDeleteJobs()
val session = sessionManager.findSessionById(sessionId)
sessionManager.select(session!!)
val directions = HomeFragmentDirections.actionHomeFragmentToBrowserFragment(null)
val extras =
FragmentNavigator.Extras.Builder()
.addSharedElement(
tabView,
"$TAB_ITEM_TRANSITION_NAME$sessionId"
)
.build()
navController.nav(R.id.homeFragment, directions, extras)
}
override fun handleSelectTopSite(url: String) {
metrics.track(Event.TopSiteOpenInNewTab)
if (url == SupportUtils.POCKET_TRENDING_URL) {
metrics.track(Event.PocketTopSiteClicked)
}
activity.components.useCases.tabsUseCases.addTab.invoke(url, true, true)
navController.nav(
R.id.homeFragment,
HomeFragmentDirections.actionHomeFragmentToBrowserFragment(null)
)
}
override fun handleShareTabs() {
invokePendingDeleteJobs()
val shareData = sessionManager
.sessionsOfType(private = browsingModeManager.mode.isPrivate)
.map { ShareData(url = it.url, title = it.title) }
.toList()
showShareFragment(shareData)
}
override fun handleStartBrowsingClicked() {
hideOnboarding()
}
override fun handleOpenSettingsClicked() {
openSettingsScreen()
}
override fun handleToggleCollectionExpanded(collection: TabCollection, expand: Boolean) {
store.dispatch(HomeFragmentAction.CollectionExpanded(collection, expand))
}
private fun showCollectionCreationFragment(
step: SaveCollectionStep,
selectedTabIds: Array<String>? = null,
selectedTabCollectionId: Long? = null
) {
if (navController.currentDestination?.id == R.id.collectionCreationFragment) return
// Only register the observer right before moving to collection creation
registerCollectionStorageObserver()
val tabIds = getListOfTabs().map { it.sessionId }.toTypedArray()
val directions = HomeFragmentDirections.actionHomeFragmentToCreateCollectionFragment(
tabIds = tabIds,
previousFragmentId = R.id.homeFragment,
saveCollectionStep = step,
selectedTabIds = selectedTabIds,
selectedTabCollectionId = selectedTabCollectionId ?: -1
)
navController.nav(R.id.homeFragment, directions)
}
private fun showShareFragment(data: List<ShareData>) {
val directions = HomeFragmentDirections.actionHomeFragmentToShareFragment(
data = data.toTypedArray()
)
navController.nav(R.id.homeFragment, directions)
}
companion object {
private const val TAB_ITEM_TRANSITION_NAME = "tab_item"
}
}