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

Commit

Permalink
For #6758 - Part 6: Add top site view
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrielluong committed Jan 21, 2020
1 parent dae8ebd commit 058c5c2
Show file tree
Hide file tree
Showing 10 changed files with 216 additions and 5 deletions.
6 changes: 6 additions & 0 deletions app/src/main/java/org/mozilla/fenix/home/HomeFragmentStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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) {
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ interface SessionControlController {
*/
fun handleSelectTab(tabView: View, sessionId: String)

/**
* @see [TopSiteInteractor.onSelectTopSite]
*/
fun handleSelectTopSite(url: String)

/**
* @see [TabSessionInteractor.onShareTabs]
*/
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -214,6 +226,10 @@ class SessionControlInteractor(
controller.handleSelectTab(tabView, sessionId)
}

override fun onSelectTopSite(url: String) {
controller.handleSelectTopSite(url)
}

override fun onShareTabs() {
controller.handleShareTabs()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()) {
Expand All @@ -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())
Expand Down Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* 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
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* 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
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* 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
}
}
11 changes: 11 additions & 0 deletions app/src/main/res/layout/component_top_sites.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?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" />

40 changes: 40 additions & 0 deletions app/src/main/res/layout/top_site_item.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?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>

0 comments on commit 058c5c2

Please sign in to comment.