Skip to content
This repository has been archived by the owner on Jun 17, 2024. It is now read-only.

Commit

Permalink
Browse files Browse the repository at this point in the history
8163: Add MenuStyle concept class r=jonalmeida a=NotWoods

Most of our components are styled using data classes. This change allows the menus to be styled in the same manner, which is required to port the Fenix tabs tray menu to menu2.



Co-authored-by: Tiger Oakes <[email protected]>
  • Loading branch information
MozLando and NotWoods committed Aug 21, 2020
2 parents d6a6c73 + c683c9e commit 3a4c8ca
Show file tree
Hide file tree
Showing 15 changed files with 189 additions and 121 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -39,35 +41,43 @@ 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 {
val view = LayoutInflater.from(anchor.context).inflate(R.layout.mozac_browser_menu, null)

adapter.menu = this

menuList = view.findViewById<RecyclerView>(R.id.mozac_browser_menu_recyclerView).apply {
menuList = view.findViewById<DynamicWidthRecyclerView>(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<CardView>(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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -33,6 +34,7 @@ class WebExtensionBrowserMenu internal constructor(
override fun show(
anchor: View,
orientation: Orientation,
style: MenuStyle?,
endOfMenuAlwaysVisible: Boolean,
onDismiss: () -> Unit
): PopupWindow {
Expand All @@ -47,6 +49,7 @@ class WebExtensionBrowserMenu internal constructor(
return super.show(
anchor,
orientation,
style,
endOfMenuAlwaysVisible,
onDismiss
).apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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" />

</androidx.cardview.widget.CardView>

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
<dimen name="mozac_browser_menu_item_container_padding_end">16dp</dimen>
<!--Menu Item -->

<!--DynamicWidthRecyclerView -->
<dimen name="mozac_browser_menu_material_min_tap_area">48dp</dimen>
<dimen name="mozac_browser_menu_material_min_item_width">112dp</dimen>
<!--DynamicWidthRecyclerView -->

<!--BrowserMenuDivider -->
<dimen name="mozac_browser_menu_item_divider_height">1dp</dimen>
<!--BrowserMenuDivider -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<CardView>(R.id.mozac_browser_menu_menuView)
val recyclerView = popup.contentView.findViewById<DynamicWidthRecyclerView>(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(
Expand Down
Loading

0 comments on commit 3a4c8ca

Please sign in to comment.