Commit 9ecb67e7 authored by Gabriel Luong's avatar Gabriel Luong Committed by Jeff Boek
Browse files

For #6758 - Part 6: Add top site view

parent f68f89f2
......@@ -33,6 +33,12 @@ data class Tab(
val icon: Bitmap? = null
)
data class TopSiteItem(
override val id: Long,
override val title: String,
override val url: String
) : TopSite
fun List<Tab>.toSessionBundle(sessionManager: SessionManager): List<Session> {
return this.mapNotNull { sessionManager.findSessionById(it.sessionId) }
}
......
......@@ -17,6 +17,7 @@ import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.tab_list_row.*
import mozilla.components.feature.media.state.MediaState
import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.top.sites.TopSite
import org.mozilla.fenix.home.OnboardingState
import org.mozilla.fenix.home.Tab
import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionHeaderViewHolder
......@@ -27,6 +28,7 @@ import org.mozilla.fenix.home.sessioncontrol.viewholders.SaveTabGroupViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.TabHeaderViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.TabInCollectionViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.TabViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.TopSiteViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingAutomaticSignInViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingFinishViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingHeaderViewHolder
......@@ -65,6 +67,8 @@ sealed class AdapterItem(@LayoutRes val viewType: Int) {
}
}
data class TopSiteList(val topSites: List<TopSite>) : AdapterItem(TopSiteViewHolder.LAYOUT_ID)
object SaveTabGroup : AdapterItem(SaveTabGroupViewHolder.LAYOUT_ID)
object PrivateBrowsingDescription : AdapterItem(PrivateBrowsingDescriptionViewHolder.LAYOUT_ID)
......@@ -148,6 +152,7 @@ class SessionControlAdapter(
return when (viewType) {
TabHeaderViewHolder.LAYOUT_ID -> TabHeaderViewHolder(view, interactor)
TabViewHolder.LAYOUT_ID -> TabViewHolder(view, interactor)
TopSiteViewHolder.LAYOUT_ID -> TopSiteViewHolder(view, interactor)
SaveTabGroupViewHolder.LAYOUT_ID -> SaveTabGroupViewHolder(view, interactor)
PrivateBrowsingDescriptionViewHolder.LAYOUT_ID -> PrivateBrowsingDescriptionViewHolder(view, interactor)
NoContentMessageViewHolder.LAYOUT_ID -> NoContentMessageViewHolder(view)
......@@ -169,6 +174,7 @@ class SessionControlAdapter(
override fun getItemViewType(position: Int) = getItem(position).viewType
@SuppressWarnings("ComplexMethod")
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = getItem(position)
when (holder) {
......@@ -179,6 +185,9 @@ class SessionControlAdapter(
is TabViewHolder -> {
holder.bindSession((item as AdapterItem.TabItem).tab)
}
is TopSiteViewHolder -> {
holder.bind((item as AdapterItem.TopSiteList).topSites)
}
is NoContentMessageViewHolder -> {
val (icon, header, description) = item as AdapterItem.NoContentMessage
holder.bind(icon, header, description)
......
......@@ -112,6 +112,11 @@ interface SessionControlController {
*/
fun handleSelectTab(tabView: View, sessionId: String)
/**
* @see [TopSiteInteractor.onSelectTopSite]
*/
fun handleSelectTopSite(url: String)
/**
* @see [TabSessionInteractor.onShareTabs]
*/
......@@ -298,6 +303,14 @@ class DefaultSessionControlController(
navController.nav(R.id.homeFragment, directions, extras)
}
override fun handleSelectTopSite(url: String) {
activity.components.useCases.tabsUseCases.addTab.invoke(url, true, true)
navController.nav(
R.id.homeFragment,
HomeFragmentDirections.actionHomeFragmentToBrowserFragment(null)
)
}
override fun handleShareTabs() {
invokePendingDeleteJobs()
val shareData = sessionManager
......
......@@ -149,15 +149,27 @@ interface TabSessionInteractor {
fun onShareTabs()
}
/**
* Interface for top site related actions in the [SessionControlInteractor].
*/
interface TopSiteInteractor {
/**
* Selects the given top site. Called when a user clicks on a top site.
*
* @param url The URL of the top site.
*/
fun onSelectTopSite(url: String)
}
/**
* Interactor for the Home screen.
* Provides implementations for the CollectionInteractor, OnboardingInteractor and
* TabSessionInteractor.
* Provides implementations for the CollectionInteractor, OnboardingInteractor,
* TabSessionInteractor and TopSiteInteractor.
*/
@SuppressWarnings("TooManyFunctions")
class SessionControlInteractor(
private val controller: SessionControlController
) : CollectionInteractor, OnboardingInteractor, TabSessionInteractor {
) : CollectionInteractor, OnboardingInteractor, TabSessionInteractor, TopSiteInteractor {
override fun onCloseTab(sessionId: String) {
controller.handleCloseTab(sessionId)
}
......@@ -214,6 +226,10 @@ class SessionControlInteractor(
controller.handleSelectTab(tabView, sessionId)
}
override fun onSelectTopSite(url: String) {
controller.handleSelectTopSite(url)
}
override fun onShareTabs() {
controller.handleShareTabs()
}
......
......@@ -15,6 +15,7 @@ import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.extensions.LayoutContainer
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.top.sites.TopSite
import mozilla.components.lib.state.ext.consumeFrom
import org.mozilla.fenix.R
import org.mozilla.fenix.home.HomeFragmentState
......@@ -37,10 +38,16 @@ val noCollectionMessage = AdapterItem.NoContentMessage(
private fun normalModeAdapterItems(
tabs: List<Tab>,
topSites: List<TopSite>,
collections: List<TabCollection>,
expandedCollections: Set<Long>
): List<AdapterItem> {
val items = mutableListOf<AdapterItem>()
if (topSites.isNotEmpty()) {
items.add(AdapterItem.TopSiteList(topSites))
}
items.add(AdapterItem.TabHeader(false, tabs.isNotEmpty()))
if (tabs.isNotEmpty()) {
......@@ -52,7 +59,6 @@ private fun normalModeAdapterItems(
items.add(AdapterItem.CollectionHeader)
if (collections.isNotEmpty()) {
// If the collection is expanded, we want to add all of its tabs beneath it in the adapter
collections.map {
AdapterItem.CollectionItem(it, expandedCollections.contains(it.id), tabs.isNotEmpty())
......@@ -116,7 +122,7 @@ private fun onboardingAdapterItems(onboardingState: OnboardingState): List<Adapt
}
private fun HomeFragmentState.toAdapterList(): List<AdapterItem> = when (mode) {
is Mode.Normal -> normalModeAdapterItems(tabs, collections, expandedCollections)
is Mode.Normal -> normalModeAdapterItems(tabs, topSites, collections, expandedCollections)
is Mode.Private -> privateModeAdapterItems(tabs)
is Mode.Onboarding -> onboardingAdapterItems(mode.state)
}
......
/* 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
import android.view.View
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.component_top_sites.view.*
import mozilla.components.feature.top.sites.TopSite
import org.mozilla.fenix.R
import org.mozilla.fenix.home.sessioncontrol.TopSiteInteractor
import org.mozilla.fenix.home.sessioncontrol.viewholders.topsites.TopSitesAdapter
class TopSiteViewHolder(
view: View,
interactor: TopSiteInteractor,
override val containerView: View? = view
) : RecyclerView.ViewHolder(view), LayoutContainer {
private val topSitesAdapter = TopSitesAdapter(interactor)
init {
view.top_sites_list.apply {
adapter = topSitesAdapter
layoutManager = GridLayoutManager(view.context, NUM_COLUMNS)
}
}
fun bind(topSites: List<TopSite>) {
topSitesAdapter.submitList(topSites)
}
companion object {
const val LAYOUT_ID = R.layout.component_top_sites
const val NUM_COLUMNS = 5
}
}
/* 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.topsites
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.top_site_item.view.*
import mozilla.components.feature.top.sites.TopSite
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.loadIntoView
import org.mozilla.fenix.home.sessioncontrol.TopSiteInteractor
class TopSiteItemViewHolder(
private val view: View,
private val interactor: TopSiteInteractor
) : RecyclerView.ViewHolder(view) {
private lateinit var topSite: TopSite
init {
view.top_site_item.setOnClickListener {
interactor.onSelectTopSite(topSite.url)
}
}
fun bind(topSite: TopSite) {
this.topSite = topSite
view.top_site_title.text = topSite.title
view.context.components.core.icons.loadIntoView(view.favicon_image, topSite.url)
}
companion object {
const val LAYOUT_ID = R.layout.top_site_item
}
}
/* 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.topsites
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import mozilla.components.feature.top.sites.TopSite
import org.mozilla.fenix.home.sessioncontrol.TopSiteInteractor
class TopSitesAdapter(
private val interactor: TopSiteInteractor
) : ListAdapter<TopSite, TopSiteItemViewHolder>(DiffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TopSiteItemViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(TopSiteItemViewHolder.LAYOUT_ID, parent, false)
return TopSiteItemViewHolder(view, interactor)
}
override fun onBindViewHolder(holder: TopSiteItemViewHolder, position: Int) {
holder.bind(getItem(position))
}
private object DiffCallback : DiffUtil.ItemCallback<TopSite>() {
override fun areItemsTheSame(oldItem: TopSite, newItem: TopSite) =
oldItem.id == newItem.id || oldItem.title == newItem.title || oldItem.url == newItem.url
override fun areContentsTheSame(oldItem: TopSite, newItem: TopSite) =
oldItem.id == newItem.id || oldItem.title == newItem.title || oldItem.url == newItem.url
}
}
<?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.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/top_sites_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:listitem="@layout/top_site_item" />
<?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:id="@+id/top_site_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:padding="4dp"
android:paddingBottom="6dp">
<ImageView
android:id="@+id/favicon_image"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginBottom="2dp"
android:adjustViewBounds="true"
android:importantForAccessibility="no"
android:scaleType="fitCenter"
app:layout_constraintBottom_toTopOf="@id/top_site_title"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/top_site_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="2dp"
android:singleLine="true"
android:textColor="?primaryText"
android:textSize="10sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/favicon_image" />
</androidx.constraintlayout.widget.ConstraintLayout>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment