feat: refactor bottom sheet into reusable component

This commit is contained in:
tapframe 2026-04-04 01:36:49 +05:30
parent 5e9b7c07a0
commit 9bb4c341df
6 changed files with 284 additions and 193 deletions

View file

@ -0,0 +1,125 @@
package com.nuvio.app.core.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.SheetState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NuvioModalBottomSheet(
onDismissRequest: () -> Unit,
sheetState: SheetState,
modifier: Modifier = Modifier,
containerColor: Color = MaterialTheme.colorScheme.surface,
contentColor: Color = MaterialTheme.colorScheme.onSurface,
shape: Shape = RoundedCornerShape(topStart = 28.dp, topEnd = 28.dp),
showDragHandle: Boolean = true,
content: @Composable ColumnScope.() -> Unit,
) {
ModalBottomSheet(
onDismissRequest = onDismissRequest,
sheetState = sheetState,
modifier = modifier,
containerColor = containerColor,
contentColor = contentColor,
shape = shape,
dragHandle = if (showDragHandle) {
{ NuvioBottomSheetDragHandle() }
} else {
null
},
content = content,
)
}
@Composable
fun NuvioBottomSheetDivider(
modifier: Modifier = Modifier,
) {
HorizontalDivider(
modifier = modifier,
color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.6f),
)
}
@Composable
fun NuvioBottomSheetActionRow(
title: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
icon: ImageVector? = null,
trailingContent: (@Composable RowScope.() -> Unit)? = null,
) {
Row(
modifier = modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(horizontal = 16.dp, vertical = 16.dp),
horizontalArrangement = Arrangement.spacedBy(14.dp),
verticalAlignment = Alignment.CenterVertically,
) {
if (icon != null) {
Icon(
imageVector = icon,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(22.dp),
)
}
Text(
text = title,
modifier = Modifier.weight(1f),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
trailingContent?.invoke(this)
}
}
@OptIn(ExperimentalMaterial3Api::class)
suspend fun dismissNuvioBottomSheet(
sheetState: SheetState,
onDismiss: () -> Unit,
) {
if (sheetState.isVisible) {
sheetState.hide()
}
onDismiss()
}
@Composable
private fun NuvioBottomSheetDragHandle() {
Box(
modifier = Modifier
.padding(top = 10.dp, bottom = 6.dp)
.size(width = 54.dp, height = 5.dp)
.clip(RoundedCornerShape(999.dp))
.background(MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.65f)),
)
}

View file

@ -4,14 +4,11 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
@ -23,11 +20,8 @@ import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.CheckCircleOutline
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.SheetState
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
@ -58,26 +52,16 @@ fun NuvioPosterActionSheet(
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val coroutineScope = rememberCoroutineScope()
ModalBottomSheet(
NuvioModalBottomSheet(
onDismissRequest = {
coroutineScope.launch {
dismissPosterActionSheet(
dismissNuvioBottomSheet(
sheetState = sheetState,
onDismiss = onDismiss,
)
}
},
sheetState = sheetState,
containerColor = MaterialTheme.colorScheme.surface,
dragHandle = {
Box(
modifier = Modifier
.padding(top = 10.dp, bottom = 6.dp)
.size(width = 54.dp, height = 5.dp)
.clip(RoundedCornerShape(999.dp))
.background(MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.65f)),
)
},
) {
Column(
modifier = Modifier
@ -85,28 +69,28 @@ fun NuvioPosterActionSheet(
.padding(bottom = 16.dp + nuvioPlatformExtraBottomPadding),
) {
PosterSheetHeader(item = item)
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.6f))
PosterSheetActionRow(
NuvioBottomSheetDivider()
NuvioBottomSheetActionRow(
icon = if (isSaved) Icons.Default.Bookmark else Icons.Default.BookmarkBorder,
title = if (isSaved) "Remove from Library" else "Add to Library",
onClick = {
onToggleLibrary()
coroutineScope.launch {
dismissPosterActionSheet(
dismissNuvioBottomSheet(
sheetState = sheetState,
onDismiss = onDismiss,
)
}
},
)
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.6f))
PosterSheetActionRow(
NuvioBottomSheetDivider()
NuvioBottomSheetActionRow(
icon = if (isWatched) Icons.Default.CheckCircle else Icons.Default.CheckCircleOutline,
title = if (isWatched) "Mark as Unwatched" else "Mark as Watched",
onClick = {
onToggleWatched()
coroutineScope.launch {
dismissPosterActionSheet(
dismissNuvioBottomSheet(
sheetState = sheetState,
onDismiss = onDismiss,
)
@ -117,17 +101,6 @@ fun NuvioPosterActionSheet(
}
}
@OptIn(ExperimentalMaterial3Api::class)
private suspend fun dismissPosterActionSheet(
sheetState: SheetState,
onDismiss: () -> Unit,
) {
if (sheetState.isVisible) {
sheetState.hide()
}
onDismiss()
}
@Composable
fun NuvioWatchedBadge(
modifier: Modifier = Modifier,
@ -226,35 +199,3 @@ private fun PosterSheetHeader(
}
}
@Composable
private fun PosterSheetActionRow(
icon: androidx.compose.ui.graphics.vector.ImageVector,
title: String,
onClick: () -> Unit,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(horizontal = 16.dp, vertical = 16.dp),
horizontalArrangement = Arrangement.spacedBy(14.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(22.dp),
)
Column {
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
Spacer(modifier = Modifier.weight(1f))
}
}

View file

@ -23,7 +23,6 @@ import androidx.compose.material.icons.automirrored.rounded.KeyboardArrowRight
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
@ -35,6 +34,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.nuvio.app.core.ui.NuvioModalBottomSheet
import com.nuvio.app.features.trakt.TraktCommentReview
@OptIn(ExperimentalMaterial3Api::class)
@ -56,10 +56,9 @@ fun CommentDetailSheet(
scrollState.scrollTo(0)
}
ModalBottomSheet(
NuvioModalBottomSheet(
onDismissRequest = onDismiss,
sheetState = sheetState,
containerColor = MaterialTheme.colorScheme.surface,
contentColor = MaterialTheme.colorScheme.onSurface,
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp),
) {

View file

@ -1,12 +1,10 @@
package com.nuvio.app.features.details.components
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@ -16,21 +14,19 @@ import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.DoneAll
import androidx.compose.material.icons.filled.PlaylistAddCheckCircle
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.SheetState
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.nuvio.app.core.ui.NuvioBottomSheetActionRow
import com.nuvio.app.core.ui.NuvioBottomSheetDivider
import com.nuvio.app.core.ui.NuvioModalBottomSheet
import com.nuvio.app.core.ui.dismissNuvioBottomSheet
import com.nuvio.app.features.details.MetaVideo
import com.nuvio.app.core.ui.nuvioPlatformExtraBottomPadding
import kotlinx.coroutines.launch
@ -52,25 +48,13 @@ fun EpisodeWatchedActionSheet(
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val coroutineScope = rememberCoroutineScope()
ModalBottomSheet(
NuvioModalBottomSheet(
onDismissRequest = {
coroutineScope.launch {
dismissEpisodeActionSheet(sheetState = sheetState, onDismiss = onDismiss)
dismissNuvioBottomSheet(sheetState = sheetState, onDismiss = onDismiss)
}
},
sheetState = sheetState,
containerColor = MaterialTheme.colorScheme.surface,
dragHandle = {
Box(
modifier = Modifier
.padding(top = 10.dp, bottom = 6.dp)
.size(width = 54.dp, height = 5.dp)
.background(
color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.65f),
shape = RoundedCornerShape(999.dp),
),
)
},
) {
Column(
modifier = Modifier
@ -81,20 +65,20 @@ fun EpisodeWatchedActionSheet(
episode = episode,
seasonLabel = seasonLabel,
)
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.6f))
EpisodeActionSheetRow(
NuvioBottomSheetDivider()
NuvioBottomSheetActionRow(
icon = Icons.Default.CheckCircle,
title = if (isEpisodeWatched) "Mark as unwatched" else "Mark as watched",
onClick = {
onToggleWatched()
coroutineScope.launch {
dismissEpisodeActionSheet(sheetState = sheetState, onDismiss = onDismiss)
dismissNuvioBottomSheet(sheetState = sheetState, onDismiss = onDismiss)
}
},
)
if (canMarkPreviousEpisodes) {
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.6f))
EpisodeActionSheetRow(
NuvioBottomSheetDivider()
NuvioBottomSheetActionRow(
icon = Icons.Default.DoneAll,
title = if (arePreviousEpisodesWatched) {
"Mark previous as unwatched"
@ -104,13 +88,13 @@ fun EpisodeWatchedActionSheet(
onClick = {
onTogglePreviousWatched()
coroutineScope.launch {
dismissEpisodeActionSheet(sheetState = sheetState, onDismiss = onDismiss)
dismissNuvioBottomSheet(sheetState = sheetState, onDismiss = onDismiss)
}
},
)
}
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.6f))
EpisodeActionSheetRow(
NuvioBottomSheetDivider()
NuvioBottomSheetActionRow(
icon = Icons.Default.PlaylistAddCheckCircle,
title = if (isSeasonWatched) {
"Mark $seasonLabel as unwatched"
@ -120,7 +104,7 @@ fun EpisodeWatchedActionSheet(
onClick = {
onToggleSeasonWatched()
coroutineScope.launch {
dismissEpisodeActionSheet(sheetState = sheetState, onDismiss = onDismiss)
dismissNuvioBottomSheet(sheetState = sheetState, onDismiss = onDismiss)
}
},
)
@ -128,17 +112,6 @@ fun EpisodeWatchedActionSheet(
}
}
@OptIn(ExperimentalMaterial3Api::class)
private suspend fun dismissEpisodeActionSheet(
sheetState: SheetState,
onDismiss: () -> Unit,
) {
if (sheetState.isVisible) {
sheetState.hide()
}
onDismiss()
}
@Composable
private fun EpisodeActionSheetHeader(
episode: MetaVideo,
@ -174,33 +147,3 @@ private fun EpisodeActionSheetHeader(
}
}
@Composable
private fun EpisodeActionSheetRow(
icon: ImageVector,
title: String,
onClick: () -> Unit,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(horizontal = 16.dp, vertical = 16.dp),
horizontalArrangement = Arrangement.spacedBy(14.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(22.dp),
)
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Spacer(modifier = Modifier.weight(1f))
}
}

View file

@ -13,11 +13,9 @@ import androidx.compose.material.icons.rounded.Close
import androidx.compose.material.icons.Icons
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberModalBottomSheetState
@ -33,6 +31,9 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import com.nuvio.app.core.ui.NuvioBottomSheetDivider
import com.nuvio.app.core.ui.NuvioModalBottomSheet
import com.nuvio.app.core.ui.dismissNuvioBottomSheet
import com.nuvio.app.core.ui.nuvioPlatformExtraBottomPadding
import com.nuvio.app.features.player.PlatformPlayerSurface
import com.nuvio.app.features.player.PlayerResizeMode
@ -74,17 +75,13 @@ fun TrailerPlayerPopup(
val dismissSheet: () -> Unit = {
coroutineScope.launch {
if (sheetState.isVisible) {
sheetState.hide()
}
onDismiss()
dismissNuvioBottomSheet(sheetState = sheetState, onDismiss = onDismiss)
}
}
ModalBottomSheet(
NuvioModalBottomSheet(
onDismissRequest = dismissSheet,
sheetState = sheetState,
containerColor = MaterialTheme.colorScheme.surface,
) {
Column(
modifier = Modifier
@ -112,7 +109,6 @@ fun TrailerPlayerPopup(
if (headerSubtitle.isNotBlank()) {
Text(
text = headerSubtitle,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
@ -129,7 +125,7 @@ fun TrailerPlayerPopup(
}
}
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.55f))
NuvioBottomSheetDivider()
Box(
modifier = Modifier

View file

@ -13,24 +13,30 @@ import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Check
import androidx.compose.material.icons.rounded.KeyboardArrowDown
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SheetState
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -43,11 +49,17 @@ import androidx.compose.ui.unit.sp
import coil3.compose.AsyncImage
import com.nuvio.app.core.format.formatReleaseDateForDisplay
import com.nuvio.app.core.ui.NuvioAnimatedWatchedBadge
import com.nuvio.app.core.ui.NuvioBottomSheetActionRow
import com.nuvio.app.core.ui.NuvioBottomSheetDivider
import com.nuvio.app.core.ui.NuvioModalBottomSheet
import com.nuvio.app.core.ui.dismissNuvioBottomSheet
import com.nuvio.app.core.ui.nuvioPlatformExtraBottomPadding
import com.nuvio.app.core.ui.posterCardClickable
import com.nuvio.app.features.home.MetaPreview
import com.nuvio.app.features.home.PosterShape
import com.nuvio.app.features.home.components.HomeEmptyStateCard
import com.nuvio.app.features.watching.application.WatchingState
import kotlinx.coroutines.launch
internal fun LazyListScope.discoverContent(
state: DiscoverUiState,
@ -150,13 +162,17 @@ private fun DiscoverFilterRow(
horizontalArrangement = Arrangement.spacedBy(10.dp),
) {
DiscoverDropdownChip(
title = "Select Type",
label = state.selectedType?.displayTypeLabel() ?: "Type",
selectedKey = state.selectedType,
options = state.typeOptions.map { DiscoverOptionItem(key = it, label = it.displayTypeLabel()) },
enabled = state.typeOptions.isNotEmpty(),
onSelected = { onTypeSelected(it.key) },
)
DiscoverDropdownChip(
title = "Select Catalog",
label = state.selectedCatalog?.catalogName ?: "Catalog",
selectedKey = state.selectedCatalogKey,
options = state.catalogOptions.map { option -> DiscoverOptionItem(key = option.key, label = option.catalogName) },
enabled = state.catalogOptions.isNotEmpty(),
onSelected = { onCatalogSelected(it.key) },
@ -170,7 +186,9 @@ private fun DiscoverFilterRow(
addAll(state.genreOptions.map { genre -> DiscoverOptionItem(key = genre, label = genre) })
}
DiscoverDropdownChip(
title = "Select Genre",
label = state.selectedGenre ?: "All Genres",
selectedKey = state.selectedGenre ?: "",
options = genreOptions,
enabled = genreOptions.size > 1 || selectedCatalog?.genreRequired == true,
onSelected = { option ->
@ -180,57 +198,126 @@ private fun DiscoverFilterRow(
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun DiscoverDropdownChip(
title: String,
label: String,
selectedKey: String?,
options: List<DiscoverOptionItem>,
enabled: Boolean,
onSelected: (DiscoverOptionItem) -> Unit,
) {
var expanded by remember { mutableStateOf(false) }
var isSheetVisible by remember { mutableStateOf(false) }
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val coroutineScope = rememberCoroutineScope()
Box {
Row(
Row(
modifier = Modifier
.clip(RoundedCornerShape(20.dp))
.background(MaterialTheme.colorScheme.surface)
.then(
if (enabled) {
Modifier.clickable { isSheetVisible = true }
} else {
Modifier
},
)
.padding(horizontal = 18.dp, vertical = 14.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = label,
style = MaterialTheme.typography.titleMedium,
color = if (enabled) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Icon(
imageVector = Icons.Rounded.KeyboardArrowDown,
contentDescription = null,
tint = if (enabled) MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.outline,
)
}
if (isSheetVisible) {
DiscoverOptionsSheet(
title = title,
options = options,
selectedKey = selectedKey,
sheetState = sheetState,
onDismiss = {
coroutineScope.launch {
dismissNuvioBottomSheet(
sheetState = sheetState,
onDismiss = { isSheetVisible = false },
)
}
},
onSelected = { option ->
onSelected(option)
coroutineScope.launch {
dismissNuvioBottomSheet(
sheetState = sheetState,
onDismiss = { isSheetVisible = false },
)
}
},
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun DiscoverOptionsSheet(
title: String,
options: List<DiscoverOptionItem>,
selectedKey: String?,
sheetState: SheetState,
onDismiss: () -> Unit,
onSelected: (DiscoverOptionItem) -> Unit,
) {
NuvioModalBottomSheet(
onDismissRequest = onDismiss,
sheetState = sheetState,
) {
Column(
modifier = Modifier
.clip(RoundedCornerShape(20.dp))
.background(MaterialTheme.colorScheme.surface)
.then(
if (enabled) {
Modifier.clickable { expanded = true }
} else {
Modifier
},
)
.padding(horizontal = 18.dp, vertical = 14.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
.fillMaxWidth()
.padding(bottom = 16.dp + nuvioPlatformExtraBottomPadding),
) {
Text(
text = label,
style = MaterialTheme.typography.titleMedium,
color = if (enabled) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
text = title,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 14.dp),
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onSurface,
)
Icon(
imageVector = Icons.Rounded.KeyboardArrowDown,
contentDescription = null,
tint = if (enabled) MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.outline,
)
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
) {
options.forEach { option ->
DropdownMenuItem(
text = { Text(option.label) },
onClick = {
expanded = false
onSelected(option)
},
)
NuvioBottomSheetDivider()
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.heightIn(max = 420.dp),
) {
itemsIndexed(options) { index, option ->
NuvioBottomSheetActionRow(
title = option.label,
onClick = { onSelected(option) },
trailingContent = {
if (option.key == selectedKey) {
Icon(
imageVector = Icons.Rounded.Check,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(20.dp),
)
}
},
)
if (index < options.lastIndex) {
NuvioBottomSheetDivider()
}
}
}
}
}