Bug 1816567 - Create a top-level DropdownMenu composable
parent
e5c7f151b0
commit
701bfd97a2
@ -0,0 +1,100 @@
|
||||
/* 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.compose
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.DropdownMenu
|
||||
import androidx.compose.material.DropdownMenuItem
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.mozilla.fenix.compose.annotation.LightDarkPreview
|
||||
import org.mozilla.fenix.theme.FirefoxTheme
|
||||
|
||||
/**
|
||||
* Popup action dropdown menu.
|
||||
*
|
||||
* @param menuItems List of items to be displayed in the menu.
|
||||
* @param showMenu Whether or not the menu is currently displayed to the user.
|
||||
* @param onDismissRequest Invoked when user dismisses the menu or on orientation changes.
|
||||
* @param modifier Modifier to be applied to the menu.
|
||||
*/
|
||||
@Composable
|
||||
fun DropdownMenu(
|
||||
menuItems: List<MenuItem>,
|
||||
showMenu: Boolean,
|
||||
onDismissRequest: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
DisposableEffect(LocalConfiguration.current.orientation) {
|
||||
onDispose { onDismissRequest() }
|
||||
}
|
||||
|
||||
MaterialTheme(shapes = MaterialTheme.shapes.copy(medium = RoundedCornerShape(2.dp))) {
|
||||
DropdownMenu(
|
||||
expanded = showMenu && menuItems.isNotEmpty(),
|
||||
onDismissRequest = { onDismissRequest() },
|
||||
modifier = Modifier
|
||||
.background(color = FirefoxTheme.colors.layer2)
|
||||
.then(modifier),
|
||||
) {
|
||||
for (item in menuItems) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
item.onClick()
|
||||
},
|
||||
) {
|
||||
Text(
|
||||
text = item.title,
|
||||
color = item.color ?: FirefoxTheme.colors.textPrimary,
|
||||
maxLines = 1,
|
||||
style = FirefoxTheme.typography.subtitle1,
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.align(Alignment.CenterVertically),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a text item from the dropdown menu.
|
||||
*
|
||||
* @property title Text the item should display.
|
||||
* @property color Color used to display the text.
|
||||
* @property onClick Callback to be called when the item is clicked.
|
||||
*/
|
||||
data class MenuItem(
|
||||
val title: String,
|
||||
val color: Color? = null,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
|
||||
@LightDarkPreview
|
||||
@Composable
|
||||
private fun DropdownMenuPreview() {
|
||||
FirefoxTheme {
|
||||
DropdownMenu(
|
||||
listOf(
|
||||
MenuItem("Rename") {},
|
||||
MenuItem("Share") {},
|
||||
MenuItem("Remove", FirefoxTheme.colors.textWarning) {},
|
||||
),
|
||||
true,
|
||||
{},
|
||||
)
|
||||
}
|
||||
}
|
@ -1,210 +0,0 @@
|
||||
/* 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.collections
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.DropdownMenu
|
||||
import androidx.compose.material.DropdownMenuItem
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import mozilla.components.browser.state.selector.normalTabs
|
||||
import mozilla.components.browser.state.state.recover.RecoverableTab
|
||||
import mozilla.components.concept.engine.Engine
|
||||
import mozilla.components.feature.tab.collections.Tab
|
||||
import mozilla.components.feature.tab.collections.TabCollection
|
||||
import org.mozilla.fenix.R.string
|
||||
import org.mozilla.fenix.compose.inComposePreview
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.theme.FirefoxTheme
|
||||
import org.mozilla.fenix.theme.Theme
|
||||
|
||||
/**
|
||||
* Menu shown for a [org.mozilla.fenix.home.collections.Collection].
|
||||
*
|
||||
* @see [DropdownMenu]
|
||||
*
|
||||
* @param showMenu Whether this is currently open and visible to the user.
|
||||
* @param menuItems List of options shown.
|
||||
* @param onDismissRequest Called when the user chooses a menu option or requests to dismiss the menu.
|
||||
*/
|
||||
@Composable
|
||||
fun CollectionMenu(
|
||||
showMenu: Boolean,
|
||||
menuItems: List<CollectionMenuItem>,
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
DisposableEffect(LocalConfiguration.current.orientation) {
|
||||
onDispose { onDismissRequest() }
|
||||
}
|
||||
|
||||
// DropdownMenu uses the medium shape from MaterialTheme.
|
||||
// Override it's corner radius to be the same 8.dp as in mozac_browser_menu_corner_radius
|
||||
MaterialTheme(shapes = MaterialTheme.shapes.copy(medium = RoundedCornerShape(8.dp))) {
|
||||
DropdownMenu(
|
||||
expanded = showMenu,
|
||||
onDismissRequest = { onDismissRequest() },
|
||||
modifier = Modifier
|
||||
.background(color = FirefoxTheme.colors.layer2),
|
||||
) {
|
||||
for (item in menuItems) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
item.onClick()
|
||||
},
|
||||
) {
|
||||
Text(
|
||||
text = item.title,
|
||||
color = item.color,
|
||||
maxLines = 1,
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.align(Alignment.CenterVertically),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A menu item for collections.
|
||||
*
|
||||
* @property title The menu item title.
|
||||
* @property color The color that should be set for the title.
|
||||
* @property onClick Invoked when the user clicks on the menu item.
|
||||
*/
|
||||
@Immutable
|
||||
data class CollectionMenuItem(
|
||||
val title: String,
|
||||
val color: Color,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
|
||||
/**
|
||||
* Constructs and returns the default list of menu options for a [TabCollection].
|
||||
*
|
||||
* @param collection [TabCollection] for which the menu will be shown.
|
||||
* Might serve as an argument for the callbacks for when the user interacts with certain menu options.
|
||||
* @param onOpenTabsTapped Invoked when the user chooses to open the tabs from [collection].
|
||||
* @param onRenameCollectionTapped Invoked when the user chooses to rename the [collection].
|
||||
* @param onAddTabTapped Invoked when the user chooses to add tabs to [collection].
|
||||
* @param onDeleteCollectionTapped Invoked when the user chooses to delete [collection].
|
||||
*/
|
||||
@Composable
|
||||
fun getMenuItems(
|
||||
collection: TabCollection,
|
||||
onOpenTabsTapped: (TabCollection) -> Unit,
|
||||
onRenameCollectionTapped: (TabCollection) -> Unit,
|
||||
onAddTabTapped: (TabCollection) -> Unit,
|
||||
onDeleteCollectionTapped: (TabCollection) -> Unit,
|
||||
): List<CollectionMenuItem> {
|
||||
return listOfNotNull(
|
||||
CollectionMenuItem(
|
||||
title = stringResource(string.collection_open_tabs),
|
||||
color = FirefoxTheme.colors.textPrimary,
|
||||
) {
|
||||
onOpenTabsTapped(collection)
|
||||
},
|
||||
CollectionMenuItem(
|
||||
title = stringResource(string.collection_rename),
|
||||
color = FirefoxTheme.colors.textPrimary,
|
||||
) {
|
||||
onRenameCollectionTapped(collection)
|
||||
},
|
||||
|
||||
if (hasOpenTabs()) {
|
||||
CollectionMenuItem(
|
||||
title = stringResource(string.add_tab),
|
||||
color = FirefoxTheme.colors.textPrimary,
|
||||
) {
|
||||
onAddTabTapped(collection)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
},
|
||||
|
||||
CollectionMenuItem(
|
||||
title = stringResource(string.collection_delete),
|
||||
color = FirefoxTheme.colors.textWarning,
|
||||
) {
|
||||
onDeleteCollectionTapped(collection)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun hasOpenTabs() = when (inComposePreview) {
|
||||
true -> true
|
||||
false -> LocalContext.current.components.core.store.state.normalTabs.isNotEmpty()
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
private fun CollectionMenuDarkPreview() {
|
||||
FirefoxTheme(Theme.Dark) {
|
||||
CollectionMenu(
|
||||
showMenu = true,
|
||||
menuItems = getMenuItems(
|
||||
collection = collectionPreview,
|
||||
onOpenTabsTapped = {},
|
||||
onRenameCollectionTapped = {},
|
||||
onAddTabTapped = {},
|
||||
onDeleteCollectionTapped = {},
|
||||
),
|
||||
) {}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
private fun CollectionMenuLightPreview() {
|
||||
FirefoxTheme(Theme.Light) {
|
||||
CollectionMenu(
|
||||
showMenu = true,
|
||||
menuItems = getMenuItems(
|
||||
collection = collectionPreview,
|
||||
onOpenTabsTapped = {},
|
||||
onRenameCollectionTapped = {},
|
||||
onAddTabTapped = {},
|
||||
onDeleteCollectionTapped = {},
|
||||
),
|
||||
) {}
|
||||
}
|
||||
}
|
||||
|
||||
private val collectionPreview = object : TabCollection {
|
||||
override val id: Long = 1L
|
||||
override val tabs: List<Tab> = emptyList()
|
||||
override val title: String = "Collection 1"
|
||||
|
||||
override fun restore(
|
||||
context: Context,
|
||||
engine: Engine,
|
||||
restoreSessionId: Boolean,
|
||||
): List<RecoverableTab> = emptyList()
|
||||
|
||||
override fun restoreSubset(
|
||||
context: Context,
|
||||
engine: Engine,
|
||||
tabs: List<Tab>,
|
||||
restoreSessionId: Boolean,
|
||||
): List<RecoverableTab> = emptyList()
|
||||
}
|
Loading…
Reference in New Issue