From 04dcfa5cab5dfb21fb9b87c839068841512f0904 Mon Sep 17 00:00:00 2001 From: Gabriel Luong Date: Mon, 24 Aug 2020 19:52:33 -0400 Subject: [PATCH] For #8312 - Show Top Sites in a ViewPager (#14116) --- app/build.gradle | 2 - .../sessioncontrol/SessionControlAdapter.kt | 14 ++-- .../home/sessioncontrol/SessionControlView.kt | 4 +- .../viewholders/TopSitePagerViewHolder.kt | 57 +++++++++++++ .../viewholders/TopSiteViewHolder.kt | 3 - .../viewholders/topsites/PagerIndicator.kt | 82 +++++++++++++++++++ .../topsites/TopSitesPagerAdapter.kt | 40 +++++++++ app/src/main/res/color/pager_dot.xml | 8 ++ app/src/main/res/drawable/pager_dot.xml | 12 +++ .../main/res/layout/component_top_sites.xml | 15 +++- .../res/layout/component_top_sites_pager.xml | 24 ++++++ app/src/main/res/values-night/colors.xml | 2 + app/src/main/res/values/colors.xml | 5 +- buildSrc/src/main/java/Dependencies.kt | 2 - 14 files changed, 249 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/TopSitePagerViewHolder.kt create mode 100644 app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/PagerIndicator.kt create mode 100644 app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSitesPagerAdapter.kt create mode 100644 app/src/main/res/color/pager_dot.xml create mode 100644 app/src/main/res/drawable/pager_dot.xml create mode 100644 app/src/main/res/layout/component_top_sites_pager.xml diff --git a/app/build.gradle b/app/build.gradle index 5055bc680..564db3d20 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -432,8 +432,6 @@ dependencies { implementation Deps.androidx_work_ktx implementation Deps.google_material - implementation Deps.google_flexbox - implementation Deps.lottie implementation Deps.adjust 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 487d34db2..bde850a17 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 @@ -22,7 +22,7 @@ import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionViewHolder import org.mozilla.fenix.home.sessioncontrol.viewholders.NoCollectionsMessageViewHolder import org.mozilla.fenix.home.sessioncontrol.viewholders.PrivateBrowsingDescriptionViewHolder import org.mozilla.fenix.home.sessioncontrol.viewholders.TabInCollectionViewHolder -import org.mozilla.fenix.home.sessioncontrol.viewholders.TopSiteViewHolder +import org.mozilla.fenix.home.sessioncontrol.viewholders.TopSitePagerViewHolder 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 @@ -42,14 +42,14 @@ sealed class AdapterItem(@LayoutRes val viewType: Int) { ButtonTipViewHolder.LAYOUT_ID ) - data class TopSiteList(val topSites: List) : AdapterItem(TopSiteViewHolder.LAYOUT_ID) { + data class TopSitePager(val topSites: List) : AdapterItem(TopSitePagerViewHolder.LAYOUT_ID) { override fun sameAs(other: AdapterItem): Boolean { - val newTopSites = (other as? TopSiteList) ?: return false + val newTopSites = (other as? TopSitePager) ?: return false return newTopSites.topSites == this.topSites } override fun contentsSameAs(other: AdapterItem): Boolean { - val newTopSites = (other as? TopSiteList) ?: return false + val newTopSites = (other as? TopSitePager) ?: return false if (newTopSites.topSites.size != this.topSites.size) return false val newSitesSequence = newTopSites.topSites.asSequence() val oldTopSites = this.topSites.asSequence() @@ -147,7 +147,7 @@ class SessionControlAdapter( val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false) return when (viewType) { ButtonTipViewHolder.LAYOUT_ID -> ButtonTipViewHolder(view, interactor) - TopSiteViewHolder.LAYOUT_ID -> TopSiteViewHolder(view, interactor) + TopSitePagerViewHolder.LAYOUT_ID -> TopSitePagerViewHolder(view, interactor) PrivateBrowsingDescriptionViewHolder.LAYOUT_ID -> PrivateBrowsingDescriptionViewHolder( view, interactor @@ -203,8 +203,8 @@ class SessionControlAdapter( val tipItem = item as AdapterItem.TipItem holder.bind(tipItem.tip) } - is TopSiteViewHolder -> { - holder.bind((item as AdapterItem.TopSiteList).topSites) + is TopSitePagerViewHolder -> { + holder.bind((item as AdapterItem.TopSitePager).topSites) } is CollectionViewHolder -> { val (collection, expanded) = item as AdapterItem.CollectionItem 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 73f1585bf..5aaaa3854 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 @@ -35,7 +35,7 @@ private fun normalModeAdapterItems( tip?.let { items.add(AdapterItem.TipItem(it)) } if (topSites.isNotEmpty()) { - items.add(AdapterItem.TopSiteList(topSites)) + items.add(AdapterItem.TopSitePager(topSites)) } if (collections.isEmpty()) { @@ -160,7 +160,7 @@ class SessionControlView( sessionControlAdapter.submitList(stateAdapterList) { val loadedTopSites = stateAdapterList.find { adapterItem -> - adapterItem is AdapterItem.TopSiteList && adapterItem.topSites.isNotEmpty() + adapterItem is AdapterItem.TopSitePager && adapterItem.topSites.isNotEmpty() } loadedTopSites?.run { homeScreenViewModel.shouldScrollToTopSites = false diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/TopSitePagerViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/TopSitePagerViewHolder.kt new file mode 100644 index 000000000..ab0142fa1 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/TopSitePagerViewHolder.kt @@ -0,0 +1,57 @@ +/* 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.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import androidx.viewpager2.widget.ViewPager2 +import kotlinx.android.synthetic.main.component_top_sites_pager.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.TopSitesPagerAdapter + +class TopSitePagerViewHolder( + view: View, + interactor: TopSiteInteractor +) : RecyclerView.ViewHolder(view) { + + private val topSitesPagerAdapter = TopSitesPagerAdapter(interactor) + private val pageIndicator = view.page_indicator + + private val topSitesPageChangeCallback = object : ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + pageIndicator.setSelection(position) + } + } + + init { + view.top_sites_pager.apply { + adapter = topSitesPagerAdapter + registerOnPageChangeCallback(topSitesPageChangeCallback) + } + } + + fun bind(topSites: List) { + topSitesPagerAdapter.updateData(topSites) + + // Don't show any page indicator if there is only 1 page. + val numPages = if (topSites.size > TOP_SITES_PER_PAGE) { + TOP_SITES_MAX_PAGE_SIZE + } else { + 0 + } + + pageIndicator.isVisible = numPages > 1 + pageIndicator.setSize(numPages) + } + + companion object { + const val LAYOUT_ID = R.layout.component_top_sites_pager + const val TOP_SITES_MAX_PAGE_SIZE = 2 + const val TOP_SITES_PER_PAGE = 8 + } +} diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/TopSiteViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/TopSiteViewHolder.kt index 44359036e..7f1d9b91e 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/TopSiteViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/TopSiteViewHolder.kt @@ -6,7 +6,6 @@ package org.mozilla.fenix.home.sessioncontrol.viewholders import android.view.View import androidx.recyclerview.widget.RecyclerView -import com.google.android.flexbox.FlexboxLayoutManager import kotlinx.android.synthetic.main.component_top_sites.view.* import mozilla.components.feature.top.sites.TopSite import org.mozilla.fenix.R @@ -23,8 +22,6 @@ class TopSiteViewHolder( init { view.top_sites_list.apply { adapter = topSitesAdapter - layoutManager = FlexboxLayoutManager(view.context) - isNestedScrollingEnabled = false } } diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/PagerIndicator.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/PagerIndicator.kt new file mode 100644 index 000000000..dd5acdf5e --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/PagerIndicator.kt @@ -0,0 +1,82 @@ +/* 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.content.Context +import android.util.AttributeSet +import android.util.TypedValue +import android.view.View +import android.widget.LinearLayout +import androidx.core.view.MarginLayoutParamsCompat +import org.mozilla.fenix.R + +/** + * A pager indicator widget to display the number of pages and the current selected page. + */ +class PagerIndicator : LinearLayout { + + constructor(context: Context) : super(context) + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) + + private var selectedIndex = 0 + + /** + * Set the number of pager dots to display. + */ + fun setSize(size: Int) { + if (childCount == size) { + return + } + if (selectedIndex >= size) { + selectedIndex = size - 1 + } + + removeAllViews() + for (i in 0 until size) { + val isLast = i == size - 1 + addView( + View(context).apply { + setBackgroundResource(R.drawable.pager_dot) + isSelected = i == selectedIndex + }, + LayoutParams(dpToPx(DOT_SIZE_IN_DP), dpToPx(DOT_SIZE_IN_DP)).apply { + if (!isLast) { + MarginLayoutParamsCompat.setMarginEnd(this, dpToPx(DOT_MARGIN)) + } + } + ) + } + } + + /** + * Set the current selected pager dot. + */ + fun setSelection(index: Int) { + if (selectedIndex == index) { + return + } + + getChildAt(selectedIndex)?.run { + isSelected = false + } + getChildAt(index)?.run { + isSelected = true + } + selectedIndex = index + } + + companion object { + private const val DOT_SIZE_IN_DP = 6f + private const val DOT_MARGIN = 4f + } +} + +fun Context.dpToPx(value: Float): Int = + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, resources.displayMetrics).toInt() + +fun View.dpToPx(value: Float): Int = context.dpToPx(value) diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSitesPagerAdapter.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSitesPagerAdapter.kt new file mode 100644 index 000000000..b335e57de --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSitesPagerAdapter.kt @@ -0,0 +1,40 @@ +/* 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.RecyclerView +import mozilla.components.feature.top.sites.TopSite +import org.mozilla.fenix.home.sessioncontrol.TopSiteInteractor +import org.mozilla.fenix.home.sessioncontrol.viewholders.TopSiteViewHolder + +class TopSitesPagerAdapter( + private val interactor: TopSiteInteractor +) : RecyclerView.Adapter() { + + private var topSites: List> = listOf() + + fun updateData(topSites: List) { + this.topSites = topSites.chunked(TOP_SITES_PER_PAGE) + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TopSiteViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(TopSiteViewHolder.LAYOUT_ID, parent, false) + return TopSiteViewHolder(view, interactor) + } + + override fun onBindViewHolder(holder: TopSiteViewHolder, position: Int) { + holder.bind(this.topSites[position]) + } + + override fun getItemCount(): Int = this.topSites.size + + companion object { + const val TOP_SITES_PER_PAGE = 8 + } +} diff --git a/app/src/main/res/color/pager_dot.xml b/app/src/main/res/color/pager_dot.xml new file mode 100644 index 000000000..c34fa0ffc --- /dev/null +++ b/app/src/main/res/color/pager_dot.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/pager_dot.xml b/app/src/main/res/drawable/pager_dot.xml new file mode 100644 index 000000000..869199def --- /dev/null +++ b/app/src/main/res/drawable/pager_dot.xml @@ -0,0 +1,12 @@ + + + + + + + diff --git a/app/src/main/res/layout/component_top_sites.xml b/app/src/main/res/layout/component_top_sites.xml index c0031d108..b9e0861f8 100644 --- a/app/src/main/res/layout/component_top_sites.xml +++ b/app/src/main/res/layout/component_top_sites.xml @@ -2,12 +2,19 @@ + diff --git a/app/src/main/res/layout/component_top_sites_pager.xml b/app/src/main/res/layout/component_top_sites_pager.xml new file mode 100644 index 000000000..f6f66d7fd --- /dev/null +++ b/app/src/main/res/layout/component_top_sites_pager.xml @@ -0,0 +1,24 @@ + + + + + + + + + + diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index 29193af41..2ecd8fbb8 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -72,6 +72,8 @@ @color/top_site_title_text_dark_theme + #3A3944 + #5B5B66 @color/synced_tabs_separator_dark_theme diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 3dbeaf224..a8938b5a2 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -4,6 +4,7 @@ - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + #BFBFC9 #312A65 #7A312A65 #9059FF @@ -42,7 +43,7 @@ @color/foundation_light_theme @color/foundation_light_theme @color/foundation_light_theme - #BFBFC9 + @color/light_grey_50 @color/accent_light_theme #C45A27 #FFFDE2 @@ -271,6 +272,8 @@ @color/top_site_title_text_light_theme + @color/photonLightGrey30 + @color/light_grey_50 @color/synced_tabs_separator_light_theme diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 8ba27fd3e..7118dad51 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -28,7 +28,6 @@ object Versions { const val androidx_transition = "1.3.0" const val androidx_work = "2.2.0" const val google_material = "1.1.0" - const val google_flexbox = "2.0.1" const val mozilla_android_components = AndroidComponents.VERSION @@ -175,7 +174,6 @@ object Deps { const val androidx_work_ktx = "androidx.work:work-runtime-ktx:${Versions.androidx_work}" const val androidx_work_testing = "androidx.work:work-testing:${Versions.androidx_work}" const val google_material = "com.google.android.material:material:${Versions.google_material}" - const val google_flexbox = "com.google.android:flexbox:${Versions.google_flexbox}" const val adjust = "com.adjust.sdk:adjust-android:${Versions.adjust}" const val installreferrer = "com.android.installreferrer:installreferrer:${Versions.installreferrer}"