You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
iceraven-browser/app/src/main/java/org/mozilla/fenix/compose/list/ListItem.kt

340 lines
11 KiB
Kotlin

/* 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.list
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.mozilla.fenix.R
import org.mozilla.fenix.compose.Favicon
import org.mozilla.fenix.theme.FirefoxTheme
private val LIST_ITEM_HEIGHT = 56.dp
private val ICON_SIZE = 24.dp
/**
* List item used to display a label with an optional description text and
* an optional [IconButton] at the end.
*
* @param label The label in the list item.
* @param modifier [Modifier] to be applied to the layout.
* @param description An optional description text below the label.
* @param maxDescriptionLines An optional maximum number of lines for the description text to span.
* @param onClick Called when the user clicks on the item.
* @param iconPainter [Painter] used to display an [IconButton] after the list item.
* @param iconDescription Content description of the icon.
* @param onIconClick Called when the user clicks on the icon.
*/
@Composable
fun TextListItem(
label: String,
modifier: Modifier = Modifier,
description: String? = null,
maxDescriptionLines: Int = 1,
onClick: (() -> Unit)? = null,
iconPainter: Painter? = null,
iconDescription: String? = null,
onIconClick: (() -> Unit)? = null,
) {
ListItem(
label = label,
modifier = modifier,
description = description,
maxDescriptionLines = maxDescriptionLines,
onClick = onClick,
) {
if (iconPainter != null && onIconClick != null) {
IconButton(
onClick = onIconClick,
modifier = Modifier
.padding(end = 16.dp)
.size(ICON_SIZE),
) {
Icon(
painter = iconPainter,
contentDescription = iconDescription,
tint = FirefoxTheme.colors.iconPrimary,
)
}
}
}
}
/**
* List item used to display a label and a [Favicon] with an optional description text and
* an optional [IconButton] at the end.
*
* @param label The label in the list item.
* @param description An optional description text below the label.
* @param onClick Called when the user clicks on the item.
* @param url Website [url] for which the favicon will be shown.
* @param iconPainter [Painter] used to display an [IconButton] after the list item.
* @param iconDescription Content description of the icon.
* @param onIconClick Called when the user clicks on the icon.
*/
@Composable
fun FaviconListItem(
label: String,
description: String? = null,
onClick: (() -> Unit)? = null,
url: String,
iconPainter: Painter? = null,
iconDescription: String? = null,
onIconClick: (() -> Unit)? = null,
) {
ListItem(
label = label,
description = description,
onClick = onClick,
beforeListAction = {
Favicon(
url = url,
size = ICON_SIZE,
modifier = Modifier.padding(horizontal = 16.dp),
)
},
afterListAction = {
if (iconPainter != null && onIconClick != null) {
IconButton(
onClick = onIconClick,
modifier = Modifier
.padding(end = 16.dp)
.size(ICON_SIZE),
) {
Icon(
painter = iconPainter,
contentDescription = iconDescription,
tint = FirefoxTheme.colors.iconPrimary,
)
}
}
},
)
}
/**
* List item used to display a label and an icon at the beginning with an optional description
* text and an optional [IconButton] at the end.
*
* @param label The label in the list item.
* @param description An optional description text below the label.
* @param onClick Called when the user clicks on the item.
* @param beforeIconPainter [Painter] used to display an [Icon] before the list item.
* @param beforeIconDescription Content description of the icon.
* @param afterIconPainter [Painter] used to display an [IconButton] after the list item.
* @param afterIconDescription Content description of the icon.
* @param onAfterIconClick Called when the user clicks on the icon.
*/
@Composable
fun IconListItem(
label: String,
description: String? = null,
onClick: (() -> Unit)? = null,
beforeIconPainter: Painter,
beforeIconDescription: String? = null,
afterIconPainter: Painter? = null,
afterIconDescription: String? = null,
onAfterIconClick: (() -> Unit)? = null,
) {
ListItem(
label = label,
description = description,
onClick = onClick,
beforeListAction = {
Icon(
painter = beforeIconPainter,
contentDescription = beforeIconDescription,
modifier = Modifier.padding(horizontal = 16.dp),
tint = FirefoxTheme.colors.iconPrimary,
)
},
afterListAction = {
if (afterIconPainter != null && onAfterIconClick != null) {
IconButton(
onClick = onAfterIconClick,
modifier = Modifier
.padding(end = 16.dp)
.size(ICON_SIZE),
) {
Icon(
painter = afterIconPainter,
contentDescription = afterIconDescription,
tint = FirefoxTheme.colors.iconPrimary,
)
}
}
},
)
}
/**
* Base list item used to display a label with an optional description text and
* the flexibility to add custom UI to either end of the item.
*
* @param label The label in the list item.
* @param modifier [Modifier] to be applied to the layout.
* @param description An optional description text below the label.
* @param maxDescriptionLines An optional maximum number of lines for the description text to span.
* @param onClick Called when the user clicks on the item.
* @param beforeListAction Optional Composable for adding UI before the list item.
* @param afterListAction Optional Composable for adding UI to the end of the list item.
*/
@Composable
private fun ListItem(
label: String,
modifier: Modifier = Modifier,
description: String? = null,
maxDescriptionLines: Int = 1,
onClick: (() -> Unit)? = null,
beforeListAction: @Composable RowScope.() -> Unit = {},
afterListAction: @Composable RowScope.() -> Unit = {},
) {
Row(
modifier = when (onClick != null) {
true -> Modifier.clickable { onClick() }
false -> Modifier
}.then(
Modifier.defaultMinSize(minHeight = LIST_ITEM_HEIGHT),
),
verticalAlignment = Alignment.CenterVertically,
) {
beforeListAction()
Column(
modifier = modifier
.padding(horizontal = 16.dp, vertical = 6.dp)
.weight(1f),
) {
Text(
text = label,
color = FirefoxTheme.colors.textPrimary,
style = FirefoxTheme.typography.subtitle1,
maxLines = 1,
)
description?.let {
Text(
text = description,
color = FirefoxTheme.colors.textSecondary,
style = FirefoxTheme.typography.body2,
maxLines = maxDescriptionLines,
)
}
}
afterListAction()
}
}
@Composable
@Preview(name = "TextListItem", uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun TextListItemPreview() {
FirefoxTheme {
Box(Modifier.background(FirefoxTheme.colors.layer1)) {
TextListItem(label = "Label only")
}
}
}
@Composable
@Preview(name = "TextListItem with a description", uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun TextListItemWithDescriptionPreview() {
FirefoxTheme {
Box(Modifier.background(FirefoxTheme.colors.layer1)) {
TextListItem(
label = "Label + description",
description = "Description text",
)
}
}
}
@Composable
@Preview(name = "TextListItem with a right icon", uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun TextListItemWithIconPreview() {
FirefoxTheme {
Box(Modifier.background(FirefoxTheme.colors.layer1)) {
TextListItem(
label = "Label + right icon",
iconPainter = painterResource(R.drawable.ic_menu),
iconDescription = "click me",
onIconClick = { println("icon click") },
)
}
}
}
@Composable
@Preview(name = "IconListItem", uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun IconListItemPreview() {
FirefoxTheme {
Box(Modifier.background(FirefoxTheme.colors.layer1)) {
IconListItem(
label = "Left icon list item",
beforeIconPainter = painterResource(R.drawable.ic_folder_icon),
beforeIconDescription = "click me",
)
}
}
}
@Composable
@Preview(
name = "IconListItem with an interactable right icon",
uiMode = Configuration.UI_MODE_NIGHT_YES,
)
private fun IconListItemWithRightIconPreview() {
FirefoxTheme {
Box(Modifier.background(FirefoxTheme.colors.layer1)) {
IconListItem(
label = "Left icon list item + right icon",
beforeIconPainter = painterResource(R.drawable.ic_folder_icon),
beforeIconDescription = null,
afterIconPainter = painterResource(R.drawable.ic_menu),
afterIconDescription = "click me",
onAfterIconClick = { println("icon click") },
)
}
}
}
@Composable
@Preview(
name = "FaviconListItem with a right icon and onClicks",
uiMode = Configuration.UI_MODE_NIGHT_YES,
)
private fun FaviconListItemPreview() {
FirefoxTheme {
Box(Modifier.background(FirefoxTheme.colors.layer1)) {
FaviconListItem(
label = "Favicon + right icon + clicks",
description = "Description text",
onClick = { println("list item click") },
url = "",
iconPainter = painterResource(R.drawable.ic_menu),
onIconClick = { println("icon click") },
)
}
}
}