From 9d3cf790510ed4eaddcd924ba31e93b4c20b111c Mon Sep 17 00:00:00 2001 From: Gabriel Luong Date: Wed, 9 Jun 2021 15:33:11 -0400 Subject: [PATCH] For #19916 - Add last viewed tab to home screen Co-authored-by: Jonathan Almeida --- .../java/org/mozilla/fenix/FeatureFlags.kt | 5 + .../org/mozilla/fenix/ext/BrowserState.kt | 24 +++ .../org/mozilla/fenix/home/HomeFragment.kt | 28 +++- .../mozilla/fenix/home/HomeFragmentStore.kt | 25 +-- .../controller/RecentTabController.kt | 53 +++++++ .../interactor/RecentTabInteractor.kt | 23 +++ .../recenttabs/view/RecentTabViewHolder.kt | 36 +++++ .../view/RecentTabsHeaderViewHolder.kt | 32 ++++ .../sessioncontrol/RecentTabsListFeature.kt | 47 ++++++ .../sessioncontrol/SessionControlAdapter.kt | 14 +- .../SessionControlInteractor.kt | 20 ++- .../home/sessioncontrol/SessionControlView.kt | 21 ++- ...round.xml => home_list_row_background.xml} | 0 app/src/main/res/layout/collection_header.xml | 2 +- .../res/layout/collection_home_list_row.xml | 2 +- .../main/res/layout/recent_tabs_header.xml | 34 +++++ .../main/res/layout/recent_tabs_list_row.xml | 50 ++++++ app/src/main/res/values-night/colors.xml | 3 + app/src/main/res/values/colors.xml | 3 + app/src/main/res/values/strings.xml | 6 + app/src/main/res/values/styles.xml | 12 ++ .../org/mozilla/fenix/ext/BrowserStateTest.kt | 64 ++++++++ .../DefaultSessionControlControllerTest.kt | 3 +- .../fenix/home/HomeFragmentStoreTest.kt | 23 ++- .../fenix/home/RecentTabsListFeatureTest.kt | 144 ++++++++++++++++++ .../home/SessionControlInteractorTest.kt | 17 ++- .../controller/RecentTabControllerTest.kt | 89 +++++++++++ .../view/RecentTabViewHolderTest.kt | 53 +++++++ .../view/RecentTabsHeaderViewHolderTest.kt | 39 +++++ 29 files changed, 846 insertions(+), 26 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/ext/BrowserState.kt create mode 100644 app/src/main/java/org/mozilla/fenix/home/recenttabs/controller/RecentTabController.kt create mode 100644 app/src/main/java/org/mozilla/fenix/home/recenttabs/interactor/RecentTabInteractor.kt create mode 100644 app/src/main/java/org/mozilla/fenix/home/recenttabs/view/RecentTabViewHolder.kt create mode 100644 app/src/main/java/org/mozilla/fenix/home/recenttabs/view/RecentTabsHeaderViewHolder.kt create mode 100644 app/src/main/java/org/mozilla/fenix/home/sessioncontrol/RecentTabsListFeature.kt rename app/src/main/res/drawable/{collection_home_list_row_background.xml => home_list_row_background.xml} (100%) create mode 100644 app/src/main/res/layout/recent_tabs_header.xml create mode 100644 app/src/main/res/layout/recent_tabs_list_row.xml create mode 100644 app/src/test/java/org/mozilla/fenix/ext/BrowserStateTest.kt create mode 100644 app/src/test/java/org/mozilla/fenix/home/RecentTabsListFeatureTest.kt create mode 100644 app/src/test/java/org/mozilla/fenix/home/recenttabs/controller/RecentTabControllerTest.kt create mode 100644 app/src/test/java/org/mozilla/fenix/home/recenttabs/view/RecentTabViewHolderTest.kt create mode 100644 app/src/test/java/org/mozilla/fenix/home/recenttabs/view/RecentTabsHeaderViewHolderTest.kt diff --git a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt index 30ab06df3..f7bea3a1c 100644 --- a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt +++ b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt @@ -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 } diff --git a/app/src/main/java/org/mozilla/fenix/ext/BrowserState.kt b/app/src/main/java/org/mozilla/fenix/ext/BrowserState.kt new file mode 100644 index 000000000..5630172a6 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/ext/BrowserState.kt @@ -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 { + val tab = selectedTab + + return if (tab != null && !tab.content.private) { + listOfNotNull(tab) + } else { + emptyList() + } +} diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 4cb0d57d4..f0440a137 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -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 @@ -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 @@ -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 @@ -171,6 +175,7 @@ class HomeFragment : Fragment() { private lateinit var currentMode: CurrentMode private val topSitesFeature = ViewBoundFeatureWrapper() + private val recentTabsListFeature = ViewBoundFeatureWrapper() @VisibleForTesting internal var getMenuButton: () -> MenuButton? = { menuButton } @@ -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() ) ) } @@ -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, @@ -263,6 +280,10 @@ class HomeFragment : Fragment() { showDeleteCollectionPrompt = ::showDeleteCollectionPrompt, showTabTray = ::openTabsTray, handleSwipedItemDeletionCancel = ::handleSwipedItemDeletionCancel + ), + recentTabController = DefaultRecentTabsController( + selectTabUseCase = components.useCases.tabsUseCases.selectTab, + navController = findNavController() ) ) @@ -577,7 +598,8 @@ class HomeFragment : Fragment() { ) ).getTip() }, - showCollectionPlaceholder = components.settings.showCollectionsPlaceholderOnHome + showCollectionPlaceholder = components.settings.showCollectionsPlaceholderOnHome, + recentTabs = components.core.store.state.asRecentTabs() ) ) diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragmentStore.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragmentStore.kt index 59b25cc40..c10a2f0b1 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragmentStore.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragmentStore.kt @@ -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 @@ -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( initialState, ::homeFragmentStateReducer ) @@ -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, - val expandedCollections: Set, - val mode: Mode, - val topSites: List, + val collections: List = emptyList(), + val expandedCollections: Set = emptySet(), + val mode: Mode = Mode.Normal, + val topSites: List = emptyList(), val tip: Tip? = null, - val showCollectionPlaceholder: Boolean, - val showSetAsDefaultBrowserCard: Boolean + val showCollectionPlaceholder: Boolean = false, + val showSetAsDefaultBrowserCard: Boolean = false, + val recentTabs: List = emptyList() ) : State sealed class HomeFragmentAction : Action { @@ -58,7 +61,8 @@ sealed class HomeFragmentAction : Action { val mode: Mode, val collections: List, val tip: Tip? = null, - val showCollectionPlaceholder: Boolean + val showCollectionPlaceholder: Boolean, + val recentTabs: List ) : HomeFragmentAction() @@ -69,6 +73,7 @@ sealed class HomeFragmentAction : Action { data class ModeChange(val mode: Mode) : HomeFragmentAction() data class TopSitesChange(val topSites: List) : HomeFragmentAction() data class RemoveTip(val tip: Tip) : HomeFragmentAction() + data class RecentTabsChange(val recentTabs: List) : HomeFragmentAction() object RemoveCollectionsPlaceholder : HomeFragmentAction() object RemoveSetDefaultBrowserCard : HomeFragmentAction() } @@ -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() @@ -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) } } diff --git a/app/src/main/java/org/mozilla/fenix/home/recenttabs/controller/RecentTabController.kt b/app/src/main/java/org/mozilla/fenix/home/recenttabs/controller/RecentTabController.kt new file mode 100644 index 000000000..0a5384dcf --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/home/recenttabs/controller/RecentTabController.kt @@ -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() + ) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/home/recenttabs/interactor/RecentTabInteractor.kt b/app/src/main/java/org/mozilla/fenix/home/recenttabs/interactor/RecentTabInteractor.kt new file mode 100644 index 000000000..a2568108b --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/home/recenttabs/interactor/RecentTabInteractor.kt @@ -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() +} diff --git a/app/src/main/java/org/mozilla/fenix/home/recenttabs/view/RecentTabViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/recenttabs/view/RecentTabViewHolder.kt new file mode 100644 index 000000000..27644e128 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/home/recenttabs/view/RecentTabViewHolder.kt @@ -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 + } +} diff --git a/app/src/main/java/org/mozilla/fenix/home/recenttabs/view/RecentTabsHeaderViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/recenttabs/view/RecentTabsHeaderViewHolder.kt new file mode 100644 index 000000000..3933e1fd5 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/home/recenttabs/view/RecentTabsHeaderViewHolder.kt @@ -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 + } +} diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/RecentTabsListFeature.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/RecentTabsListFeature.kt new file mode 100644 index 000000000..a51c37a10 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/RecentTabsListFeature.kt @@ -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 + +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(browserStore) { + + override suspend fun onState(flow: Flow) { + 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)) + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt index 74434731d..26a7c4da9 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt @@ -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 @@ -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.recenttabs.view.RecentTabViewHolder +import org.mozilla.fenix.home.recenttabs.view.RecentTabsHeaderViewHolder import org.mozilla.fenix.home.tips.ButtonTipViewHolder import mozilla.components.feature.tab.collections.Tab as ComponentTab @@ -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]. */ @@ -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() } } @@ -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) + } } } } diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt index c88fa0fc1..a4a26cffb 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt @@ -8,6 +8,8 @@ import mozilla.components.feature.tab.collections.Tab import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.top.sites.TopSite import org.mozilla.fenix.components.tips.Tip +import org.mozilla.fenix.home.recenttabs.controller.RecentTabController +import org.mozilla.fenix.home.recenttabs.interactor.RecentTabInteractor /** * Interface for tab related actions in the [SessionControlInteractor]. @@ -204,14 +206,16 @@ interface ExperimentCardInteractor { /** * Interactor for the Home screen. - * Provides implementations for the CollectionInteractor, OnboardingInteractor, - * TabSessionInteractor and TopSiteInteractor. + * Provides implementations for the CollectionInteractor, OnboardingInteractor, TopSiteInteractor, + * TipInteractor, TabSessionInteractor, ToolbarInteractor, ExperimentCardInteractor, and + * RecentTabInteractor. */ @SuppressWarnings("TooManyFunctions") class SessionControlInteractor( - private val controller: SessionControlController + private val controller: SessionControlController, + private val recentTabController: RecentTabController ) : CollectionInteractor, OnboardingInteractor, TopSiteInteractor, TipInteractor, - TabSessionInteractor, ToolbarInteractor, ExperimentCardInteractor { + TabSessionInteractor, ToolbarInteractor, ExperimentCardInteractor, RecentTabInteractor { override fun onCollectionAddTabTapped(collection: TabCollection) { controller.handleCollectionAddTabTapped(collection) } @@ -315,4 +319,12 @@ class SessionControlInteractor( override fun onCloseExperimentCardClicked() { controller.handleCloseExperimentCard() } + + override fun onRecentTabClicked(tabId: String) { + recentTabController.handleRecentTabClicked(tabId) + } + + override fun onRecentTabShowAllClicked() { + recentTabController.handleRecentTabShowAllClicked() + } } diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt index 859d23d1a..aca15a662 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt @@ -10,6 +10,7 @@ import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import kotlinx.android.extensions.LayoutContainer +import mozilla.components.browser.state.state.TabSessionState import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.top.sites.TopSite import org.mozilla.fenix.components.tips.Tip @@ -28,7 +29,8 @@ private fun normalModeAdapterItems( expandedCollections: Set, tip: Tip?, showCollectionsPlaceholder: Boolean, - showSetAsDefaultBrowserCard: Boolean + showSetAsDefaultBrowserCard: Boolean, + recentTabs: List ): List { val items = mutableListOf() @@ -42,6 +44,10 @@ private fun normalModeAdapterItems( items.add(AdapterItem.TopSitePager(topSites)) } + if (recentTabs.isNotEmpty()) { + showRecentTabs(recentTabs, items) + } + if (collections.isEmpty()) { if (showCollectionsPlaceholder) { items.add(AdapterItem.NoCollectionsMessage) @@ -53,6 +59,16 @@ private fun normalModeAdapterItems( return items } +private fun showRecentTabs( + recentTabs: List, + items: MutableList +) { + items.add(AdapterItem.RecentTabsHeader) + recentTabs.forEach { + items.add(AdapterItem.RecentTabItem(it)) + } +} + private fun showCollections( collections: List, expandedCollections: Set, @@ -116,7 +132,8 @@ private fun HomeFragmentState.toAdapterList(): List = when (mode) { expandedCollections, tip, showCollectionPlaceholder, - showSetAsDefaultBrowserCard + showSetAsDefaultBrowserCard, + recentTabs ) is Mode.Private -> privateModeAdapterItems() is Mode.Onboarding -> onboardingAdapterItems(mode.state) diff --git a/app/src/main/res/drawable/collection_home_list_row_background.xml b/app/src/main/res/drawable/home_list_row_background.xml similarity index 100% rename from app/src/main/res/drawable/collection_home_list_row_background.xml rename to app/src/main/res/drawable/home_list_row_background.xml diff --git a/app/src/main/res/layout/collection_header.xml b/app/src/main/res/layout/collection_header.xml index 9122f0455..c92d6f15b 100644 --- a/app/src/main/res/layout/collection_header.xml +++ b/app/src/main/res/layout/collection_header.xml @@ -5,7 +5,7 @@ diff --git a/app/src/main/res/layout/collection_home_list_row.xml b/app/src/main/res/layout/collection_home_list_row.xml index 8bb37660d..c6efdcba4 100644 --- a/app/src/main/res/layout/collection_home_list_row.xml +++ b/app/src/main/res/layout/collection_home_list_row.xml @@ -8,7 +8,7 @@ android:layout_width="match_parent" android:layout_height="48dp" android:layout_marginTop="12dp" - android:background="@drawable/collection_home_list_row_background" + android:background="@drawable/home_list_row_background" android:clickable="true" android:clipToPadding="false" android:elevation="@dimen/home_collection_elevation" diff --git a/app/src/main/res/layout/recent_tabs_header.xml b/app/src/main/res/layout/recent_tabs_header.xml new file mode 100644 index 000000000..59f9e92cc --- /dev/null +++ b/app/src/main/res/layout/recent_tabs_header.xml @@ -0,0 +1,34 @@ + + + + + + + + diff --git a/app/src/main/res/layout/recent_tabs_list_row.xml b/app/src/main/res/layout/recent_tabs_list_row.xml new file mode 100644 index 000000000..82cc850c7 --- /dev/null +++ b/app/src/main/res/layout/recent_tabs_list_row.xml @@ -0,0 +1,50 @@ + + + + + + + + + diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index a2e2e7920..2e0e53da4 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -95,6 +95,9 @@ @color/collection_icon_color_green_dark_theme @color/collection_icon_color_yellow_dark_theme + + @color/photonLightGrey50 + @color/inset_dark_theme @color/primary_text_dark_theme diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index de34efd09..7a80a9e59 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -300,6 +300,9 @@ @color/collection_icon_color_yellow + + @color/photonDarkGrey05 + #B9F0FD #0E214A diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c29f65614..d210a33dd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -100,6 +100,12 @@ Top sites + + + Jump back in + + Show all + Open Tabs diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 828b802db..1fe47dd71 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -369,6 +369,12 @@ @font/metropolis_semibold + + + +