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 9, 2021
1 parent 2ebc71f commit ea4dca0
Show file tree
Hide file tree
Showing 12 changed files with 251 additions and 8 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
}
25 changes: 24 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 @@ -67,6 +67,7 @@ import mozilla.components.browser.menu.view.MenuButton
import mozilla.components.browser.state.selector.findTab
import mozilla.components.browser.state.selector.normalTabs
import mozilla.components.browser.state.selector.privateTabs
import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.selectedOrDefaultSearchEngine
import mozilla.components.browser.state.store.BrowserStore
Expand All @@ -85,6 +86,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 Down Expand Up @@ -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.RecentTabsListBinding
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 recentTabsListBinding = ViewBoundFeatureWrapper<RecentTabsListBinding>()

@VisibleForTesting
internal var getMenuButton: () -> MenuButton? = { menuButton }
Expand Down Expand Up @@ -243,6 +247,17 @@ class HomeFragment : Fragment() {
view = view
)

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

_sessionControlInteractor = SessionControlInteractor(
DefaultSessionControlController(
activity = activity,
Expand Down Expand Up @@ -578,7 +593,15 @@ class HomeFragment : Fragment() {
)
).getTip()
},
showCollectionPlaceholder = components.settings.showCollectionsPlaceholderOnHome
showCollectionPlaceholder = components.settings.showCollectionsPlaceholderOnHome,
recentTabs = run {
val selectedTab = components.core.store.state.selectedTab
if (selectedTab != null) {
listOf(selectedTab)
} else {
emptyList()
}
}
)
)

Expand Down
14 changes: 11 additions & 3 deletions app/src/main/java/org/mozilla/fenix/home/HomeFragmentStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
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
import mozilla.components.lib.state.State
import mozilla.components.lib.state.Store
import org.mozilla.fenix.components.tips.Tip
import org.mozilla.fenix.home.sessioncontrol.viewholders.recenttabs.RecentTabViewHolder

/**
* The [Store] for holding the [HomeFragmentState] and applying [HomeFragmentAction]s.
Expand Down Expand Up @@ -41,6 +43,7 @@ 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 TODO add recentTabs
*/
data class HomeFragmentState(
val collections: List<TabCollection>,
Expand All @@ -49,7 +52,8 @@ data class HomeFragmentState(
val topSites: List<TopSite>,
val tip: Tip? = null,
val showCollectionPlaceholder: Boolean,
val showSetAsDefaultBrowserCard: Boolean
val showSetAsDefaultBrowserCard: Boolean,
val recentTabs: List<TabSessionState> = emptyList()
) : State

sealed class HomeFragmentAction : Action {
Expand All @@ -58,7 +62,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 +74,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 +88,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 +112,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,33 @@
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

@OptIn(ExperimentalCoroutinesApi::class)
class RecentTabsListBinding(
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)
RecentTabViewHolder.LAYOUT_ID -> RecentTabViewHolder(view)
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 @@ -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
Expand All @@ -28,7 +29,8 @@ private fun normalModeAdapterItems(
expandedCollections: Set<Long>,
tip: Tip?,
showCollectionsPlaceholder: Boolean,
showSetAsDefaultBrowserCard: Boolean
showSetAsDefaultBrowserCard: Boolean,
recentTabs: List<TabSessionState>
): List<AdapterItem> {
val items = mutableListOf<AdapterItem>()

Expand All @@ -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)
Expand All @@ -53,6 +59,16 @@ private fun normalModeAdapterItems(
return items
}

private fun showRecentTabs(
recentTabs: List<TabSessionState>,
items: MutableList<AdapterItem>
) {
items.add(AdapterItem.RecentTabsHeader)
recentTabs.forEach {
items.add(AdapterItem.RecentTabItem(it))
}
}

private fun showCollections(
collections: List<TabCollection>,
expandedCollections: Set<Long>,
Expand Down Expand Up @@ -116,7 +132,8 @@ private fun HomeFragmentState.toAdapterList(): List<AdapterItem> = when (mode) {
expandedCollections,
tip,
showCollectionPlaceholder,
showSetAsDefaultBrowserCard
showSetAsDefaultBrowserCard,
recentTabs
)
is Mode.Private -> privateModeAdapterItems()
is Mode.Onboarding -> onboardingAdapterItems(mode.state)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.mozilla.fenix.home.sessioncontrol.viewholders.recenttabs

import android.view.View
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.recent_tabs_list_row.view.*
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.fenix.R

class RecentTabViewHolder (
private val view: View
) : RecyclerView.ViewHolder(view) {
fun bindTab(tab: TabSessionState) {
view.recent_tab_title.text = tab.content.title
view.recent_tab_icon.setImageBitmap(tab.content.icon)
view.setOnClickListener {

Logger.info("TEST recent tab clicked: $tab")
// TODO in interactor
// components.useCases.selectTab(tab)
// navGraph().navigate(browserId)
}
}

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,17 @@
/* 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.viewholders.recenttabs

import android.view.View
import androidx.recyclerview.widget.RecyclerView
import org.mozilla.fenix.R

class RecentTabsHeaderViewHolder(
view: View
) : RecyclerView.ViewHolder(view) {
companion object {
const val LAYOUT_ID = R.layout.recent_tabs_header
}
}
33 changes: 33 additions & 0 deletions app/src/main/res/layout/recent_tabs_header.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content" >

<androidx.appcompat.widget.AppCompatTextView
style="@style/Header20TextStyle"
android:id="@+id/header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/home_screen_recent_tabs_header"
android:maxLines="2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<com.google.android.material.button.MaterialButton
android:id="@+id/showAllButton"
style="@style/Button12TextStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:text="@string/home_screen_recent_tabs_header"
android:textColor="@color/photonDarkGrey05"
android:gravity="center"
android:textAlignment="gravity"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="@id/header" />
</androidx.constraintlayout.widget.ConstraintLayout>
Loading

0 comments on commit ea4dca0

Please sign in to comment.