Close #22402: Add top placeholder for home
This commit is contained in:
parent
dde916038d
commit
24e4452cb5
|
@ -25,7 +25,7 @@ private const val EXPECTED_SUPPRESSION_COUNT = 19
|
||||||
@Suppress("TopLevelPropertyNaming") // it's silly this would have a different naming convention b/c no const
|
@Suppress("TopLevelPropertyNaming") // it's silly this would have a different naming convention b/c no const
|
||||||
private val EXPECTED_RUNBLOCKING_RANGE = 0..1 // CI has +1 counts compared to local runs: increment these together
|
private val EXPECTED_RUNBLOCKING_RANGE = 0..1 // CI has +1 counts compared to local runs: increment these together
|
||||||
private const val EXPECTED_RECYCLER_VIEW_CONSTRAINT_LAYOUT_CHILDREN = 4
|
private const val EXPECTED_RECYCLER_VIEW_CONSTRAINT_LAYOUT_CHILDREN = 4
|
||||||
private const val EXPECTED_NUMBER_OF_INFLATION = 12
|
private const val EXPECTED_NUMBER_OF_INFLATION = 13
|
||||||
|
|
||||||
private val failureMsgStrictMode = getErrorMessage(
|
private val failureMsgStrictMode = getErrorMessage(
|
||||||
shortName = "StrictMode suppression",
|
shortName = "StrictMode suppression",
|
||||||
|
|
|
@ -377,8 +377,7 @@ class HomeFragment : Fragment() {
|
||||||
homeFragmentStore,
|
homeFragmentStore,
|
||||||
binding.sessionControlRecyclerView,
|
binding.sessionControlRecyclerView,
|
||||||
viewLifecycleOwner,
|
viewLifecycleOwner,
|
||||||
sessionControlInteractor,
|
sessionControlInteractor
|
||||||
homeViewModel
|
|
||||||
)
|
)
|
||||||
|
|
||||||
updateSessionControlView()
|
updateSessionControlView()
|
||||||
|
@ -546,8 +545,6 @@ class HomeFragment : Fragment() {
|
||||||
if (bundleArgs.getBoolean(FOCUS_ON_ADDRESS_BAR)) {
|
if (bundleArgs.getBoolean(FOCUS_ON_ADDRESS_BAR)) {
|
||||||
navigateToSearch()
|
navigateToSearch()
|
||||||
} else if (bundleArgs.getLong(FOCUS_ON_COLLECTION, -1) >= 0) {
|
} else if (bundleArgs.getLong(FOCUS_ON_COLLECTION, -1) >= 0) {
|
||||||
// No need to scroll to async'd loaded TopSites if we want to scroll to collections.
|
|
||||||
homeViewModel.shouldScrollToTopSites = false
|
|
||||||
/* Triggered when the user has added a tab to a collection and has tapped
|
/* Triggered when the user has added a tab to a collection and has tapped
|
||||||
* the View action on the [TabsTrayDialogFragment] snackbar.*/
|
* the View action on the [TabsTrayDialogFragment] snackbar.*/
|
||||||
scrollAndAnimateCollection(bundleArgs.getLong(FOCUS_ON_COLLECTION, -1))
|
scrollAndAnimateCollection(bundleArgs.getLong(FOCUS_ON_COLLECTION, -1))
|
||||||
|
|
|
@ -11,9 +11,4 @@ class HomeScreenViewModel : ViewModel() {
|
||||||
* Used to delete a specific session once the home screen is resumed
|
* Used to delete a specific session once the home screen is resumed
|
||||||
*/
|
*/
|
||||||
var sessionToDelete: String? = null
|
var sessionToDelete: String? = null
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to remember if we need to scroll to top of the homeFragment's recycleView (top sites) see #8561
|
|
||||||
* */
|
|
||||||
var shouldScrollToTopSites: Boolean = true
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
/* 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
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.utils.view.ViewHolder
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View holder for a synchronous, unconditional and invisible placeholder. This is to anchor home to
|
||||||
|
* the top when home is created.
|
||||||
|
*/
|
||||||
|
class TopPlaceholderViewHolder(
|
||||||
|
view: View
|
||||||
|
) : ViewHolder(view) {
|
||||||
|
|
||||||
|
fun bind() = Unit
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val LAYOUT_ID = R.layout.top_placeholder_item
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ import org.mozilla.fenix.components.tips.Tip
|
||||||
import org.mozilla.fenix.historymetadata.view.HistoryMetadataGroupViewHolder
|
import org.mozilla.fenix.historymetadata.view.HistoryMetadataGroupViewHolder
|
||||||
import org.mozilla.fenix.historymetadata.view.HistoryMetadataHeaderViewHolder
|
import org.mozilla.fenix.historymetadata.view.HistoryMetadataHeaderViewHolder
|
||||||
import org.mozilla.fenix.home.HomeFragmentStore
|
import org.mozilla.fenix.home.HomeFragmentStore
|
||||||
|
import org.mozilla.fenix.home.TopPlaceholderViewHolder
|
||||||
import org.mozilla.fenix.home.OnboardingState
|
import org.mozilla.fenix.home.OnboardingState
|
||||||
import org.mozilla.fenix.home.recentbookmarks.view.RecentBookmarksViewHolder
|
import org.mozilla.fenix.home.recentbookmarks.view.RecentBookmarksViewHolder
|
||||||
import org.mozilla.fenix.home.recenttabs.view.RecentTabViewHolder
|
import org.mozilla.fenix.home.recenttabs.view.RecentTabViewHolder
|
||||||
|
@ -49,6 +50,7 @@ import org.mozilla.fenix.home.topsites.TopSitePagerViewHolder
|
||||||
import mozilla.components.feature.tab.collections.Tab as ComponentTab
|
import mozilla.components.feature.tab.collections.Tab as ComponentTab
|
||||||
|
|
||||||
sealed class AdapterItem(@LayoutRes val viewType: Int) {
|
sealed class AdapterItem(@LayoutRes val viewType: Int) {
|
||||||
|
object TopPlaceholderItem : AdapterItem(TopPlaceholderViewHolder.LAYOUT_ID)
|
||||||
data class TipItem(val tip: Tip) : AdapterItem(
|
data class TipItem(val tip: Tip) : AdapterItem(
|
||||||
ButtonTipViewHolder.LAYOUT_ID
|
ButtonTipViewHolder.LAYOUT_ID
|
||||||
)
|
)
|
||||||
|
@ -255,6 +257,7 @@ class SessionControlAdapter(
|
||||||
|
|
||||||
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
||||||
return when (viewType) {
|
return when (viewType) {
|
||||||
|
TopPlaceholderViewHolder.LAYOUT_ID -> TopPlaceholderViewHolder(view)
|
||||||
ButtonTipViewHolder.LAYOUT_ID -> ButtonTipViewHolder(view, interactor)
|
ButtonTipViewHolder.LAYOUT_ID -> ButtonTipViewHolder(view, interactor)
|
||||||
TopSitePagerViewHolder.LAYOUT_ID -> TopSitePagerViewHolder(view, interactor)
|
TopSitePagerViewHolder.LAYOUT_ID -> TopSitePagerViewHolder(view, interactor)
|
||||||
PrivateBrowsingDescriptionViewHolder.LAYOUT_ID -> PrivateBrowsingDescriptionViewHolder(
|
PrivateBrowsingDescriptionViewHolder.LAYOUT_ID -> PrivateBrowsingDescriptionViewHolder(
|
||||||
|
@ -350,6 +353,9 @@ class SessionControlAdapter(
|
||||||
val tipItem = item as AdapterItem.TipItem
|
val tipItem = item as AdapterItem.TipItem
|
||||||
holder.bind(tipItem.tip)
|
holder.bind(tipItem.tip)
|
||||||
}
|
}
|
||||||
|
is TopPlaceholderViewHolder -> {
|
||||||
|
holder.bind()
|
||||||
|
}
|
||||||
is TopSitePagerViewHolder -> {
|
is TopSitePagerViewHolder -> {
|
||||||
holder.bind((item as AdapterItem.TopSitePager).topSites)
|
holder.bind((item as AdapterItem.TopSitePager).topSites)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,6 @@ import org.mozilla.fenix.ext.settings
|
||||||
import org.mozilla.fenix.historymetadata.HistoryMetadataGroup
|
import org.mozilla.fenix.historymetadata.HistoryMetadataGroup
|
||||||
import org.mozilla.fenix.home.HomeFragmentState
|
import org.mozilla.fenix.home.HomeFragmentState
|
||||||
import org.mozilla.fenix.home.HomeFragmentStore
|
import org.mozilla.fenix.home.HomeFragmentStore
|
||||||
import org.mozilla.fenix.home.HomeScreenViewModel
|
|
||||||
import org.mozilla.fenix.home.Mode
|
import org.mozilla.fenix.home.Mode
|
||||||
import org.mozilla.fenix.home.OnboardingState
|
import org.mozilla.fenix.home.OnboardingState
|
||||||
import org.mozilla.fenix.home.recenttabs.RecentTab
|
import org.mozilla.fenix.home.recenttabs.RecentTab
|
||||||
|
@ -46,6 +45,9 @@ internal fun normalModeAdapterItems(
|
||||||
val items = mutableListOf<AdapterItem>()
|
val items = mutableListOf<AdapterItem>()
|
||||||
var shouldShowCustomizeHome = false
|
var shouldShowCustomizeHome = false
|
||||||
|
|
||||||
|
// Add a synchronous, unconditional and invisible placeholder so home is anchored to the top when created.
|
||||||
|
items.add(AdapterItem.TopPlaceholderItem)
|
||||||
|
|
||||||
tip?.let { items.add(AdapterItem.TipItem(it)) }
|
tip?.let { items.add(AdapterItem.TipItem(it)) }
|
||||||
|
|
||||||
if (showSetAsDefaultBrowserCard) {
|
if (showSetAsDefaultBrowserCard) {
|
||||||
|
@ -182,8 +184,7 @@ class SessionControlView(
|
||||||
store: HomeFragmentStore,
|
store: HomeFragmentStore,
|
||||||
val containerView: View,
|
val containerView: View,
|
||||||
viewLifecycleOwner: LifecycleOwner,
|
viewLifecycleOwner: LifecycleOwner,
|
||||||
internal val interactor: SessionControlInteractor,
|
internal val interactor: SessionControlInteractor
|
||||||
private var homeScreenViewModel: HomeScreenViewModel
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val view: RecyclerView = containerView as RecyclerView
|
val view: RecyclerView = containerView as RecyclerView
|
||||||
|
@ -222,20 +223,6 @@ class SessionControlView(
|
||||||
|
|
||||||
if (shouldReportMetrics) interactor.reportSessionMetrics(state)
|
if (shouldReportMetrics) interactor.reportSessionMetrics(state)
|
||||||
|
|
||||||
val stateAdapterList = state.toAdapterList()
|
sessionControlAdapter.submitList(state.toAdapterList())
|
||||||
if (homeScreenViewModel.shouldScrollToTopSites) {
|
|
||||||
sessionControlAdapter.submitList(stateAdapterList) {
|
|
||||||
|
|
||||||
val loadedTopSites = stateAdapterList.find { adapterItem ->
|
|
||||||
adapterItem is AdapterItem.TopSitePager && adapterItem.topSites.isNotEmpty()
|
|
||||||
}
|
|
||||||
loadedTopSites?.run {
|
|
||||||
homeScreenViewModel.shouldScrollToTopSites = false
|
|
||||||
view.scrollToPosition(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sessionControlAdapter.submitList(stateAdapterList)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
7
app/src/main/res/layout/top_placeholder_item.xml
Normal file
7
app/src/main/res/layout/top_placeholder_item.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?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/. -->
|
||||||
|
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"/>
|
|
@ -14,6 +14,7 @@ import mozilla.components.feature.tab.collections.TabCollection
|
||||||
import mozilla.components.feature.top.sites.TopSite
|
import mozilla.components.feature.top.sites.TopSite
|
||||||
import mozilla.components.service.pocket.PocketRecommendedStory
|
import mozilla.components.service.pocket.PocketRecommendedStory
|
||||||
import mozilla.components.support.test.robolectric.testContext
|
import mozilla.components.support.test.robolectric.testContext
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Assert.assertFalse
|
import org.junit.Assert.assertFalse
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -96,8 +97,7 @@ class SessionControlViewTest {
|
||||||
mockk(relaxed = true),
|
mockk(relaxed = true),
|
||||||
view,
|
view,
|
||||||
mockk(relaxed = true),
|
mockk(relaxed = true),
|
||||||
interactor,
|
interactor
|
||||||
mockk(relaxed = true)
|
|
||||||
)
|
)
|
||||||
val recentTabs = listOf<RecentTab>(mockk(relaxed = true))
|
val recentTabs = listOf<RecentTab>(mockk(relaxed = true))
|
||||||
|
|
||||||
|
@ -118,8 +118,7 @@ class SessionControlViewTest {
|
||||||
mockk(relaxed = true),
|
mockk(relaxed = true),
|
||||||
view,
|
view,
|
||||||
mockk(relaxed = true),
|
mockk(relaxed = true),
|
||||||
interactor,
|
interactor
|
||||||
mockk(relaxed = true)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val state = HomeFragmentState()
|
val state = HomeFragmentState()
|
||||||
|
@ -155,8 +154,9 @@ class SessionControlViewTest {
|
||||||
pocketArticles
|
pocketArticles
|
||||||
)
|
)
|
||||||
|
|
||||||
assertTrue(results[0] is AdapterItem.RecentBookmarks)
|
assertTrue(results[0] is AdapterItem.TopPlaceholderItem)
|
||||||
assertTrue(results[1] is AdapterItem.CustomizeHomeButton)
|
assertTrue(results[1] is AdapterItem.RecentBookmarks)
|
||||||
|
assertTrue(results[2] is AdapterItem.CustomizeHomeButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -182,9 +182,10 @@ class SessionControlViewTest {
|
||||||
pocketArticles
|
pocketArticles
|
||||||
)
|
)
|
||||||
|
|
||||||
assertTrue(results[0] is AdapterItem.RecentTabsHeader)
|
assertTrue(results[0] is AdapterItem.TopPlaceholderItem)
|
||||||
assertTrue(results[1] is AdapterItem.RecentTabItem)
|
assertTrue(results[1] is AdapterItem.RecentTabsHeader)
|
||||||
assertTrue(results[2] is AdapterItem.CustomizeHomeButton)
|
assertTrue(results[2] is AdapterItem.RecentTabItem)
|
||||||
|
assertTrue(results[3] is AdapterItem.CustomizeHomeButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -210,9 +211,10 @@ class SessionControlViewTest {
|
||||||
pocketArticles
|
pocketArticles
|
||||||
)
|
)
|
||||||
|
|
||||||
assertTrue(results[0] is AdapterItem.HistoryMetadataHeader)
|
assertTrue(results[0] is AdapterItem.TopPlaceholderItem)
|
||||||
assertTrue(results[1] is AdapterItem.HistoryMetadataGroup)
|
assertTrue(results[1] is AdapterItem.HistoryMetadataHeader)
|
||||||
assertTrue(results[2] is AdapterItem.CustomizeHomeButton)
|
assertTrue(results[2] is AdapterItem.HistoryMetadataGroup)
|
||||||
|
assertTrue(results[3] is AdapterItem.CustomizeHomeButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -238,8 +240,9 @@ class SessionControlViewTest {
|
||||||
pocketArticles
|
pocketArticles
|
||||||
)
|
)
|
||||||
|
|
||||||
assertTrue(results[0] is AdapterItem.PocketStoriesItem)
|
assertTrue(results[0] is AdapterItem.TopPlaceholderItem)
|
||||||
assertTrue(results[1] is AdapterItem.CustomizeHomeButton)
|
assertTrue(results[1] is AdapterItem.PocketStoriesItem)
|
||||||
|
assertTrue(results[2] is AdapterItem.CustomizeHomeButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -264,6 +267,36 @@ class SessionControlViewTest {
|
||||||
historyMetadata,
|
historyMetadata,
|
||||||
pocketArticles
|
pocketArticles
|
||||||
)
|
)
|
||||||
assertTrue(results.isEmpty())
|
assertEquals(results.size, 1)
|
||||||
|
assertTrue(results[0] is AdapterItem.TopPlaceholderItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN all items THEN top placeholder item is always the first item`() {
|
||||||
|
val collection = mockk<TabCollection> {
|
||||||
|
every { id } returns 123L
|
||||||
|
}
|
||||||
|
val topSites = listOf<TopSite>(mockk())
|
||||||
|
val collections = listOf(collection)
|
||||||
|
val expandedCollections = emptySet<Long>()
|
||||||
|
val recentBookmarks = listOf<BookmarkNode>(mockk())
|
||||||
|
val recentTabs = listOf<RecentTab.Tab>(mockk())
|
||||||
|
val historyMetadata = listOf<HistoryMetadataGroup>(mockk())
|
||||||
|
val pocketArticles = listOf<PocketRecommendedStory>(mockk())
|
||||||
|
|
||||||
|
val results = normalModeAdapterItems(
|
||||||
|
topSites,
|
||||||
|
collections,
|
||||||
|
expandedCollections,
|
||||||
|
null,
|
||||||
|
recentBookmarks,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
recentTabs,
|
||||||
|
historyMetadata,
|
||||||
|
pocketArticles
|
||||||
|
)
|
||||||
|
|
||||||
|
assertTrue(results[0] is AdapterItem.TopPlaceholderItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user