[fenix] For https://github.com/mozilla-mobile/fenix/issues/19933 - Show a media tab item on homescreen for the last tab with media
parent
e671e5314e
commit
e469026805
@ -0,0 +1,132 @@
|
||||
/* 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.recenttabs.view
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import mozilla.components.support.ktx.android.content.getColorFromAttr
|
||||
import mozilla.components.support.ktx.android.util.dpToPx
|
||||
import org.mozilla.fenix.R
|
||||
|
||||
private const val TOP_MARGIN_DP = 1
|
||||
|
||||
/**
|
||||
* All possible positions of a recent tab in relation to others when shown in the "Jump back in" section.
|
||||
*/
|
||||
enum class RecentTabsItemPosition {
|
||||
/**
|
||||
* This is the only tab to be shown in this section.
|
||||
*/
|
||||
SINGLE,
|
||||
|
||||
/**
|
||||
* This item is to be shown at the top of the section with others below it.
|
||||
*/
|
||||
TOP,
|
||||
|
||||
/**
|
||||
* This item is to be shown between others in this section.
|
||||
*/
|
||||
MIDDLE,
|
||||
|
||||
/**
|
||||
* This item is to be shown at the bottom of the section with others above it.
|
||||
*/
|
||||
BOTTOM
|
||||
}
|
||||
|
||||
/**
|
||||
* Helpers for setting various layout properties for the view from a [RecentTabViewHolder].
|
||||
*
|
||||
* Depending on the provided [RecentTabsItemPosition]:
|
||||
* - sets a different background so that the entire section possibly containing
|
||||
* more such items would have rounded corners but sibling items not.
|
||||
* - sets small margins for the items so that there's a clear separation between siblings
|
||||
*/
|
||||
sealed class RecentTabViewDecorator {
|
||||
/**
|
||||
* Apply the decoration to [itemView].
|
||||
*/
|
||||
abstract operator fun invoke(itemView: View): View
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Get the appropriate decorator to set view background / margins depending on the position
|
||||
* of that view in the recent tabs section.
|
||||
*/
|
||||
fun forPosition(position: RecentTabsItemPosition) = when (position) {
|
||||
RecentTabsItemPosition.SINGLE -> SingleTabDecoration
|
||||
RecentTabsItemPosition.TOP -> TopTabDecoration
|
||||
RecentTabsItemPosition.MIDDLE -> MiddleTabDecoration
|
||||
RecentTabsItemPosition.BOTTOM -> BottomTabDecoration
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorator for a view shown in the recent tabs section that will update it to express
|
||||
* that that item is the single one shown in this section.
|
||||
*/
|
||||
object SingleTabDecoration : RecentTabViewDecorator() {
|
||||
override fun invoke(itemView: View): View {
|
||||
val context = itemView.context
|
||||
|
||||
itemView.background =
|
||||
AppCompatResources.getDrawable(context, R.drawable.home_list_row_background)
|
||||
|
||||
return itemView
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorator for a view shown in the recent tabs section that will update it to express
|
||||
* that this is an item shown at the top of the section and there are others below it.
|
||||
*/
|
||||
object TopTabDecoration : RecentTabViewDecorator() {
|
||||
override fun invoke(itemView: View): View {
|
||||
val context = itemView.context
|
||||
|
||||
itemView.background =
|
||||
AppCompatResources.getDrawable(context, R.drawable.rounded_top_corners)
|
||||
|
||||
return itemView
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorator for a view shown in the recent tabs section that will update it to express
|
||||
* that this is an item shown has other recents tabs to be shown on top or below it.
|
||||
*/
|
||||
object MiddleTabDecoration : RecentTabViewDecorator() {
|
||||
override fun invoke(itemView: View): View {
|
||||
val context = itemView.context
|
||||
|
||||
itemView.setBackgroundColor(context.getColorFromAttr(R.attr.above))
|
||||
|
||||
(itemView.layoutParams as? ViewGroup.MarginLayoutParams)?.topMargin =
|
||||
TOP_MARGIN_DP.dpToPx(context.resources.displayMetrics)
|
||||
|
||||
return itemView
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorator for a view shown in the recent tabs section that will update it to express
|
||||
* that this is an item shown at the bottom of the section and there are others above it.
|
||||
*/
|
||||
object BottomTabDecoration : RecentTabViewDecorator() {
|
||||
override fun invoke(itemView: View): View {
|
||||
val context = itemView.context
|
||||
|
||||
itemView.background =
|
||||
AppCompatResources.getDrawable(context, R.drawable.rounded_bottom_corners)
|
||||
|
||||
(itemView.layoutParams as? ViewGroup.MarginLayoutParams)?.topMargin =
|
||||
TOP_MARGIN_DP.dpToPx(context.resources.displayMetrics)
|
||||
|
||||
return itemView
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
/* 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.recenttabs.view
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.slot
|
||||
import io.mockk.unmockkStatic
|
||||
import io.mockk.verify
|
||||
import mozilla.components.support.ktx.android.content.getColorFromAttr
|
||||
import mozilla.components.support.ktx.android.util.dpToPx
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.R
|
||||
|
||||
class RecentTabViewDecoratorTest {
|
||||
@Test
|
||||
fun `WHEN forPosition is called with RecentTabsItemPosition#SINGLE THEN return SingleTabDecoration`() {
|
||||
val result = RecentTabViewDecorator.forPosition(RecentTabsItemPosition.SINGLE)
|
||||
|
||||
assertTrue(result is RecentTabViewDecorator.SingleTabDecoration)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN forPosition is called with RecentTabsItemPosition#TOP THEN return TopTabDecoration`() {
|
||||
val result = RecentTabViewDecorator.forPosition(RecentTabsItemPosition.TOP)
|
||||
|
||||
assertTrue(result is RecentTabViewDecorator.TopTabDecoration)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN forPosition is called with RecentTabsItemPosition#MIDDLE THEN return MiddleTabDecoration`() {
|
||||
val result = RecentTabViewDecorator.forPosition(RecentTabsItemPosition.MIDDLE)
|
||||
|
||||
assertTrue(result is RecentTabViewDecorator.MiddleTabDecoration)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN forPosition is called with RecentTabsItemPosition#BOTTOM THEN return SingleTabDecoration`() {
|
||||
val result = RecentTabViewDecorator.forPosition(RecentTabsItemPosition.BOTTOM)
|
||||
|
||||
assertTrue(result is RecentTabViewDecorator.BottomTabDecoration)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN SingleTabDecoration is invoked for a View THEN set the appropriate background`() {
|
||||
val view: View = mockk(relaxed = true)
|
||||
val drawable: Drawable = mockk()
|
||||
val drawableResCaptor = slot<Int>()
|
||||
|
||||
try {
|
||||
mockkStatic(AppCompatResources::class)
|
||||
every { AppCompatResources.getDrawable(any(), capture(drawableResCaptor)) } returns drawable
|
||||
|
||||
RecentTabViewDecorator.SingleTabDecoration(view)
|
||||
|
||||
verify { view.background = drawable }
|
||||
assertEquals(R.drawable.home_list_row_background, drawableResCaptor.captured)
|
||||
} finally {
|
||||
unmockkStatic(AppCompatResources::class)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN TopTabDecoration is invoked for a View THEN set the appropriate background`() {
|
||||
val view: View = mockk(relaxed = true)
|
||||
val drawable: Drawable = mockk()
|
||||
val drawableResCaptor = slot<Int>()
|
||||
|
||||
try {
|
||||
mockkStatic(AppCompatResources::class)
|
||||
every { AppCompatResources.getDrawable(any(), capture(drawableResCaptor)) } returns drawable
|
||||
|
||||
RecentTabViewDecorator.TopTabDecoration(view)
|
||||
|
||||
verify { view.background = drawable }
|
||||
assertEquals(R.drawable.rounded_top_corners, drawableResCaptor.captured)
|
||||
} finally {
|
||||
unmockkStatic(AppCompatResources::class)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN MiddleTabDecoration is invoked for a View THEN set the appropriate background and layout params`() {
|
||||
val colorAttrCaptor = slot<Int>()
|
||||
val viewLayoutParams: ViewGroup.MarginLayoutParams = mockk(relaxed = true)
|
||||
|
||||
try {
|
||||
mockkStatic("mozilla.components.support.ktx.android.util.DisplayMetricsKt")
|
||||
mockkStatic("mozilla.components.support.ktx.android.content.ContextKt")
|
||||
val view: View = mockk(relaxed = true) {
|
||||
every { layoutParams } returns viewLayoutParams
|
||||
every { context.getColorFromAttr(capture(colorAttrCaptor)) } returns 42
|
||||
every { context.resources.displayMetrics } returns mockk(relaxed = true)
|
||||
}
|
||||
every { any<Int>().dpToPx(any()) } returns 43
|
||||
|
||||
RecentTabViewDecorator.MiddleTabDecoration(view)
|
||||
|
||||
verify { view.setBackgroundColor(42) }
|
||||
assertEquals(R.attr.above, colorAttrCaptor.captured)
|
||||
assertEquals(43, viewLayoutParams.topMargin)
|
||||
} finally {
|
||||
unmockkStatic("mozilla.components.support.ktx.android.content.ContextKt")
|
||||
unmockkStatic("mozilla.components.support.ktx.android.util.DisplayMetricsKt")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN BottomTabDecoration is invoked for a View THEN set the appropriate background and layout params`() {
|
||||
val viewLayoutParams: ViewGroup.MarginLayoutParams = mockk(relaxed = true)
|
||||
val drawable: Drawable = mockk()
|
||||
val drawableResCaptor = slot<Int>()
|
||||
|
||||
try {
|
||||
mockkStatic(AppCompatResources::class)
|
||||
every { AppCompatResources.getDrawable(any(), capture(drawableResCaptor)) } returns drawable
|
||||
mockkStatic("mozilla.components.support.ktx.android.util.DisplayMetricsKt")
|
||||
val view: View = mockk(relaxed = true) {
|
||||
every { layoutParams } returns viewLayoutParams
|
||||
every { context.resources.displayMetrics } returns mockk(relaxed = true)
|
||||
}
|
||||
every { any<Int>().dpToPx(any()) } returns 43
|
||||
|
||||
RecentTabViewDecorator.BottomTabDecoration(view)
|
||||
|
||||
verify { view.background = drawable }
|
||||
assertEquals(R.drawable.rounded_bottom_corners, drawableResCaptor.captured)
|
||||
assertEquals(43, viewLayoutParams.topMargin)
|
||||
} finally {
|
||||
unmockkStatic("mozilla.components.support.ktx.android.util.DisplayMetricsKt")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/* 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
|
||||
|
||||
import io.mockk.mockk
|
||||
import mozilla.components.browser.state.state.TabSessionState
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertSame
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.home.recenttabs.view.RecentTabsItemPosition
|
||||
|
||||
class SessionControlViewTest {
|
||||
@Test
|
||||
fun `GIVEN two recent tabs WHEN showRecentTabs is called THEN add the header, and two recent items to be shown`() {
|
||||
val recentTab: TabSessionState = mockk()
|
||||
val mediaTab: TabSessionState = mockk()
|
||||
val items = mutableListOf<AdapterItem>()
|
||||
|
||||
showRecentTabs(listOf(recentTab, mediaTab), items)
|
||||
|
||||
assertEquals(3, items.size)
|
||||
assertTrue(items[0] is AdapterItem.RecentTabsHeader)
|
||||
assertEquals(recentTab, (items[1] as AdapterItem.RecentTabItem).tab)
|
||||
assertEquals(mediaTab, (items[2] as AdapterItem.RecentTabItem).tab)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN one recent tab WHEN showRecentTabs is called THEN add the header and the recent tab to items shown`() {
|
||||
val recentTab: TabSessionState = mockk()
|
||||
val items = mutableListOf<AdapterItem>()
|
||||
|
||||
showRecentTabs(listOf(recentTab), items)
|
||||
|
||||
assertEquals(2, items.size)
|
||||
assertTrue(items[0] is AdapterItem.RecentTabsHeader)
|
||||
assertEquals(recentTab, (items[1] as AdapterItem.RecentTabItem).tab)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN only one recent tab and no media tab WHEN showRecentTabs is called THEN add the recent item as a single one to be shown`() {
|
||||
val recentTab: TabSessionState = mockk()
|
||||
val items = mutableListOf<AdapterItem>()
|
||||
|
||||
showRecentTabs(listOf(recentTab), items)
|
||||
|
||||
assertEquals(recentTab, (items[1] as AdapterItem.RecentTabItem).tab)
|
||||
assertSame(RecentTabsItemPosition.SINGLE, (items[1] as AdapterItem.RecentTabItem).position)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN two recent tabs WHEN showRecentTabs is called THEN add one item as top and one as bottom to be shown`() {
|
||||
val recentTab: TabSessionState = mockk()
|
||||
val mediaTab: TabSessionState = mockk()
|
||||
val items = mutableListOf<AdapterItem>()
|
||||
|
||||
showRecentTabs(listOf(recentTab, mediaTab), items)
|
||||
|
||||
assertEquals(recentTab, (items[1] as AdapterItem.RecentTabItem).tab)
|
||||
assertSame(RecentTabsItemPosition.TOP, (items[1] as AdapterItem.RecentTabItem).position)
|
||||
assertEquals(mediaTab, (items[2] as AdapterItem.RecentTabItem).tab)
|
||||
assertSame(RecentTabsItemPosition.BOTTOM, (items[2] as AdapterItem.RecentTabItem).position)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN three recent tabs WHEN showRecentTabs is called THEN add one recent item as top, one as middle and one as bottom to be shown`() {
|
||||
val recentTab1: TabSessionState = mockk()
|
||||
val recentTab2: TabSessionState = mockk()
|
||||
val mediaTab: TabSessionState = mockk()
|
||||
val items = mutableListOf<AdapterItem>()
|
||||
|
||||
showRecentTabs(listOf(recentTab1, recentTab2, mediaTab), items)
|
||||
|
||||
assertEquals(recentTab1, (items[1] as AdapterItem.RecentTabItem).tab)
|
||||
assertSame(RecentTabsItemPosition.TOP, (items[1] as AdapterItem.RecentTabItem).position)
|
||||
assertEquals(recentTab2, (items[2] as AdapterItem.RecentTabItem).tab)
|
||||
assertSame(RecentTabsItemPosition.MIDDLE, (items[2] as AdapterItem.RecentTabItem).position)
|
||||
assertEquals(mediaTab, (items[3] as AdapterItem.RecentTabItem).tab)
|
||||
assertSame(RecentTabsItemPosition.BOTTOM, (items[3] as AdapterItem.RecentTabItem).position)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue