refactor(ui): Refactor SettingsItem component (#3364)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2025-10-06 21:23:34 -05:00 committed by GitHub
parent 4d7ad96a09
commit 3f923ae5c6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 114 additions and 101 deletions

View file

@ -21,6 +21,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.KeyboardArrowRight
import androidx.compose.material.icons.rounded.Android
@ -30,6 +31,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -40,50 +42,35 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.meshtastic.core.ui.theme.AppTheme
/** A clickable settings button item. */
/**
* A clickable settings item with optional supporting text and trailing content. Defaults to a trailing arrow icon if no
* custom trailing content is provided.
*/
@Composable
fun SettingsItem(
text: String,
supportingText: String? = null,
textColor: Color = LocalContentColor.current,
enabled: Boolean = true,
leadingIcon: ImageVector? = null,
leadingIconTint: Color = LocalContentColor.current,
trailingIcon: ImageVector? = Icons.AutoMirrored.Rounded.KeyboardArrowRight,
trailingIconTint: Color = LocalContentColor.current,
trailingContent: @Composable (() -> Unit)? = null,
onClick: () -> Unit,
) {
SettingsItem(
val finalTrailingContent: @Composable (() -> Unit) =
trailingContent ?: { Icons.AutoMirrored.Rounded.KeyboardArrowRight.Icon(LocalContentColor.current) }
SettingsListItem(
text = text,
textColor = textColor,
enabled = enabled,
leadingIcon = leadingIcon,
leadingIconTint = leadingIconTint,
trailingContent = { trailingIcon.Icon(trailingIconTint) },
onClick = onClick,
leadingContent = { leadingIcon.Icon(leadingIconTint) },
supportingContent = { supportingText?.let { Text(text = it, style = MaterialTheme.typography.titleMedium) } },
trailingContent = finalTrailingContent,
)
}
/** A clickable settings button item. */
@Composable
fun SettingsItem(
text: String,
textColor: Color = LocalContentColor.current,
enabled: Boolean = true,
leadingIcon: ImageVector? = null,
leadingIconTint: Color = LocalContentColor.current,
trailingContent: @Composable (() -> Unit),
onClick: () -> Unit,
) {
ClickableWrapper(enabled = enabled, onClick = onClick) {
Content(
leading = { leadingIcon.Icon(leadingIconTint) },
text = text,
textColor = textColor,
trailing = trailingContent,
)
}
}
/** A toggleable settings switch item. */
@Composable
fun SettingsItemSwitch(
@ -95,67 +82,84 @@ fun SettingsItemSwitch(
leadingIconTint: Color = LocalContentColor.current,
onClick: () -> Unit,
) {
ClickableWrapper(enabled = enabled, onClick = onClick) {
Content(
leading = { leadingIcon.Icon(leadingIconTint) },
text = text,
textColor = textColor,
trailing = { Switch(checked = checked, enabled = enabled, onCheckedChange = null) },
)
}
SettingsListItem(
text = text,
textColor = textColor,
enabled = enabled,
onClick = onClick,
leadingContent = { leadingIcon.Icon(leadingIconTint) },
trailingContent = { Switch(checked = checked, enabled = enabled, onCheckedChange = null) },
)
}
/** A settings detail item. */
@Composable
fun SettingsItemDetail(
text: String,
supportingText: String?,
textColor: Color = LocalContentColor.current,
icon: ImageVector? = null,
iconTint: Color = LocalContentColor.current,
trailingText: String? = null,
enabled: Boolean = true,
onClick: (() -> Unit)? = null,
) {
val content: @Composable ColumnScope.() -> Unit = {
Content(
leading = { icon.Icon(iconTint) },
text = text,
textColor = textColor,
trailing = { trailingText?.let { Text(text = it) } },
SettingsListItem(
text = text,
textColor = textColor,
enabled = enabled,
onClick = onClick,
leadingContent = { icon.Icon(iconTint) },
supportingContent = {
supportingText?.let {
Text(
text = it,
style = MaterialTheme.typography.titleLarge,
color = textColor, // Detail style explicitly sets color
)
}
},
trailingContent = {},
)
}
/**
* Base composable for all settings screen list items. It handles the Material3 [ListItem] structure and the conditional
* click wrapper.
*/
@Composable
private fun SettingsListItem(
text: String,
textColor: Color,
enabled: Boolean,
onClick: (() -> Unit)?,
leadingContent: @Composable (() -> Unit)? = null,
supportingContent: @Composable (() -> Unit)? = null,
trailingContent: @Composable (() -> Unit)? = null,
) {
val listItemContent: @Composable ColumnScope.() -> Unit = {
ListItem(
modifier = Modifier.padding(horizontal = 8.dp),
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
headlineContent = { Text(text = text, color = textColor) },
supportingContent = { SelectionContainer { supportingContent?.invoke() } },
leadingContent = leadingContent,
trailingContent = trailingContent,
)
}
if (onClick != null) {
ClickableWrapper(enabled = enabled, onClick = onClick, content = content)
Card(
onClick = onClick,
enabled = enabled,
colors =
CardDefaults.cardColors(containerColor = Color.Transparent, disabledContainerColor = Color.Transparent),
content = listItemContent,
)
} else {
Column(content = content)
Column(content = listItemContent)
}
}
/** A clickable Card wrapper used for all clickable settings items. */
@Composable
private fun ClickableWrapper(enabled: Boolean, onClick: () -> Unit, content: @Composable ColumnScope.() -> Unit) {
Card(
onClick = onClick,
enabled = enabled,
colors =
CardDefaults.cardColors(containerColor = Color.Transparent, disabledContainerColor = Color.Transparent),
content = content,
)
}
/** The row content to display for a settings item. */
@Composable
private fun Content(leading: @Composable () -> Unit, text: String, textColor: Color, trailing: @Composable () -> Unit) {
ListItem(
modifier = Modifier.padding(horizontal = 8.dp),
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
headlineContent = { Text(text = text, color = textColor) },
leadingContent = { leading() },
trailingContent = { trailing() },
)
}
@Composable
private fun ImageVector?.Icon(tint: Color = LocalContentColor.current) =
this?.let { Icon(imageVector = it, contentDescription = null, modifier = Modifier.size(24.dp), tint = tint) }
@ -181,5 +185,5 @@ private fun SettingsItemSwitchPreview() {
@Preview(showBackground = true)
@Composable
private fun SettingsItemDetailPreview() {
AppTheme { SettingsItemDetail(text = "Text 1", icon = Icons.Rounded.Android, trailingText = "Text2") }
AppTheme { SettingsItemDetail(text = "Text 1", icon = Icons.Rounded.Android, supportingText = "Text2") }
}