Skip to content

Commit

Permalink
[components] Close mozilla-mobile/android-components#12398: Add suppo…
Browse files Browse the repository at this point in the history
…rt for forcing menu orientation
  • Loading branch information
rocketsroger authored and mergify[bot] committed Jun 28, 2022
1 parent 8f5e85d commit af86e76
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 14 deletions.
Expand Up @@ -40,10 +40,13 @@ class BrowserMenuController(
/**
* @param anchor The view on which to pin the popup window.
* @param orientation The preferred orientation to show the popup window.
* @param forceOrientation When set to true, the orientation will be respected even when the
* menu doesn't fully fit.
*/
override fun show(
anchor: View,
orientation: Orientation?
orientation: Orientation?,
forceOrientation: Boolean,
): PopupWindow {
val view = MenuView(anchor.context).apply {
// Show nested list if present, or the standard menu candidates list.
Expand All @@ -56,7 +59,11 @@ class BrowserMenuController(
view.onDismiss = ::dismiss
view.onReopenMenu = ::reopenMenu
setOnDismissListener(menuDismissListener)
displayPopup(view, anchor, orientation)
displayPopup(view, anchor, orientation, forceOrientation)

if (orientation == Orientation.UP && forceOrientation) {
view.scrollOnceToTheBottom()
}
}.also {
currentPopupInfo = PopupMenuInfo(
window = it,
Expand Down
Expand Up @@ -16,6 +16,7 @@ internal fun PopupWindow.displayPopup(
containerView: View,
anchor: View,
preferredOrientation: Orientation? = null,
forceOrientation: Boolean = false,
) {
// Measure menu
val spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
Expand All @@ -34,9 +35,9 @@ internal fun PopupWindow.displayPopup(

// Try to use the preferred orientation, if doesn't fit fallback to the best fit.
when {
preferredOrientation == Orientation.DOWN && fitsDown ->
preferredOrientation == Orientation.DOWN && (fitsDown || forceOrientation) ->
showPopupWithDownOrientation(anchor, reversed)
preferredOrientation == Orientation.UP && fitsUp ->
preferredOrientation == Orientation.UP && (fitsUp || forceOrientation) ->
showPopupWithUpOrientation(anchor, containerHeight, reversed)
else -> {
showPopupWhereBestFits(
Expand Down
Expand Up @@ -11,7 +11,6 @@ import android.util.AttributeSet
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
Expand Down Expand Up @@ -75,7 +74,7 @@ class MenuView @JvmOverloads constructor(
// In devices with Android 6 and below stackFromEnd is not working properly,
// as a result, we have to provided a backwards support.
// See: https://github.com/mozilla-mobile/android-components/issues/3211
if (side == Side.END) scrollOnceToTheBottom(recyclerView)
if (side == Side.END) scrollOnceToTheBottom()
}
}

Expand All @@ -86,8 +85,10 @@ class MenuView @JvmOverloads constructor(
style.backgroundColor?.let { cardView.setCardBackgroundColor(it) }
}

@VisibleForTesting
internal fun scrollOnceToTheBottom(recyclerView: RecyclerView) {
/**
* Scroll to the bottom of the menu view.
*/
fun scrollOnceToTheBottom() {
recyclerView.onNextGlobalLayout {
recyclerView.adapter?.let { recyclerView.scrollToPosition(it.itemCount - 1) }
}
Expand Down
Expand Up @@ -134,6 +134,26 @@ class PopupWindowTest {
verify(popupWindow).showAtLocation(anchor, Gravity.START or Gravity.TOP, 0, 10)
}

@Test
fun `WHEN displaying force up popup from bottom left THEN show popup up and to the left`() {
anchor = createMockViewWith(x = 0, y = 190, false)
doReturn(300).`when`(menuContentView).measuredHeight

popupWindow.displayPopup(menuContentView, anchor, Orientation.UP, true)
assertEquals(R.style.Mozac_Browser_Menu2_Animation_OverflowMenuLeftBottom, popupWindow.animationStyle)
verify(popupWindow).showAsDropDown(anchor, 0, -300)
}

@Test
fun `WHEN displaying force down popup from top left THEN show popup down and to the right`() {
anchor = createMockViewWith(x = 0, y = 0, false)
doReturn(300).`when`(menuContentView).measuredHeight

popupWindow.displayPopup(menuContentView, anchor, Orientation.DOWN, true)
assertEquals(R.style.Mozac_Browser_Menu2_Animation_OverflowMenuLeftTop, popupWindow.animationStyle)
verify(popupWindow).showAsDropDown(anchor, 0, 0)
}

private fun createMockViewWith(x: Int, y: Int, isRTL: Boolean): View {
val view = spy(View(testContext))
doAnswer { invocation ->
Expand Down
Expand Up @@ -15,7 +15,6 @@ import mozilla.components.browser.menu2.R
import mozilla.components.concept.menu.MenuStyle
import mozilla.components.concept.menu.Side
import mozilla.components.concept.menu.candidate.DecorativeTextMenuCandidate
import mozilla.components.support.test.any
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
Expand Down Expand Up @@ -63,25 +62,25 @@ class MenuViewTest {
@Test
@Config(sdk = [Build.VERSION_CODES.M])
fun `setVisibleSide will be forwarded to scrollOnceToTheBottom on devices with Android M and below`() {
doNothing().`when`(menuView).scrollOnceToTheBottom(any())
doNothing().`when`(menuView).scrollOnceToTheBottom()

menuView.setVisibleSide(Side.END)
val layoutManager = recyclerView.layoutManager as LinearLayoutManager

assertFalse(layoutManager.stackFromEnd)
verify(menuView).scrollOnceToTheBottom(any())
verify(menuView).scrollOnceToTheBottom()
}

@Test
@Config(sdk = [Build.VERSION_CODES.N])
fun `setVisibleSide changes stackFromEnd on devices with Android N and above`() {
doNothing().`when`(menuView).scrollOnceToTheBottom(any())
doNothing().`when`(menuView).scrollOnceToTheBottom()

menuView.setVisibleSide(Side.END)
val layoutManager = recyclerView.layoutManager as LinearLayoutManager

assertTrue(layoutManager.stackFromEnd)
verify(menuView, never()).scrollOnceToTheBottom(any())
verify(menuView, never()).scrollOnceToTheBottom()
}

@Test
Expand Down
Expand Up @@ -17,8 +17,10 @@ interface MenuController : Observable<MenuController.Observer> {
/**
* @param anchor The view on which to pin the popup window.
* @param orientation The preferred orientation to show the popup window.
* @param forceOrientation When set to true, the orientation will be respected even when the
* menu doesn't fully fit.
*/
fun show(anchor: View, orientation: Orientation? = null): PopupWindow
fun show(anchor: View, orientation: Orientation? = null, forceOrientation: Boolean = false): PopupWindow

/**
* Dismiss the menu popup if the menu is visible.
Expand Down

0 comments on commit af86e76

Please sign in to comment.