feat: Refactor settings UI components to use SettingsGroup for better organization

This commit is contained in:
tapframe 2026-03-28 20:37:02 +05:30
parent c5cf82d54b
commit 2510798092
7 changed files with 303 additions and 257 deletions

View file

@ -13,13 +13,15 @@ internal fun LazyListScope.appearanceSettingsContent(
title = "HOME",
isTablet = isTablet,
) {
SettingsNavigationRow(
title = "Continue Watching",
description = "Show, hide, and style the Continue Watching shelf.",
icon = Icons.Rounded.Style,
isTablet = isTablet,
onClick = onContinueWatchingClick,
)
SettingsGroup(isTablet = isTablet) {
SettingsNavigationRow(
title = "Continue Watching",
description = "Show, hide, and style the Continue Watching shelf.",
icon = Icons.Rounded.Style,
isTablet = isTablet,
onClick = onContinueWatchingClick,
)
}
}
}
}

View file

@ -15,13 +15,15 @@ internal fun LazyListScope.contentDiscoveryContent(
title = "SOURCES",
isTablet = isTablet,
) {
SettingsNavigationRow(
title = "Addons",
description = "Install, remove, refresh, and sort your content sources.",
icon = Icons.Rounded.Extension,
isTablet = isTablet,
onClick = onAddonsClick,
)
SettingsGroup(isTablet = isTablet) {
SettingsNavigationRow(
title = "Addons",
description = "Install, remove, refresh, and sort your content sources.",
icon = Icons.Rounded.Extension,
isTablet = isTablet,
onClick = onAddonsClick,
)
}
}
}
item {
@ -29,13 +31,15 @@ internal fun LazyListScope.contentDiscoveryContent(
title = "HOME",
isTablet = isTablet,
) {
SettingsNavigationRow(
title = "Homescreen",
description = "Control which catalogs appear on Home and in what order.",
icon = Icons.Rounded.Tune,
isTablet = isTablet,
onClick = onHomescreenClick,
)
SettingsGroup(isTablet = isTablet) {
SettingsNavigationRow(
title = "Homescreen",
description = "Control which catalogs appear on Home and in what order.",
icon = Icons.Rounded.Tune,
isTablet = isTablet,
onClick = onHomescreenClick,
)
}
}
}
}

View file

@ -34,13 +34,15 @@ internal fun LazyListScope.continueWatchingSettingsContent(
title = "VISIBILITY",
isTablet = isTablet,
) {
SettingsSwitchRow(
title = "Show Continue Watching",
description = "Display the Continue Watching shelf on the Home screen.",
checked = isVisible,
isTablet = isTablet,
onCheckedChange = ContinueWatchingPreferencesRepository::setVisible,
)
SettingsGroup(isTablet = isTablet) {
SettingsSwitchRow(
title = "Show Continue Watching",
description = "Display the Continue Watching shelf on the Home screen.",
checked = isVisible,
isTablet = isTablet,
onCheckedChange = ContinueWatchingPreferencesRepository::setVisible,
)
}
}
}
item {

View file

@ -17,13 +17,15 @@ internal fun LazyListScope.homescreenSettingsContent(
title = "HERO",
isTablet = isTablet,
) {
SettingsSwitchRow(
title = "Show Hero",
description = "Display a featured hero carousel at the top of Home.",
checked = heroEnabled,
isTablet = isTablet,
onCheckedChange = HomeCatalogSettingsRepository::setHeroEnabled,
)
SettingsGroup(isTablet = isTablet) {
SettingsSwitchRow(
title = "Show Hero",
description = "Display a featured hero carousel at the top of Home.",
checked = heroEnabled,
isTablet = isTablet,
onCheckedChange = HomeCatalogSettingsRepository::setHeroEnabled,
)
}
}
}
item {
@ -32,14 +34,19 @@ internal fun LazyListScope.homescreenSettingsContent(
title = "HERO SOURCES",
isTablet = isTablet,
) {
items.forEach { item ->
SettingsSwitchRow(
title = item.displayTitle,
description = item.addonName,
checked = item.heroSourceEnabled,
isTablet = isTablet,
onCheckedChange = { HomeCatalogSettingsRepository.setHeroSourceEnabled(item.key, it) },
)
SettingsGroup(isTablet = isTablet) {
items.forEachIndexed { index, item ->
if (index > 0) {
SettingsGroupDivider(isTablet = isTablet)
}
SettingsSwitchRow(
title = item.displayTitle,
description = item.addonName,
checked = item.heroSourceEnabled,
isTablet = isTablet,
onCheckedChange = { HomeCatalogSettingsRepository.setHeroSourceEnabled(item.key, it) },
)
}
}
}
}
@ -56,17 +63,22 @@ internal fun LazyListScope.homescreenSettingsContent(
title = "CATALOGS",
isTablet = isTablet,
) {
items.forEachIndexed { index, item ->
HomescreenCatalogRow(
item = item,
isTablet = isTablet,
canMoveUp = index > 0,
canMoveDown = index < items.lastIndex,
onTitleChange = { HomeCatalogSettingsRepository.setCustomTitle(item.key, it) },
onEnabledChange = { HomeCatalogSettingsRepository.setEnabled(item.key, it) },
onMoveUp = { HomeCatalogSettingsRepository.moveUp(item.key) },
onMoveDown = { HomeCatalogSettingsRepository.moveDown(item.key) },
)
SettingsGroup(isTablet = isTablet) {
items.forEachIndexed { index, item ->
if (index > 0) {
SettingsGroupDivider(isTablet = isTablet)
}
HomescreenCatalogRow(
item = item,
isTablet = isTablet,
canMoveUp = index > 0,
canMoveDown = index < items.lastIndex,
onTitleChange = { HomeCatalogSettingsRepository.setCustomTitle(item.key, it) },
onEnabledChange = { HomeCatalogSettingsRepository.setEnabled(item.key, it) },
onMoveUp = { HomeCatalogSettingsRepository.moveUp(item.key) },
onMoveDown = { HomeCatalogSettingsRepository.moveDown(item.key) },
)
}
}
}
}

View file

@ -12,13 +12,15 @@ internal fun LazyListScope.playbackSettingsContent(
title = "PLAYER",
isTablet = isTablet,
) {
SettingsSwitchRow(
title = "Show Loading Overlay",
description = "Show the opening loading overlay while a stream starts playing.",
checked = showLoadingOverlay,
isTablet = isTablet,
onCheckedChange = PlayerSettingsRepository::setShowLoadingOverlay,
)
SettingsGroup(isTablet = isTablet) {
SettingsSwitchRow(
title = "Show Loading Overlay",
description = "Show the opening loading overlay while a stream starts playing.",
checked = showLoadingOverlay,
isTablet = isTablet,
onCheckedChange = PlayerSettingsRepository::setShowLoadingOverlay,
)
}
}
}
}

View file

@ -22,6 +22,7 @@ import androidx.compose.material.icons.automirrored.rounded.ArrowForward
import androidx.compose.material.icons.rounded.KeyboardArrowDown
import androidx.compose.material.icons.rounded.KeyboardArrowUp
import androidx.compose.material3.Icon
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedTextFieldDefaults
@ -52,14 +53,37 @@ private fun SettingsCard(
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(if (isTablet) 20.dp else 16.dp),
border = BorderStroke(
1.dp,
MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.42f),
0.5.dp,
MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.16f),
),
) {
Column(content = content)
}
}
@Composable
internal fun SettingsGroup(
isTablet: Boolean,
modifier: Modifier = Modifier,
content: @Composable ColumnScope.() -> Unit,
) {
SettingsCard(
isTablet = isTablet,
modifier = modifier,
) {
Column(content = content)
}
}
@Composable
internal fun SettingsGroupDivider(isTablet: Boolean) {
HorizontalDivider(
modifier = Modifier.padding(start = if (isTablet) 78.dp else 66.dp),
thickness = 0.5.dp,
color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.12f),
)
}
@Composable
internal fun TabletPageHeader(
title: String,
@ -155,9 +179,7 @@ internal fun SettingsSection(
isTablet: Boolean,
content: @Composable ColumnScope.() -> Unit,
) {
Column(
verticalArrangement = Arrangement.spacedBy(if (isTablet) 10.dp else 8.dp),
) {
Column {
NuvioSectionLabel(text = title)
Spacer(modifier = Modifier.height(if (isTablet) 12.dp else 10.dp))
content()
@ -177,61 +199,59 @@ internal fun SettingsNavigationRow(
val verticalPadding = if (isTablet) 16.dp else 14.dp
val horizontalPadding = if (isTablet) 20.dp else 16.dp
SettingsCard(isTablet = isTablet) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(horizontal = horizontalPadding, vertical = verticalPadding),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(horizontal = horizontalPadding, vertical = verticalPadding),
horizontalArrangement = Arrangement.SpaceBetween,
.padding(end = 12.dp)
.widthIn(max = if (isTablet) 560.dp else 320.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Row(
modifier = Modifier
.padding(end = 12.dp)
.widthIn(max = if (isTablet) 560.dp else 320.dp),
verticalAlignment = Alignment.CenterVertically,
Surface(
modifier = Modifier.size(iconSize),
color = MaterialTheme.colorScheme.primary.copy(alpha = 0.12f),
shape = RoundedCornerShape(iconRadius),
) {
Surface(
modifier = Modifier.size(iconSize),
color = MaterialTheme.colorScheme.primary.copy(alpha = 0.12f),
shape = RoundedCornerShape(iconRadius),
Row(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
Row(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
)
}
}
Spacer(modifier = Modifier.width(if (isTablet) 16.dp else 14.dp))
Column {
Text(
text = title,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface,
fontWeight = FontWeight.Medium,
)
Spacer(modifier = Modifier.height(2.dp))
Text(
text = description,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.alpha(0.92f),
Icon(
imageVector = icon,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
)
}
}
Icon(
imageVector = Icons.AutoMirrored.Rounded.ArrowForward,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
Spacer(modifier = Modifier.width(if (isTablet) 16.dp else 14.dp))
Column {
Text(
text = title,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface,
fontWeight = FontWeight.Medium,
)
Spacer(modifier = Modifier.height(2.dp))
Text(
text = description,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.alpha(0.92f),
)
}
}
Icon(
imageVector = Icons.AutoMirrored.Rounded.ArrowForward,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
@ -246,46 +266,44 @@ internal fun SettingsSwitchRow(
val verticalPadding = if (isTablet) 16.dp else 14.dp
val horizontalPadding = if (isTablet) 20.dp else 16.dp
SettingsCard(isTablet = isTablet) {
Row(
Row(
modifier = Modifier
.fillMaxWidth()
.clickable { onCheckedChange(!checked) }
.padding(horizontal = horizontalPadding, vertical = verticalPadding),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.clickable { onCheckedChange(!checked) }
.padding(horizontal = horizontalPadding, vertical = verticalPadding),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
.padding(end = 12.dp)
.widthIn(max = if (isTablet) 560.dp else 280.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
Column(
modifier = Modifier
.padding(end = 12.dp)
.widthIn(max = if (isTablet) 560.dp else 280.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
text = title,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface,
fontWeight = FontWeight.Medium,
)
if (!description.isNullOrBlank()) {
Text(
text = description,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
Switch(
checked = checked,
onCheckedChange = onCheckedChange,
colors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colorScheme.onPrimary,
checkedTrackColor = MaterialTheme.colorScheme.primary,
uncheckedThumbColor = MaterialTheme.colorScheme.onSurfaceVariant,
uncheckedTrackColor = MaterialTheme.colorScheme.outlineVariant,
),
Text(
text = title,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface,
fontWeight = FontWeight.Medium,
)
if (!description.isNullOrBlank()) {
Text(
text = description,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
Switch(
checked = checked,
onCheckedChange = onCheckedChange,
colors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colorScheme.onPrimary,
checkedTrackColor = MaterialTheme.colorScheme.primary,
uncheckedThumbColor = MaterialTheme.colorScheme.onSurfaceVariant,
uncheckedTrackColor = MaterialTheme.colorScheme.outlineVariant,
),
)
}
}
@ -303,83 +321,81 @@ internal fun HomescreenCatalogRow(
val horizontalPadding = if (isTablet) 20.dp else 16.dp
val verticalPadding = if (isTablet) 18.dp else 16.dp
SettingsCard(isTablet = isTablet) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = horizontalPadding, vertical = verticalPadding),
verticalArrangement = Arrangement.spacedBy(14.dp),
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = horizontalPadding, vertical = verticalPadding),
verticalArrangement = Arrangement.spacedBy(14.dp),
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Top,
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Top,
Column(
modifier = Modifier
.padding(end = 12.dp)
.widthIn(max = if (isTablet) 560.dp else 260.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
Column(
modifier = Modifier
.padding(end = 12.dp)
.widthIn(max = if (isTablet) 560.dp else 260.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
text = item.displayTitle,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface,
fontWeight = FontWeight.SemiBold,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
)
Text(
text = item.addonName,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
Switch(
checked = item.enabled,
onCheckedChange = onEnabledChange,
colors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colorScheme.onPrimary,
checkedTrackColor = MaterialTheme.colorScheme.primary,
uncheckedThumbColor = MaterialTheme.colorScheme.onSurfaceVariant,
uncheckedTrackColor = MaterialTheme.colorScheme.outlineVariant,
),
Text(
text = item.displayTitle,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface,
fontWeight = FontWeight.SemiBold,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
)
Text(
text = item.addonName,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
OutlinedTextField(
value = item.customTitle,
onValueChange = onTitleChange,
modifier = Modifier.fillMaxWidth(),
singleLine = true,
label = { Text("Display Name") },
placeholder = { Text(item.defaultTitle) },
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.75f),
unfocusedBorderColor = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.42f),
focusedContainerColor = MaterialTheme.colorScheme.surface,
unfocusedContainerColor = MaterialTheme.colorScheme.surface,
disabledContainerColor = MaterialTheme.colorScheme.surface,
Switch(
checked = item.enabled,
onCheckedChange = onEnabledChange,
colors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colorScheme.onPrimary,
checkedTrackColor = MaterialTheme.colorScheme.primary,
uncheckedThumbColor = MaterialTheme.colorScheme.onSurfaceVariant,
uncheckedTrackColor = MaterialTheme.colorScheme.outlineVariant,
),
)
}
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically,
) {
MoveActionChip(
label = "Move Up",
icon = Icons.Rounded.KeyboardArrowUp,
enabled = canMoveUp,
onClick = onMoveUp,
)
MoveActionChip(
label = "Move Down",
icon = Icons.Rounded.KeyboardArrowDown,
enabled = canMoveDown,
onClick = onMoveDown,
)
}
OutlinedTextField(
value = item.customTitle,
onValueChange = onTitleChange,
modifier = Modifier.fillMaxWidth(),
singleLine = true,
label = { Text("Display Name") },
placeholder = { Text(item.defaultTitle) },
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.75f),
unfocusedBorderColor = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.42f),
focusedContainerColor = MaterialTheme.colorScheme.surface,
unfocusedContainerColor = MaterialTheme.colorScheme.surface,
disabledContainerColor = MaterialTheme.colorScheme.surface,
),
)
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically,
) {
MoveActionChip(
label = "Move Up",
icon = Icons.Rounded.KeyboardArrowUp,
enabled = canMoveUp,
onClick = onMoveUp,
)
MoveActionChip(
label = "Move Down",
icon = Icons.Rounded.KeyboardArrowDown,
enabled = canMoveDown,
onClick = onMoveDown,
)
}
}
}

View file

@ -23,27 +23,31 @@ internal fun LazyListScope.settingsRootContent(
title = "GENERAL",
isTablet = isTablet,
) {
SettingsNavigationRow(
title = "Playback",
description = "Control player behavior and viewing defaults.",
icon = Icons.Rounded.PlayArrow,
isTablet = isTablet,
onClick = onPlaybackClick,
)
SettingsNavigationRow(
title = "Appearance",
description = "Tune home presentation and visual preferences.",
icon = Icons.Rounded.Palette,
isTablet = isTablet,
onClick = onAppearanceClick,
)
SettingsNavigationRow(
title = "Content & Discovery",
description = "Manage addons and discovery sources.",
icon = Icons.Rounded.Extension,
isTablet = isTablet,
onClick = onContentDiscoveryClick,
)
SettingsGroup(isTablet = isTablet) {
SettingsNavigationRow(
title = "Playback",
description = "Control player behavior and viewing defaults.",
icon = Icons.Rounded.PlayArrow,
isTablet = isTablet,
onClick = onPlaybackClick,
)
SettingsGroupDivider(isTablet = isTablet)
SettingsNavigationRow(
title = "Appearance",
description = "Tune home presentation and visual preferences.",
icon = Icons.Rounded.Palette,
isTablet = isTablet,
onClick = onAppearanceClick,
)
SettingsGroupDivider(isTablet = isTablet)
SettingsNavigationRow(
title = "Content & Discovery",
description = "Manage addons and discovery sources.",
icon = Icons.Rounded.Extension,
isTablet = isTablet,
onClick = onContentDiscoveryClick,
)
}
}
}
item {
@ -51,29 +55,33 @@ internal fun LazyListScope.settingsRootContent(
title = "ACCOUNT & SYNC",
isTablet = isTablet,
) {
if (onSwitchProfileClick != null) {
SettingsGroup(isTablet = isTablet) {
if (onSwitchProfileClick != null) {
SettingsNavigationRow(
title = "Switch Profile",
description = "Change to a different profile.",
icon = Icons.Rounded.People,
isTablet = isTablet,
onClick = onSwitchProfileClick,
)
SettingsGroupDivider(isTablet = isTablet)
}
SettingsNavigationRow(
title = "Switch Profile",
description = "Change to a different profile.",
icon = Icons.Rounded.People,
title = "Account",
description = "Manage your account, sign out, or delete.",
icon = Icons.Rounded.AccountCircle,
isTablet = isTablet,
onClick = onSwitchProfileClick,
onClick = onAccountClick,
)
SettingsGroupDivider(isTablet = isTablet)
SettingsNavigationRow(
title = "Sync Overview",
description = "View synced data counts per profile.",
icon = Icons.Rounded.Sync,
isTablet = isTablet,
onClick = onSyncOverviewClick,
)
}
SettingsNavigationRow(
title = "Account",
description = "Manage your account, sign out, or delete.",
icon = Icons.Rounded.AccountCircle,
isTablet = isTablet,
onClick = onAccountClick,
)
SettingsNavigationRow(
title = "Sync Overview",
description = "View synced data counts per profile.",
icon = Icons.Rounded.Sync,
isTablet = isTablet,
onClick = onSyncOverviewClick,
)
}
}
}