Skip to content

Commit

Permalink
For mozilla-mobile#19916 - Add last viewed tab to home screen
Browse files Browse the repository at this point in the history
Co-authored-by: Jonathan Almeida <jalmeida@mozilla.com>
  • Loading branch information
gabrielluong and jonalmeida committed Jun 10, 2021
1 parent 2ebc71f commit 93b5545
Show file tree
Hide file tree
Showing 26 changed files with 657 additions and 18 deletions.
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
}
21 changes: 21 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,21 @@
/* 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> =
if (selectedTab != null) {
listOfNotNull(selectedTab)
} else {
emptyList()
}
18 changes: 17 additions & 1 deletion 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 @@ -112,6 +114,7 @@ import org.mozilla.fenix.ext.runIfFragmentIsAttached
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.home.mozonline.showPrivacyPopWindow
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 +174,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 @@ -243,6 +247,17 @@ 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(
activity = activity,
Expand Down Expand Up @@ -578,7 +593,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,42 @@
/* 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 kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import mozilla.components.browser.state.selector.selectedTab
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(
browserStore: BrowserStore,
private val homeStore: HomeFragmentStore,
) : AbstractBinding<BrowserState>(browserStore) {

override suspend fun onState(flow: Flow<BrowserState>) {
flow.map { it.selectedTab }
.ifChanged()
.collect { selectedTab ->
val recentTabsList = if (selectedTab != null) {
listOf(selectedTab)
} else {
emptyList()
}

homeStore.dispatch(HomeFragmentAction.RecentTabsChange(recentTabsList))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.top.sites.TopSite
import mozilla.components.ui.widgets.WidgetSiteItemView
Expand All @@ -36,6 +37,8 @@ import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingTh
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingToolbarPositionPickerViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingTrackingProtectionViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingWhatsNewViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.recenttabs.RecentTabViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.recenttabs.RecentTabsHeaderViewHolder
import org.mozilla.fenix.home.tips.ButtonTipViewHolder
import mozilla.components.feature.tab.collections.Tab as ComponentTab

Expand Down Expand Up @@ -131,6 +134,11 @@ sealed class AdapterItem(@LayoutRes val viewType: Int) {

object OnboardingWhatsNew : AdapterItem(OnboardingWhatsNewViewHolder.LAYOUT_ID)

object RecentTabsHeader : AdapterItem(RecentTabsHeaderViewHolder.LAYOUT_ID)
data class RecentTabItem(val tab: TabSessionState) : AdapterItem(RecentTabViewHolder.LAYOUT_ID) {
override fun sameAs(other: AdapterItem) = other is RecentTabItem && tab.id == other.tab.id
}

/**
* True if this item represents the same value as other. Used by [AdapterItemDiffCallback].
*/
Expand Down Expand Up @@ -211,7 +219,8 @@ class SessionControlAdapter(
view
)
ExperimentDefaultBrowserCardViewHolder.LAYOUT_ID -> ExperimentDefaultBrowserCardViewHolder(view, interactor)

RecentTabsHeaderViewHolder.LAYOUT_ID -> RecentTabsHeaderViewHolder(view, interactor)
RecentTabViewHolder.LAYOUT_ID -> RecentTabViewHolder(view, interactor)
else -> throw IllegalStateException()
}
}
Expand Down Expand Up @@ -263,6 +272,9 @@ class SessionControlAdapter(
is OnboardingAutomaticSignInViewHolder -> holder.bind(
(item as AdapterItem.OnboardingAutomaticSignIn).state.withAccount
)
is RecentTabViewHolder -> {
holder.bindTab((item as AdapterItem.RecentTabItem).tab)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import org.mozilla.fenix.components.tips.Tip
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.metrics
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph
import org.mozilla.fenix.ext.openSetDefaultBrowserOption
import org.mozilla.fenix.home.HomeFragment
import org.mozilla.fenix.home.HomeFragmentAction
Expand Down Expand Up @@ -178,6 +179,16 @@ interface SessionControlController {
* @see [ExperimentCardInteractor.onCloseExperimentCardClicked]
*/
fun handleCloseExperimentCard()

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

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

@Suppress("TooManyFunctions", "LargeClass")
Expand Down Expand Up @@ -576,4 +587,16 @@ class DefaultSessionControlController(
metrics.track(Event.CloseExperimentCardClicked)
fragmentStore.dispatch(HomeFragmentAction.RemoveSetDefaultBrowserCard)
}

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
Expand Up @@ -202,6 +202,24 @@ interface ExperimentCardInteractor {
fun onCloseExperimentCardClicked()
}

/**
* Interface for recent tab related actions in the [SessionControlInteractor].
*/
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()
}

/**
* Interactor for the Home screen.
* Provides implementations for the CollectionInteractor, OnboardingInteractor,
Expand All @@ -211,7 +229,7 @@ interface ExperimentCardInteractor {
class SessionControlInteractor(
private val controller: SessionControlController
) : CollectionInteractor, OnboardingInteractor, TopSiteInteractor, TipInteractor,
TabSessionInteractor, ToolbarInteractor, ExperimentCardInteractor {
TabSessionInteractor, ToolbarInteractor, ExperimentCardInteractor, RecentTabInteractor {
override fun onCollectionAddTabTapped(collection: TabCollection) {
controller.handleCollectionAddTabTapped(collection)
}
Expand Down Expand Up @@ -315,4 +333,12 @@ class SessionControlInteractor(
override fun onCloseExperimentCardClicked() {
controller.handleCloseExperimentCard()
}

override fun onRecentTabClicked(tabId: String) {
controller.handleRecentTabClicked(tabId)
}

override fun onRecentTabShowAllClicked() {
controller.handleRecentTabShowAllClicked()
}
}
Loading

0 comments on commit 93b5545

Please sign in to comment.