diff --git a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt index b7ecfe8bc..7c156b5b2 100644 --- a/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt +++ b/app/src/androidTest/java/org/mozilla/fenix/ui/robots/HomeScreenRobot.kt @@ -703,7 +703,7 @@ private fun assertExistingTopSitesList() = private fun assertExistingTopSitesTabs(title: String) = onView(allOf(withId(R.id.top_sites_list))) - .check(matches(hasItem(hasDescendant(withText(title))))) + .check(matches(hasDescendant(withText(title)))) .check(matches(withEffectiveVisibility(Visibility.VISIBLE))) private fun assertNotExistingTopSitesList(title: String) = 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 e789a9b90..e8d22d4c2 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 @@ -43,7 +43,12 @@ sealed class AdapterItem(@LayoutRes val viewType: Int) { ButtonTipViewHolder.LAYOUT_ID ) - data class TopSitePager(val topSites: List) : AdapterItem(TopSitePagerViewHolder.LAYOUT_ID) { + data class TopSitePagerPayload( + val changed: Set> + ) + + data class TopSitePager(val topSites: List) : + AdapterItem(TopSitePagerViewHolder.LAYOUT_ID) { override fun sameAs(other: AdapterItem): Boolean { val newTopSites = (other as? TopSitePager) ?: return false return newTopSites.topSites.size == this.topSites.size @@ -56,6 +61,19 @@ sealed class AdapterItem(@LayoutRes val viewType: Int) { val oldTopSites = this.topSites.asSequence() return newSitesSequence.zip(oldTopSites).all { (new, old) -> new == old } } + + override fun getChangePayload(newItem: AdapterItem): Any? { + val newTopSites = (newItem as? TopSitePager) ?: return null + val oldTopSites = (this as? TopSitePager) ?: return null + + val changed = mutableSetOf>() + for ((index, item) in newTopSites.topSites.withIndex()) { + if (oldTopSites.topSites.getOrNull(index) != item) { + changed.add(Pair(index, item)) + } + } + return if (changed.isNotEmpty()) TopSitePagerPayload(changed) else null + } } object PrivateBrowsingDescription : AdapterItem(PrivateBrowsingDescriptionViewHolder.LAYOUT_ID) @@ -195,6 +213,25 @@ class SessionControlAdapter( override fun getItemViewType(position: Int) = getItem(position).viewType + override fun onBindViewHolder( + holder: RecyclerView.ViewHolder, + position: Int, + payloads: MutableList + ) { + if (payloads.isNullOrEmpty()) { + onBindViewHolder(holder, position) + } else { + when (holder) { + is TopSitePagerViewHolder -> { + if (payloads[0] is AdapterItem.TopSitePagerPayload) { + val payload = payloads[0] as AdapterItem.TopSitePagerPayload + holder.update(payload) + } + } + } + } + } + @SuppressWarnings("ComplexMethod") override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val item = getItem(position) 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 index 23e59a081..724aa4730 100644 --- 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 @@ -13,6 +13,7 @@ import mozilla.components.feature.top.sites.TopSite import org.mozilla.fenix.R import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.ext.components +import org.mozilla.fenix.home.sessioncontrol.AdapterItem import org.mozilla.fenix.home.sessioncontrol.TopSiteInteractor import org.mozilla.fenix.home.sessioncontrol.viewholders.topsites.TopSitesPagerAdapter @@ -28,7 +29,11 @@ class TopSitePagerViewHolder( private val topSitesPageChangeCallback = object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { if (currentPage != position) { - pageIndicator.context.components.analytics.metrics.track(Event.TopSiteSwipeCarousel(position)) + pageIndicator.context.components.analytics.metrics.track( + Event.TopSiteSwipeCarousel( + position + ) + ) } pageIndicator.setSelection(position) @@ -43,6 +48,12 @@ class TopSitePagerViewHolder( } } + fun update(payload: AdapterItem.TopSitePagerPayload) { + for (item in payload.changed) { + topSitesPagerAdapter.notifyItemChanged(currentPage, payload) + } + } + fun bind(topSites: List) { val chunkedTopSites = topSites.chunked(TOP_SITES_PER_PAGE) topSitesPagerAdapter.submitList(chunkedTopSites) diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSitesAdapter.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSitesAdapter.kt index d511081d7..78bf438d0 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSitesAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSitesAdapter.kt @@ -8,13 +8,14 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter +import kotlinx.android.synthetic.main.top_site_item.view.* import mozilla.components.feature.top.sites.TopSite import org.mozilla.fenix.home.sessioncontrol.TopSiteInteractor import org.mozilla.fenix.perf.StartupTimeline class TopSitesAdapter( private val interactor: TopSiteInteractor -) : ListAdapter(DiffCallback) { +) : ListAdapter(TopSitesDiffCallback) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TopSiteItemViewHolder { val view = LayoutInflater.from(parent.context) .inflate(TopSiteItemViewHolder.LAYOUT_ID, parent, false) @@ -26,11 +27,39 @@ class TopSitesAdapter( holder.bind(getItem(position)) } - private object DiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: TopSite, newItem: TopSite) = - oldItem.id == newItem.id || oldItem.title == newItem.title || oldItem.url == newItem.url + override fun onBindViewHolder( + holder: TopSiteItemViewHolder, + position: Int, + payloads: MutableList + ) { + if (payloads.isNullOrEmpty()) { + onBindViewHolder(holder, position) + } else { + when (payloads[0]) { + is TopSite -> { + holder.bind((payloads[0] as TopSite)) + } + is TopSitePayload -> { + holder.itemView.top_site_title.text = (payloads[0] as TopSitePayload).newTitle + } + } + } + } + + data class TopSitePayload( + val newTitle: String? + ) + + internal object TopSitesDiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: TopSite, newItem: TopSite) = oldItem.id == newItem.id override fun areContentsTheSame(oldItem: TopSite, newItem: TopSite) = - oldItem.id == newItem.id || oldItem.title == newItem.title || oldItem.url == newItem.url + oldItem.id == newItem.id && oldItem.title == newItem.title && oldItem.url == newItem.url + + override fun getChangePayload(oldItem: TopSite, newItem: TopSite): Any? { + return if (oldItem.id == newItem.id && oldItem.url == newItem.url && oldItem.title != newItem.title) { + TopSitePayload(newItem.title) + } else null + } } } 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 index e1d1d571f..c960dc69f 100644 --- 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 @@ -10,12 +10,13 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import kotlinx.android.synthetic.main.component_top_sites.view.* import mozilla.components.feature.top.sites.TopSite +import org.mozilla.fenix.home.sessioncontrol.AdapterItem import org.mozilla.fenix.home.sessioncontrol.TopSiteInteractor import org.mozilla.fenix.home.sessioncontrol.viewholders.TopSiteViewHolder class TopSitesPagerAdapter( private val interactor: TopSiteInteractor -) : ListAdapter, TopSiteViewHolder>(DiffCallback) { +) : ListAdapter, TopSiteViewHolder>(TopSiteListDiffCallback) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TopSiteViewHolder { val view = LayoutInflater.from(parent.context) @@ -23,12 +24,30 @@ class TopSitesPagerAdapter( return TopSiteViewHolder(view, interactor) } + override fun onBindViewHolder( + holder: TopSiteViewHolder, + position: Int, + payloads: MutableList + ) { + if (payloads.isNullOrEmpty()) { + onBindViewHolder(holder, position) + } else { + if (payloads[0] is AdapterItem.TopSitePagerPayload) { + val adapter = holder.itemView.top_sites_list.adapter as TopSitesAdapter + val payload = payloads[0] as AdapterItem.TopSitePagerPayload + for (item in payload.changed) { + adapter.notifyItemChanged(item.first, item.second) + } + } + } + } + override fun onBindViewHolder(holder: TopSiteViewHolder, position: Int) { val adapter = holder.itemView.top_sites_list.adapter as TopSitesAdapter adapter.submitList(getItem(position)) } - private object DiffCallback : DiffUtil.ItemCallback>() { + internal object TopSiteListDiffCallback : DiffUtil.ItemCallback>() { override fun areItemsTheSame(oldItem: List, newItem: List): Boolean { return oldItem.size == newItem.size } @@ -36,5 +55,15 @@ class TopSitesPagerAdapter( override fun areContentsTheSame(oldItem: List, newItem: List): Boolean { return newItem.zip(oldItem).all { (new, old) -> new == old } } + + override fun getChangePayload(oldItem: List, newItem: List): Any? { + val changed = mutableSetOf>() + for ((index, item) in newItem.withIndex()) { + if (oldItem.getOrNull(index) != item) { + changed.add(Pair(index, item)) + } + } + return if (changed.isNotEmpty()) AdapterItem.TopSitePagerPayload(changed) else null + } } } diff --git a/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSitesAdapterTest.kt b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSitesAdapterTest.kt new file mode 100644 index 000000000..b64a0130d --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSitesAdapterTest.kt @@ -0,0 +1,48 @@ +/* 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 mozilla.components.feature.top.sites.TopSite +import org.junit.Assert.assertEquals +import org.junit.Test + +class TopSitesAdapterTest { + + @Test + fun testDiffCallback() { + val topSite = TopSite( + id = 1L, + title = "Title1", + url = "https://mozilla.org", + null, + TopSite.Type.DEFAULT + ) + val topSite2 = TopSite( + id = 1L, + title = "Title2", + url = "https://mozilla.org", + null, + TopSite.Type.DEFAULT + ) + + assertEquals( + TopSitesAdapter.TopSitesDiffCallback.getChangePayload(topSite, topSite2), + TopSitesAdapter.TopSitePayload("Title2") + ) + + val topSite3 = TopSite( + id = 2L, + title = "Title2", + url = "https://firefox.org", + null, + TopSite.Type.DEFAULT + ) + + assertEquals( + TopSitesAdapter.TopSitesDiffCallback.getChangePayload(topSite, topSite3), + null + ) + } +} diff --git a/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSitesPagerAdapterTest.kt b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSitesPagerAdapterTest.kt new file mode 100644 index 000000000..d8d26bf17 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/topsites/TopSitesPagerAdapterTest.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.viewholders.topsites + +import mozilla.components.feature.top.sites.TopSite +import org.junit.Assert.assertEquals +import org.junit.Test +import org.mozilla.fenix.home.sessioncontrol.AdapterItem + +class TopSitesPagerAdapterTest { + @Test + fun testDiffCallback() { + val topSite = TopSite( + id = 1L, + title = "Title1", + url = "https://mozilla.org", + null, + TopSite.Type.DEFAULT + ) + + val topSite2 = TopSite( + id = 2L, + title = "Title2", + url = "https://mozilla.org", + null, + TopSite.Type.DEFAULT + ) + + val topSite3 = TopSite( + id = 3L, + title = "Title2", + url = "https://firefox.org", + null, + TopSite.Type.DEFAULT + ) + + assertEquals( + TopSitesPagerAdapter.TopSiteListDiffCallback.getChangePayload( + listOf(topSite, topSite3), + listOf(topSite, topSite2) + ), + AdapterItem.TopSitePagerPayload(setOf(Pair(1, topSite2))) + ) + } +}