diff --git a/components/browser/menu/src/main/java/mozilla/components/browser/menu/BrowserMenu.kt b/components/browser/menu/src/main/java/mozilla/components/browser/menu/BrowserMenu.kt index 7f6f1c97bca..64a2a6ea201 100644 --- a/components/browser/menu/src/main/java/mozilla/components/browser/menu/BrowserMenu.kt +++ b/components/browser/menu/src/main/java/mozilla/components/browser/menu/BrowserMenu.kt @@ -32,14 +32,22 @@ class BrowserMenu internal constructor( private var currentPopup: PopupWindow? = null private var menuList: RecyclerView? = null + /** + * @param anchor the view on which to pin the popup window. + * @param orientation the preferred orientation to show the popup window. + * @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") - fun show(anchor: View, orientation: Orientation = DOWN): PopupWindow { + fun show(anchor: View, orientation: Orientation = DOWN, endOfMenuAlwaysVisible: Boolean = false): PopupWindow { val view = LayoutInflater.from(anchor.context).inflate(R.layout.mozac_browser_menu, null) adapter.menu = this menuList = view.findViewById(R.id.mozac_browser_menu_recyclerView).apply { - layoutManager = LinearLayoutManager(anchor.context, RecyclerView.VERTICAL, false) + layoutManager = LinearLayoutManager(anchor.context, RecyclerView.VERTICAL, false).also { + it.stackFromEnd = endOfMenuAlwaysVisible + } adapter = this@BrowserMenu.adapter } diff --git a/components/browser/menu/src/main/java/mozilla/components/browser/menu/BrowserMenuBuilder.kt b/components/browser/menu/src/main/java/mozilla/components/browser/menu/BrowserMenuBuilder.kt index b1f67a209c7..45e6f905efa 100644 --- a/components/browser/menu/src/main/java/mozilla/components/browser/menu/BrowserMenuBuilder.kt +++ b/components/browser/menu/src/main/java/mozilla/components/browser/menu/BrowserMenuBuilder.kt @@ -11,10 +11,13 @@ import android.content.Context * * @param items List of BrowserMenuItem objects to compose the menu from. * @param extras Map of extra values that are added to emitted facts + * @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. */ class BrowserMenuBuilder( val items: List, - val extras: Map = emptyMap() + val extras: Map = emptyMap(), + val endOfMenuAlwaysVisible: Boolean = false ) { fun build(context: Context): BrowserMenu { val adapter = BrowserMenuAdapter(context, items) diff --git a/components/browser/menu/src/test/java/mozilla/components/browser/menu/BrowserMenuTest.kt b/components/browser/menu/src/test/java/mozilla/components/browser/menu/BrowserMenuTest.kt index 52259ec42c9..5505111bf35 100644 --- a/components/browser/menu/src/test/java/mozilla/components/browser/menu/BrowserMenuTest.kt +++ b/components/browser/menu/src/test/java/mozilla/components/browser/menu/BrowserMenuTest.kt @@ -9,6 +9,7 @@ import android.view.View import android.widget.Button import android.widget.PopupWindow 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 @@ -69,6 +70,25 @@ class BrowserMenuTest { assertEquals(2, recyclerAdapter.itemCount) } + @Test + fun `endOfMenuAlwaysVisible will be forwarded to recyclerview layoutManager`() { + val items = listOf( + SimpleBrowserMenuItem("Hello") {}, + SimpleBrowserMenuItem("World") {}) + + val adapter = spy(BrowserMenuAdapter(testContext, items)) + val menu = BrowserMenu(adapter) + + val anchor = Button(testContext) + val popup = menu.show(anchor, endOfMenuAlwaysVisible = true) + + val recyclerView: RecyclerView = popup.contentView.findViewById(R.id.mozac_browser_menu_recyclerView) + assertNotNull(recyclerView) + + val layoutManager = recyclerView.layoutManager as LinearLayoutManager + assertTrue(layoutManager.stackFromEnd) + } + @Test fun `invalidate will be forwarded to recyclerview adapter`() { val items = listOf( diff --git a/components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/display/DisplayToolbar.kt b/components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/display/DisplayToolbar.kt index c5c91ed75ab..edf16e86ce9 100644 --- a/components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/display/DisplayToolbar.kt +++ b/components/browser/toolbar/src/main/java/mozilla/components/browser/toolbar/display/DisplayToolbar.kt @@ -138,10 +138,12 @@ internal class DisplayToolbar( setOnClickListener { menu = menuBuilder?.build(context) + val endAlwaysVisible = menuBuilder?.endOfMenuAlwaysVisible ?: false menu?.show( anchor = this, - orientation = BrowserMenu.determineMenuOrientation(toolbar)) - + orientation = BrowserMenu.determineMenuOrientation(toolbar), + endOfMenuAlwaysVisible = endAlwaysVisible + ) emitOpenMenuFact(menuBuilder?.extras) } } diff --git a/docs/changelog.md b/docs/changelog.md index c5246091d14..11349442f3d 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -12,6 +12,9 @@ permalink: /changelog/ * [Gecko](https://github.com/mozilla-mobile/android-components/blob/master/buildSrc/src/main/java/Gecko.kt) * [Configuration](https://github.com/mozilla-mobile/android-components/blob/master/buildSrc/src/main/java/Config.kt) +* **browser-menu** + * Added `endOfMenuAlwaysVisible` property/parameter to `BrowserMenuBuilder` constructor and to `BrowserMenu.show` function. + When is set to true makes sure the bottom of the menu is always visible, this allows use cases like [#3211](https://github.com/mozilla-mobile/android-components/issues/3211). # 0.56.0