Skip to content
This repository has been archived by the owner on Feb 20, 2023. It is now read-only.

For #19916 - Add last viewed tab to home screen #19944

Merged
merged 1 commit into from
Jun 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/src/main/java/org/mozilla/fenix/FeatureFlags.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,9 @@ object FeatureFlags {
* Enables the Home button in the browser toolbar to navigate back to the home screen.
*/
val showHomeButtonFeature = Config.channel.isNightlyOrDebug

/**
* Enables the "recent" tabs feature in the home screen.
*/
val showRecentTabsFeature = Config.channel.isNightlyOrDebug
}
24 changes: 24 additions & 0 deletions app/src/main/java/org/mozilla/fenix/ext/BrowserState.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* 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.ext

import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.TabSessionState

/**
* Returns the currently selected tab if there's one as a list.
*
* @return A list of the currently selected tab or an empty list.
*/
fun BrowserState.asRecentTabs(): List<TabSessionState> {
val tab = selectedTab

return if (tab != null && !tab.content.private) {
listOfNotNull(tab)
} else {
emptyList()
}
}
28 changes: 25 additions & 3 deletions app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import mozilla.components.ui.tabcounter.TabCounterMenu
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.Config
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.GleanMetrics.PerfStartup
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
Expand All @@ -101,6 +102,7 @@ import org.mozilla.fenix.components.tips.Tip
import org.mozilla.fenix.components.tips.providers.MasterPasswordTipProvider
import org.mozilla.fenix.components.toolbar.FenixTabCounterMenu
import org.mozilla.fenix.components.toolbar.ToolbarPosition
import org.mozilla.fenix.ext.asRecentTabs
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.hideToolbar
import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
Expand All @@ -111,7 +113,9 @@ import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.runIfFragmentIsAttached
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.home.mozonline.showPrivacyPopWindow
import org.mozilla.fenix.home.recenttabs.controller.DefaultRecentTabsController
import org.mozilla.fenix.home.sessioncontrol.DefaultSessionControlController
import org.mozilla.fenix.home.sessioncontrol.RecentTabsListFeature
import org.mozilla.fenix.home.sessioncontrol.SessionControlInteractor
import org.mozilla.fenix.home.sessioncontrol.SessionControlView
import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionViewHolder
Expand Down Expand Up @@ -171,6 +175,7 @@ class HomeFragment : Fragment() {
private lateinit var currentMode: CurrentMode

private val topSitesFeature = ViewBoundFeatureWrapper<TopSitesFeature>()
private val recentTabsListFeature = ViewBoundFeatureWrapper<RecentTabsListFeature>()

@VisibleForTesting
internal var getMenuButton: () -> MenuButton? = { menuButton }
Expand Down Expand Up @@ -228,7 +233,8 @@ class HomeFragment : Fragment() {
).getTip()
},
showCollectionPlaceholder = components.settings.showCollectionsPlaceholderOnHome,
showSetAsDefaultBrowserCard = components.settings.shouldShowSetAsDefaultBrowserCard()
showSetAsDefaultBrowserCard = components.settings.shouldShowSetAsDefaultBrowserCard(),
recentTabs = components.core.store.state.asRecentTabs()
)
)
}
Expand All @@ -243,8 +249,19 @@ class HomeFragment : Fragment() {
view = view
)

if (FeatureFlags.showRecentTabsFeature) {
recentTabsListFeature.set(
feature = RecentTabsListFeature(
browserStore = components.core.store,
homeStore = homeFragmentStore
),
owner = viewLifecycleOwner,
view = view
)
}

_sessionControlInteractor = SessionControlInteractor(
DefaultSessionControlController(
controller = DefaultSessionControlController(
activity = activity,
settings = components.settings,
engine = components.core.engine,
Expand All @@ -263,6 +280,10 @@ class HomeFragment : Fragment() {
showDeleteCollectionPrompt = ::showDeleteCollectionPrompt,
showTabTray = ::openTabsTray,
handleSwipedItemDeletionCancel = ::handleSwipedItemDeletionCancel
),
recentTabController = DefaultRecentTabsController(
selectTabUseCase = components.useCases.tabsUseCases.selectTab,
navController = findNavController()
)
)

Expand Down Expand Up @@ -577,7 +598,8 @@ class HomeFragment : Fragment() {
)
).getTip()
},
showCollectionPlaceholder = components.settings.showCollectionsPlaceholderOnHome
showCollectionPlaceholder = components.settings.showCollectionsPlaceholderOnHome,
recentTabs = components.core.store.state.asRecentTabs()
)
)

Expand Down
25 changes: 16 additions & 9 deletions app/src/main/java/org/mozilla/fenix/home/HomeFragmentStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package org.mozilla.fenix.home

import android.graphics.Bitmap
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.top.sites.TopSite
import mozilla.components.lib.state.Action
Expand All @@ -16,7 +17,7 @@ import org.mozilla.fenix.components.tips.Tip
* The [Store] for holding the [HomeFragmentState] and applying [HomeFragmentAction]s.
*/
class HomeFragmentStore(
initialState: HomeFragmentState
initialState: HomeFragmentState = HomeFragmentState()
) : Store<HomeFragmentState, HomeFragmentAction>(
initialState, ::homeFragmentStateReducer
)
Expand All @@ -41,15 +42,17 @@ data class Tab(
* @property topSites The list of [TopSite] in the [HomeFragment].
* @property tip The current [Tip] to show on the [HomeFragment].
* @property showCollectionPlaceholder If true, shows a placeholder when there are no collections.
* @property recentTabs The list of recent [TabSessionState] in the [HomeFragment].
*/
data class HomeFragmentState(
val collections: List<TabCollection>,
val expandedCollections: Set<Long>,
val mode: Mode,
val topSites: List<TopSite>,
val collections: List<TabCollection> = emptyList(),
val expandedCollections: Set<Long> = emptySet(),
val mode: Mode = Mode.Normal,
val topSites: List<TopSite> = emptyList(),
val tip: Tip? = null,
val showCollectionPlaceholder: Boolean,
val showSetAsDefaultBrowserCard: Boolean
val showCollectionPlaceholder: Boolean = false,
val showSetAsDefaultBrowserCard: Boolean = false,
val recentTabs: List<TabSessionState> = emptyList()
) : State

sealed class HomeFragmentAction : Action {
Expand All @@ -58,7 +61,8 @@ sealed class HomeFragmentAction : Action {
val mode: Mode,
val collections: List<TabCollection>,
val tip: Tip? = null,
val showCollectionPlaceholder: Boolean
val showCollectionPlaceholder: Boolean,
val recentTabs: List<TabSessionState>
) :
HomeFragmentAction()

Expand All @@ -69,6 +73,7 @@ sealed class HomeFragmentAction : Action {
data class ModeChange(val mode: Mode) : HomeFragmentAction()
data class TopSitesChange(val topSites: List<TopSite>) : HomeFragmentAction()
data class RemoveTip(val tip: Tip) : HomeFragmentAction()
data class RecentTabsChange(val recentTabs: List<TabSessionState>) : HomeFragmentAction()
object RemoveCollectionsPlaceholder : HomeFragmentAction()
object RemoveSetDefaultBrowserCard : HomeFragmentAction()
}
Expand All @@ -82,7 +87,8 @@ private fun homeFragmentStateReducer(
collections = action.collections,
mode = action.mode,
topSites = action.topSites,
tip = action.tip
tip = action.tip,
recentTabs = action.recentTabs
)
is HomeFragmentAction.CollectionExpanded -> {
val newExpandedCollection = state.expandedCollections.toMutableSet()
Expand All @@ -105,5 +111,6 @@ private fun homeFragmentStateReducer(
state.copy(showCollectionPlaceholder = false)
}
is HomeFragmentAction.RemoveSetDefaultBrowserCard -> state.copy(showSetAsDefaultBrowserCard = false)
is HomeFragmentAction.RecentTabsChange -> state.copy(recentTabs = action.recentTabs)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/* 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.recenttabs.controller

import androidx.navigation.NavController
import mozilla.components.feature.tabs.TabsUseCases.SelectTabUseCase
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.home.HomeFragmentDirections
import org.mozilla.fenix.home.recenttabs.interactor.RecentTabInteractor

/**
* An interface that handles the view manipulation of the recent tabs in the Home screen.
*/
interface RecentTabController {

/**
* @see [RecentTabInteractor.onRecentTabClicked]
*/
fun handleRecentTabClicked(tabId: String)

/**
* @see [RecentTabInteractor.onRecentTabShowAllClicked]
*/
fun handleRecentTabShowAllClicked()
}

/**
* The default implementation of [RecentTabController].
*
* @param selectTabUseCase [SelectTabUseCase] used selecting a tab.
* @param navController [NavController] used for navigation.
*/
class DefaultRecentTabsController(
private val selectTabUseCase: SelectTabUseCase,
private val navController: NavController
) : RecentTabController {

override fun handleRecentTabClicked(tabId: String) {
selectTabUseCase.invoke(tabId)
navController.navigateBlockingForAsyncNavGraph(R.id.browserFragment)
}

override fun handleRecentTabShowAllClicked() {
navController.nav(
R.id.homeFragment,
HomeFragmentDirections.actionGlobalTabsTrayFragment()
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* 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.recenttabs.interactor

/**
* Interface for recent tab related actions in the Home screen.
*/
interface RecentTabInteractor {
/**
* Opens the given tab. Called when a user clicks on a recent tab.
*
* @param tabId The ID of the tab to open.
*/
fun onRecentTabClicked(tabId: String)

/**
* Show the tabs tray. Called when a user clicks on the "Show all" button besides the recent
* tabs.
*/
fun onRecentTabShowAllClicked()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/* 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.recenttabs.view

import android.view.View
import kotlinx.android.synthetic.main.recent_tabs_list_row.*
import mozilla.components.browser.state.state.TabSessionState
import org.mozilla.fenix.R
import org.mozilla.fenix.home.recenttabs.interactor.RecentTabInteractor
import org.mozilla.fenix.utils.view.ViewHolder

/**
* View holder for a recent tab item.
*
* @param interactor [RecentTabInteractor] which will have delegated to all user interactions.
*/
class RecentTabViewHolder(
view: View,
private val interactor: RecentTabInteractor
) : ViewHolder(view) {

fun bindTab(tab: TabSessionState) {
recent_tab_title.text = tab.content.title
recent_tab_icon.setImageBitmap(tab.content.icon)

itemView.setOnClickListener {
interactor.onRecentTabClicked(tab.id)
}
}

companion object {
const val LAYOUT_ID = R.layout.recent_tabs_list_row
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* 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.recenttabs.view

import android.view.View
import kotlinx.android.synthetic.main.recent_tabs_header.*
import org.mozilla.fenix.R
import org.mozilla.fenix.home.recenttabs.interactor.RecentTabInteractor
import org.mozilla.fenix.utils.view.ViewHolder

/**
* View holder for the recent tabs header and "Show all" button.
*
* @param interactor [RecentTabInteractor] which will have delegated to all user interactions.
*/
class RecentTabsHeaderViewHolder(
view: View,
private val interactor: RecentTabInteractor
) : ViewHolder(view) {

init {
show_all_button.setOnClickListener {
interactor.onRecentTabShowAllClicked()
}
}

companion object {
const val LAYOUT_ID = R.layout.recent_tabs_header
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/* 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Probably meant for this (and it's test) to be in org.mozilla.fenix.home.recenttab.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do this as a fast follow to not lose the green CI


import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import mozilla.components.browser.state.selector.normalTabs
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.lib.state.helpers.AbstractBinding
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import org.mozilla.fenix.home.HomeFragmentAction
import org.mozilla.fenix.home.HomeFragmentStore

/**
* View-bound feature that dispatches recent tab changes to the [HomeFragmentStore] when the
* [BrowserStore] is updated.
*/
@OptIn(ExperimentalCoroutinesApi::class)
class RecentTabsListFeature(
private val browserStore: BrowserStore,
private val homeStore: HomeFragmentStore
) : AbstractBinding<BrowserState>(browserStore) {

override suspend fun onState(flow: Flow<BrowserState>) {
flow.map { it.selectedTabId }
.ifChanged()
.collect { selectedTabId ->
// Attempt to get the selected normal tab since here may not be a selected tab or
// the selected tab may be a private tab.
val selectedTab = browserStore.state.normalTabs.firstOrNull {
it.id == selectedTabId
}
val recentTabsList = if (selectedTab != null) {
listOf(selectedTab)
} else {
emptyList()
}

homeStore.dispatch(HomeFragmentAction.RecentTabsChange(recentTabsList))
}
}
}
Loading