diff --git a/android-components/components/browser/menu/src/main/java/mozilla/components/browser/menu/BrowserMenu.kt b/android-components/components/browser/menu/src/main/java/mozilla/components/browser/menu/BrowserMenu.kt index 63da60475847..01e2be228cbd 100644 --- a/android-components/components/browser/menu/src/main/java/mozilla/components/browser/menu/BrowserMenu.kt +++ b/android-components/components/browser/menu/src/main/java/mozilla/components/browser/menu/BrowserMenu.kt @@ -4,7 +4,6 @@ package mozilla.components.browser.menu -import android.annotation.SuppressLint import android.graphics.Color import android.graphics.Rect import android.graphics.drawable.ColorDrawable @@ -16,12 +15,15 @@ import android.view.WindowManager import android.view.accessibility.AccessibilityNodeInfo import android.widget.PopupWindow import androidx.annotation.VisibleForTesting +import androidx.cardview.widget.CardView import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.widget.PopupWindowCompat import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import mozilla.components.browser.menu.BrowserMenu.Orientation.DOWN import mozilla.components.browser.menu.BrowserMenu.Orientation.UP +import mozilla.components.browser.menu.view.DynamicWidthRecyclerView +import mozilla.components.concept.menu.MenuStyle import mozilla.components.support.ktx.android.view.isRTL import mozilla.components.support.ktx.android.view.onNextGlobalLayout @@ -39,13 +41,15 @@ open class BrowserMenu internal constructor( /** * @param anchor the view on which to pin the popup window. * @param orientation the preferred orientation to show the popup window. + * @param style Custom styling for this menu. * @param endOfMenuAlwaysVisible when is set to true makes sure the bottom of the menu is always visible otherwise, * the top of the menu is always visible. */ - @SuppressLint("InflateParams") + @Suppress("InflateParams", "ComplexMethod") open fun show( anchor: View, orientation: Orientation = DOWN, + style: MenuStyle? = null, endOfMenuAlwaysVisible: Boolean = false, onDismiss: () -> Unit = {} ): PopupWindow { @@ -53,21 +57,27 @@ open class BrowserMenu internal constructor( adapter.menu = this - menuList = view.findViewById(R.id.mozac_browser_menu_recyclerView).apply { + menuList = view.findViewById(R.id.mozac_browser_menu_recyclerView).apply { layoutManager = LinearLayoutManager(anchor.context, RecyclerView.VERTICAL, false).also { setEndOfMenuAlwaysVisibleCompact(endOfMenuAlwaysVisible, it) } adapter = this@BrowserMenu.adapter + minWidth = style?.minWidth ?: resources.getDimensionPixelSize(R.dimen.mozac_browser_menu_width_min) + maxWidth = style?.maxWidth ?: resources.getDimensionPixelSize(R.dimen.mozac_browser_menu_width_max) } - menuList?.setAccessibilityDelegate(object : View.AccessibilityDelegate() { + view.findViewById(R.id.mozac_browser_menu_menuView).apply { + style?.backgroundColor?.let { setCardBackgroundColor(it) } + } + + menuList?.accessibilityDelegate = object : View.AccessibilityDelegate() { override fun onInitializeAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfo?) { super.onInitializeAccessibilityNodeInfo(host, info) info?.collectionInfo = AccessibilityNodeInfo.CollectionInfo.obtain( adapter.interactiveCount, 0, false ) } - }) + } return PopupWindow( view, diff --git a/android-components/components/browser/menu/src/main/java/mozilla/components/browser/menu/WebExtensionBrowserMenu.kt b/android-components/components/browser/menu/src/main/java/mozilla/components/browser/menu/WebExtensionBrowserMenu.kt index 3c7bb382f90d..77b37502d609 100644 --- a/android-components/components/browser/menu/src/main/java/mozilla/components/browser/menu/WebExtensionBrowserMenu.kt +++ b/android-components/components/browser/menu/src/main/java/mozilla/components/browser/menu/WebExtensionBrowserMenu.kt @@ -17,6 +17,7 @@ import mozilla.components.browser.state.state.SessionState import mozilla.components.browser.state.state.WebExtensionState import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.webextension.Action +import mozilla.components.concept.menu.MenuStyle import mozilla.components.lib.state.ext.flowScoped import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged @@ -33,6 +34,7 @@ class WebExtensionBrowserMenu internal constructor( override fun show( anchor: View, orientation: Orientation, + style: MenuStyle?, endOfMenuAlwaysVisible: Boolean, onDismiss: () -> Unit ): PopupWindow { @@ -47,6 +49,7 @@ class WebExtensionBrowserMenu internal constructor( return super.show( anchor, orientation, + style, endOfMenuAlwaysVisible, onDismiss ).apply { diff --git a/android-components/components/browser/menu/src/main/java/mozilla/components/browser/menu/view/DynamicWidthRecyclerView.kt b/android-components/components/browser/menu/src/main/java/mozilla/components/browser/menu/view/DynamicWidthRecyclerView.kt index 65015c0eb123..cfafa44e7191 100644 --- a/android-components/components/browser/menu/src/main/java/mozilla/components/browser/menu/view/DynamicWidthRecyclerView.kt +++ b/android-components/components/browser/menu/src/main/java/mozilla/components/browser/menu/view/DynamicWidthRecyclerView.kt @@ -7,28 +7,21 @@ package mozilla.components.browser.menu.view import android.annotation.SuppressLint import android.content.Context import android.util.AttributeSet +import androidx.annotation.Px import androidx.annotation.VisibleForTesting import androidx.recyclerview.widget.RecyclerView import mozilla.components.browser.menu.R -import mozilla.components.support.ktx.android.util.dpToPx /** - * [RecylerView] with automatically set width between widthMin / widthMax xml attributes. + * [RecyclerView] with automatically set width between widthMin / widthMax xml attributes. */ class DynamicWidthRecyclerView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : RecyclerView(context, attrs) { - @VisibleForTesting internal var minWidth: Int = -1 - @VisibleForTesting internal var maxWidth: Int = -1 - init { - context.obtainStyledAttributes(attrs, R.styleable.DynamicWidthRecyclerView).apply { - minWidth = getDimension(R.styleable.DynamicWidthRecyclerView_minWidth, minWidth.toFloat()).toInt() - maxWidth = getDimension(R.styleable.DynamicWidthRecyclerView_maxWidth, maxWidth.toFloat()).toInt() - recycle() - } - } + @Px var minWidth: Int = -1 + @Px var maxWidth: Int = -1 @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) override fun onMeasure(widthSpec: Int, heightSpec: Int) { @@ -44,49 +37,38 @@ class DynamicWidthRecyclerView @JvmOverloads constructor( } } - @VisibleForTesting() + @VisibleForTesting internal fun setReconciledDimensions( desiredWidth: Int, desiredHeight: Int ) { + val minimumTapArea = resources.getDimensionPixelSize(R.dimen.mozac_browser_menu_material_min_tap_area) + val minimumItemWidth = resources.getDimensionPixelSize(R.dimen.mozac_browser_menu_material_min_item_width) + val reconciledWidth = desiredWidth .coerceAtLeast(minWidth) // Follow material guidelines where the minimum width is 112dp. - .coerceAtLeast(getMaterialMinimumItemWidthInPx()) + .coerceAtLeast(minimumItemWidth) .coerceAtMost(maxWidth) // Leave at least 48dp as a tappable “exit area” available whenever the menu is open. - .coerceAtMost(getScreenWidth() - getMaterialMinimumTapAreaInPx()) + .coerceAtMost(getScreenWidth() - minimumTapArea) callSetMeasuredDimension(reconciledWidth, desiredHeight) } - @VisibleForTesting() + @VisibleForTesting internal fun getScreenWidth(): Int = resources.displayMetrics.widthPixels - @VisibleForTesting() - internal fun getMaterialMinimumTapAreaInPx() = - MATERIAL_MINIMUM_TAP_AREA_DP.dpToPx(resources.displayMetrics) - - @VisibleForTesting() - internal fun getMaterialMinimumItemWidthInPx() = - MATERIAL_MINIMUM_ITEM_WIDTH_DP.dpToPx(resources.displayMetrics) - @SuppressLint("WrongCall") - @VisibleForTesting() + @VisibleForTesting // Used for testing protected super.onMeasure(..) calls will be executed. internal fun callParentOnMeasure(widthSpec: Int, heightSpec: Int) { super.onMeasure(widthSpec, heightSpec) } - @VisibleForTesting() + @VisibleForTesting // Used for testing final protected setMeasuredDimension(..) calls were executed internal fun callSetMeasuredDimension(width: Int, height: Int) { setMeasuredDimension(width, height) } - - @VisibleForTesting() - internal companion object { - const val MATERIAL_MINIMUM_TAP_AREA_DP = 48 - const val MATERIAL_MINIMUM_ITEM_WIDTH_DP = 112 - } } diff --git a/android-components/components/browser/menu/src/main/res/layout/mozac_browser_menu.xml b/android-components/components/browser/menu/src/main/res/layout/mozac_browser_menu.xml index cbbf1bbf018b..83e075a2e0cb 100644 --- a/android-components/components/browser/menu/src/main/res/layout/mozac_browser_menu.xml +++ b/android-components/components/browser/menu/src/main/res/layout/mozac_browser_menu.xml @@ -6,6 +6,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" style="@style/Mozac.Browser.Menu" + android:id="@+id/mozac_browser_menu_menuView" android:layout_width="wrap_content" android:layout_height="wrap_content" app:cardCornerRadius="@dimen/mozac_browser_menu_corner_radius" @@ -19,8 +20,6 @@ android:overScrollMode="never" android:layout_width="@dimen/mozac_browser_menu_width" android:layout_height="wrap_content" - app:maxWidth="@dimen/mozac_browser_menu_width_max" - app:minWidth="@dimen/mozac_browser_menu_width_min" tools:listitem="@layout/mozac_browser_menu_item_simple" /> diff --git a/android-components/components/browser/menu/src/main/res/values/attrs.xml b/android-components/components/browser/menu/src/main/res/values/attrs.xml deleted file mode 100644 index f996ea45634d..000000000000 --- a/android-components/components/browser/menu/src/main/res/values/attrs.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - diff --git a/android-components/components/browser/menu/src/main/res/values/dimens.xml b/android-components/components/browser/menu/src/main/res/values/dimens.xml index b38649a68071..596dcc7f981b 100644 --- a/android-components/components/browser/menu/src/main/res/values/dimens.xml +++ b/android-components/components/browser/menu/src/main/res/values/dimens.xml @@ -17,6 +17,11 @@ 16dp + + 48dp + 112dp + + 1dp diff --git a/android-components/components/browser/menu/src/test/java/mozilla/components/browser/menu/BrowserMenuTest.kt b/android-components/components/browser/menu/src/test/java/mozilla/components/browser/menu/BrowserMenuTest.kt index 1f5af9b40267..8bc0ff62cd67 100644 --- a/android-components/components/browser/menu/src/test/java/mozilla/components/browser/menu/BrowserMenuTest.kt +++ b/android-components/components/browser/menu/src/test/java/mozilla/components/browser/menu/BrowserMenuTest.kt @@ -4,16 +4,21 @@ package mozilla.components.browser.menu +import android.content.res.ColorStateList +import android.graphics.Color import android.os.Build import android.view.Gravity import android.view.View import android.widget.Button import android.widget.PopupWindow +import androidx.cardview.widget.CardView import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.test.ext.junit.runners.AndroidJUnit4 import mozilla.components.browser.menu.item.SimpleBrowserMenuItem +import mozilla.components.browser.menu.view.DynamicWidthRecyclerView +import mozilla.components.concept.menu.MenuStyle import mozilla.components.support.test.any import mozilla.components.support.test.mock import mozilla.components.support.test.robolectric.testContext @@ -70,6 +75,33 @@ class BrowserMenuTest { assertTrue(menu.isShown) } + @Test + fun `show assigns width and background color`() { + val items = listOf(SimpleBrowserMenuItem("Hello") {}) + + val adapter = BrowserMenuAdapter(testContext, items) + + val menu = BrowserMenu(adapter) + + val anchor = Button(testContext) + val popup = menu.show(anchor, style = MenuStyle( + backgroundColor = Color.RED, + minWidth = 20, + maxWidth = 500 + )) + + assertNotNull(popup) + assertEquals(anchor, menu.currAnchor) + assertTrue(menu.isShown) + + val cardView = popup.contentView.findViewById(R.id.mozac_browser_menu_menuView) + val recyclerView = popup.contentView.findViewById(R.id.mozac_browser_menu_recyclerView) + + assertEquals(ColorStateList.valueOf(Color.RED), cardView.cardBackgroundColor) + assertEquals(20, recyclerView.minWidth) + assertEquals(500, recyclerView.maxWidth) + } + @Test fun `dismiss sets isShown to false`() { val items = listOf( diff --git a/android-components/components/browser/menu/src/test/java/mozilla/components/browser/menu/view/DynamicWidthRecyclerViewTest.kt b/android-components/components/browser/menu/src/test/java/mozilla/components/browser/menu/view/DynamicWidthRecyclerViewTest.kt index d21a2e4f4f07..aa9c07097966 100644 --- a/android-components/components/browser/menu/src/test/java/mozilla/components/browser/menu/view/DynamicWidthRecyclerViewTest.kt +++ b/android-components/components/browser/menu/src/test/java/mozilla/components/browser/menu/view/DynamicWidthRecyclerViewTest.kt @@ -4,10 +4,8 @@ package mozilla.components.browser.menu.view -import android.util.AttributeSet import androidx.test.ext.junit.runners.AndroidJUnit4 import mozilla.components.browser.menu.R -import mozilla.components.support.ktx.android.util.dpToPx import mozilla.components.support.test.robolectric.testContext import org.junit.Assert.assertEquals import org.junit.Test @@ -24,7 +22,9 @@ class DynamicWidthRecyclerViewTest { @Test fun `minWidth and maxWidth should be initialized from xml attributes`() { - val dynamicRecyclerView = DynamicWidthRecyclerView(testContext, getCustomAttributesSet(100, 123, 456)) + val dynamicRecyclerView = buildRecyclerView(100) + dynamicRecyclerView.minWidth = 123 + dynamicRecyclerView.maxWidth = 456 assertEquals(123, dynamicRecyclerView.minWidth) assertEquals(456, dynamicRecyclerView.maxWidth) @@ -32,7 +32,7 @@ class DynamicWidthRecyclerViewTest { @Test fun `If minWidth and maxWidth are not provided view should use layout_width`() { - val dynamicRecyclerView = spy(DynamicWidthRecyclerView(testContext, getCustomAttributesSet(100))) + val dynamicRecyclerView = buildRecyclerView(100) dynamicRecyclerView.measure(100, 100) @@ -43,10 +43,8 @@ class DynamicWidthRecyclerViewTest { @Test fun `If only minWidth is provided view should use layout_width`() { - val dynamicRecyclerView = spy(DynamicWidthRecyclerView( - testContext, - getCustomAttributesSet(layoutWidth = 100, minWidth = 50) - )) + val dynamicRecyclerView = buildRecyclerView(100) + dynamicRecyclerView.minWidth = 50 dynamicRecyclerView.measure(100, 100) @@ -57,10 +55,8 @@ class DynamicWidthRecyclerViewTest { @Test fun `If only maxWidth is provided view should use layout_width`() { - val dynamicRecyclerView = spy(DynamicWidthRecyclerView( - testContext, - getCustomAttributesSet(layoutWidth = 100, maxWidth = 300) - )) + val dynamicRecyclerView = buildRecyclerView(100) + dynamicRecyclerView.maxWidth = 300 dynamicRecyclerView.measure(100, 100) @@ -71,8 +67,9 @@ class DynamicWidthRecyclerViewTest { @Test fun `Should only allow for dynamic width if minWidth has a positive value`() { - val dynamicRecyclerView = spy( - DynamicWidthRecyclerView(testContext, getCustomAttributesSet(100, -1, 100))) + val dynamicRecyclerView = buildRecyclerView(100) + dynamicRecyclerView.minWidth = -1 + dynamicRecyclerView.maxWidth = 100 dynamicRecyclerView.measure(1, 2) @@ -83,8 +80,9 @@ class DynamicWidthRecyclerViewTest { @Test fun `Should only allow for dynamic width if minWidth is smaller than maxWidth`() { - val dynamicRecyclerView = spy( - DynamicWidthRecyclerView(testContext, getCustomAttributesSet(100, 100, 100))) + val dynamicRecyclerView = buildRecyclerView(100) + dynamicRecyclerView.minWidth = 100 + dynamicRecyclerView.maxWidth = 100 dynamicRecyclerView.measure(1, 2) @@ -95,8 +93,9 @@ class DynamicWidthRecyclerViewTest { @Test fun `To allow for dynamic width children can expand entirely between minWidth and maxWidth`() { - val dynamicRecyclerView = spy( - DynamicWidthRecyclerView(testContext, getCustomAttributesSet(100, 50, 100))) + val dynamicRecyclerView = buildRecyclerView(100) + dynamicRecyclerView.minWidth = 50 + dynamicRecyclerView.maxWidth = 100 dynamicRecyclerView.measure(10, 10) @@ -114,33 +113,14 @@ class DynamicWidthRecyclerViewTest { assertEquals(333, dynamicRecyclerView.getScreenWidth()) } - @Test - fun `getMaterialMinimumTapAreaInPx should return MATERIAL_MINIMUM_TAP_AREA_DP to pixels`() { - val dynamicRecyclerView = DynamicWidthRecyclerView(testContext, null) - - val minimumTapAreaInPx = DynamicWidthRecyclerView.MATERIAL_MINIMUM_TAP_AREA_DP - .dpToPx(testContext.resources.displayMetrics) - - assertEquals(minimumTapAreaInPx, dynamicRecyclerView.getMaterialMinimumTapAreaInPx()) - } - - @Test - fun `getMaterialMinimumItemWidthInPx should return MATERIAL_MINIMUM_ITEM_WIDTH_DP to pixels`() { - val dynamicRecyclerView = DynamicWidthRecyclerView(testContext, null) - - val minimumItemWidthInPx = DynamicWidthRecyclerView.MATERIAL_MINIMUM_ITEM_WIDTH_DP - .dpToPx(testContext.resources.displayMetrics) - - assertEquals(minimumItemWidthInPx, dynamicRecyclerView.getMaterialMinimumItemWidthInPx()) - } - @Test fun `setReconciledDimensions() must set material minimum width even if childs are smaller`() { val childrenWidth = 20 - val materialMinWidth = DynamicWidthRecyclerView.MATERIAL_MINIMUM_ITEM_WIDTH_DP + val materialMinWidth = testContext.resources.getDimensionPixelSize(R.dimen.mozac_browser_menu_material_min_item_width) // Layout width is *2 to allow bigger sizes. minWidth is /2 to verify the material min width is used. - val dynamicRecyclerView = spy(DynamicWidthRecyclerView( - testContext, getCustomAttributesSet(materialMinWidth * 2, materialMinWidth / 2, 500))) + val dynamicRecyclerView = buildRecyclerView(materialMinWidth * 2) + dynamicRecyclerView.minWidth = materialMinWidth / 2 + dynamicRecyclerView.maxWidth = 500 dynamicRecyclerView.setReconciledDimensions(childrenWidth, 500) @@ -151,9 +131,10 @@ class DynamicWidthRecyclerViewTest { fun `setReconciledDimensions() must set minWidth even if children width is smaller`() { val childrenWidth = 20 // minWidth set in xml. Ensure it is bigger than the default. - val minWidth = DynamicWidthRecyclerView.MATERIAL_MINIMUM_ITEM_WIDTH_DP + 10 - val dynamicRecyclerView = spy(DynamicWidthRecyclerView( - testContext, getCustomAttributesSet(minWidth * 2, minWidth, 500))) + val minWidth = testContext.resources.getDimensionPixelSize(R.dimen.mozac_browser_menu_material_min_item_width) + 10 + val dynamicRecyclerView = buildRecyclerView(minWidth * 2) + dynamicRecyclerView.minWidth = minWidth + dynamicRecyclerView.maxWidth = 500 dynamicRecyclerView.setReconciledDimensions(childrenWidth, 500) @@ -162,11 +143,12 @@ class DynamicWidthRecyclerViewTest { @Test fun `setReconciledDimensions() will set children width if it is bigger than minWidth and smaller than maxWidth`() { - val materialMinWidth = DynamicWidthRecyclerView.MATERIAL_MINIMUM_ITEM_WIDTH_DP + val materialMinWidth = testContext.resources.getDimensionPixelSize(R.dimen.mozac_browser_menu_material_min_item_width) val childrenWidth = materialMinWidth + 10 // Layout width is *2 to allow bigger sizes. minWidth is /2 to verify the material min width is used. - val dynamicRecyclerView = spy(DynamicWidthRecyclerView( - testContext, getCustomAttributesSet(materialMinWidth * 2, materialMinWidth, 500))) + val dynamicRecyclerView = buildRecyclerView(materialMinWidth * 2) + dynamicRecyclerView.minWidth = materialMinWidth + dynamicRecyclerView.maxWidth = 500 dynamicRecyclerView.setReconciledDimensions(childrenWidth, 500) @@ -177,11 +159,12 @@ class DynamicWidthRecyclerViewTest { @Config(qualifiers = "w500dp") @Suppress("UnnecessaryVariable") fun `setReconciledDimensions() must set maxWidth when children width is bigger`() { - val materialMaxWidth = 500 - DynamicWidthRecyclerView.MATERIAL_MINIMUM_TAP_AREA_DP + val materialMaxWidth = 500 - testContext.resources.getDimensionPixelSize(R.dimen.mozac_browser_menu_material_min_tap_area) val childrenWidth = materialMaxWidth val maxWidth = materialMaxWidth - 10 - val dynamicRecyclerView = spy(DynamicWidthRecyclerView( - testContext, getCustomAttributesSet(1000, 100, maxWidth))) + val dynamicRecyclerView = buildRecyclerView(1000) + dynamicRecyclerView.minWidth = 100 + dynamicRecyclerView.maxWidth = maxWidth dynamicRecyclerView.setReconciledDimensions(childrenWidth, 100) @@ -191,25 +174,24 @@ class DynamicWidthRecyclerViewTest { @Test @Config(qualifiers = "w500dp") fun `setReconciledDimensions must set material maximum width when children width is bigger`() { - val materialMaxWidth = 500 - DynamicWidthRecyclerView.MATERIAL_MINIMUM_TAP_AREA_DP + val materialMaxWidth = 500 - testContext.resources.getDimensionPixelSize(R.dimen.mozac_browser_menu_material_min_tap_area) val maxWidth = 500 val childrenWidth = maxWidth + 10 - val dynamicRecyclerView = spy(DynamicWidthRecyclerView( - testContext, getCustomAttributesSet(1000, 100, maxWidth))) + val dynamicRecyclerView = buildRecyclerView(1000) + dynamicRecyclerView.minWidth = 100 + dynamicRecyclerView.maxWidth = maxWidth dynamicRecyclerView.setReconciledDimensions(childrenWidth, 100) verify(dynamicRecyclerView).callSetMeasuredDimension(materialMaxWidth, 100) } - private fun getCustomAttributesSet(layoutWidth: Int, minWidth: Int? = null, maxWidth: Int? = null): AttributeSet { - return Robolectric.buildAttributeSet().apply { + private fun buildRecyclerView(layoutWidth: Int): DynamicWidthRecyclerView { + val customAttributeSet = Robolectric.buildAttributeSet().apply { // android.R.attr.layout_width needs to always be set addAttribute(android.R.attr.layout_width, "${layoutWidth}dp") - - // R.attr.minWidth and R.attr.maxWidth are optional - minWidth?.let { addAttribute(R.attr.minWidth, "${minWidth}dp") } - maxWidth?.let { addAttribute(R.attr.maxWidth, "${maxWidth}dp") } }.build() + + return spy(DynamicWidthRecyclerView(testContext, customAttributeSet)) } } diff --git a/android-components/components/browser/menu/src/test/java/mozilla/components/browser/menu/view/MenuButtonTest.kt b/android-components/components/browser/menu/src/test/java/mozilla/components/browser/menu/view/MenuButtonTest.kt index f0d5389a3a14..9648c4635155 100644 --- a/android-components/components/browser/menu/src/test/java/mozilla/components/browser/menu/view/MenuButtonTest.kt +++ b/android-components/components/browser/menu/src/test/java/mozilla/components/browser/menu/view/MenuButtonTest.kt @@ -75,7 +75,7 @@ class MenuButtonTest { menuButton.menuBuilder = menuBuilder menuButton.performClick() - verify(menu).show(eq(menuButton), any(), anyBoolean(), any()) + verify(menu).show(eq(menuButton), any(), any(), anyBoolean(), any()) menuButton.menuBuilder = mock() verify(menu).dismiss() @@ -90,7 +90,7 @@ class MenuButtonTest { verify(menuController).show(menuButton) verify(menuBuilder, never()).build(testContext) - verify(menu, never()).show(any(), any(), anyBoolean(), any()) + verify(menu, never()).show(any(), any(), any(), anyBoolean(), any()) } @Test @@ -100,7 +100,7 @@ class MenuButtonTest { menuButton.performClick() menuButton.performClick() - verify(menu, times(1)).show(eq(menuButton), any(), anyBoolean(), any()) + verify(menu, times(1)).show(eq(menuButton), any(), any(), anyBoolean(), any()) verify(menu, times(1)).dismiss() } @@ -123,7 +123,7 @@ class MenuButtonTest { menuButton.menuBuilder = menuBuilder menuButton.performClick() - verify(menu).show(eq(menuButton), any(), anyBoolean(), any()) + verify(menu).show(eq(menuButton), any(), any(), anyBoolean(), any()) menuButton.invalidateBrowserMenu() verify(menu).invalidate() diff --git a/android-components/components/browser/menu2/src/main/java/mozilla/components/browser/menu2/BrowserMenuController.kt b/android-components/components/browser/menu2/src/main/java/mozilla/components/browser/menu2/BrowserMenuController.kt index bed19396a161..be249708ef4f 100644 --- a/android-components/components/browser/menu2/src/main/java/mozilla/components/browser/menu2/BrowserMenuController.kt +++ b/android-components/components/browser/menu2/src/main/java/mozilla/components/browser/menu2/BrowserMenuController.kt @@ -11,6 +11,7 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout import mozilla.components.browser.menu2.ext.displayPopup import mozilla.components.browser.menu2.view.MenuView import mozilla.components.concept.menu.MenuController +import mozilla.components.concept.menu.MenuStyle import mozilla.components.concept.menu.Orientation import mozilla.components.concept.menu.Side import mozilla.components.concept.menu.candidate.MenuCandidate @@ -22,9 +23,11 @@ import mozilla.components.support.base.observer.ObserverRegistry /** * Controls a popup menu composed of MenuCandidate objects. * @param visibleSide Sets the menu to open with either the start or end visible. + * @param style Custom styling for this menu controller. */ class BrowserMenuController( - private val visibleSide: Side = Side.START + private val visibleSide: Side = Side.START, + private val style: MenuStyle? = null ) : MenuController, Observable by ObserverRegistry() { private var currentPopupInfo: PopupMenuInfo? = null @@ -48,6 +51,7 @@ class BrowserMenuController( // Show nested list if present, or the standard menu candidates list. submitList(menuCandidates) setVisibleSide(visibleSide) + style?.let { setStyle(it) } } return MenuPopupWindow(view).apply { diff --git a/android-components/components/browser/menu2/src/main/java/mozilla/components/browser/menu2/view/MenuView.kt b/android-components/components/browser/menu2/src/main/java/mozilla/components/browser/menu2/view/MenuView.kt index 769cb251f43b..2100b6bedae8 100644 --- a/android-components/components/browser/menu2/src/main/java/mozilla/components/browser/menu2/view/MenuView.kt +++ b/android-components/components/browser/menu2/src/main/java/mozilla/components/browser/menu2/view/MenuView.kt @@ -12,10 +12,12 @@ import android.view.LayoutInflater import android.view.View import android.widget.FrameLayout import androidx.annotation.VisibleForTesting +import androidx.cardview.widget.CardView import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import mozilla.components.browser.menu2.R import mozilla.components.browser.menu2.adapter.MenuCandidateListAdapter +import mozilla.components.concept.menu.MenuStyle import mozilla.components.concept.menu.Side import mozilla.components.concept.menu.candidate.MenuCandidate import mozilla.components.concept.menu.candidate.NestedMenuCandidate @@ -36,6 +38,7 @@ class MenuView @JvmOverloads constructor( dismiss = { onDismiss() }, reopenMenu = { onReopenMenu(it) } ) + private val cardView: CardView private val recyclerView: RecyclerView /** @@ -51,6 +54,7 @@ class MenuView @JvmOverloads constructor( init { View.inflate(context, R.layout.mozac_browser_menu2_view, this) + cardView = findViewById(R.id.mozac_browser_menu_cardView) recyclerView = findViewById(R.id.mozac_browser_menu_recyclerView) recyclerView.layoutManager = layoutManager recyclerView.adapter = menuAdapter @@ -75,6 +79,13 @@ class MenuView @JvmOverloads constructor( } } + /** + * Sets the background color for the menu view. + */ + fun setStyle(style: MenuStyle) { + style.backgroundColor?.let { cardView.setCardBackgroundColor(it) } + } + @VisibleForTesting internal fun scrollOnceToTheBottom(recyclerView: RecyclerView) { recyclerView.onNextGlobalLayout { diff --git a/android-components/components/browser/menu2/src/main/res/layout/mozac_browser_menu2_view.xml b/android-components/components/browser/menu2/src/main/res/layout/mozac_browser_menu2_view.xml index f1b50bf36a84..1e05515a6f51 100644 --- a/android-components/components/browser/menu2/src/main/res/layout/mozac_browser_menu2_view.xml +++ b/android-components/components/browser/menu2/src/main/res/layout/mozac_browser_menu2_view.xml @@ -11,6 +11,7 @@