From 800d7160b1f6f16ca1979d0c4eb9ef06a55d45fd Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Thu, 21 May 2026 15:11:42 +0530 Subject: [PATCH] ref: adjust cloud library ui and sorting --- .../composeResources/values/strings.xml | 2 + .../nuvio/app/core/ui/NuvioDropdownChip.kt | 167 +++++++++++++++++ .../app/features/library/LibraryScreen.kt | 134 ++++++++------ .../features/search/SearchDiscoverContent.kt | 169 +----------------- 4 files changed, 253 insertions(+), 219 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioDropdownChip.kt diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index ab3b8ab1..115fbf8a 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -1350,6 +1350,8 @@ %1$d playable files All Refresh cloud library + Select provider + Select type Ready to play All Torrents diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioDropdownChip.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioDropdownChip.kt new file mode 100644 index 00000000..85ea9052 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioDropdownChip.kt @@ -0,0 +1,167 @@ +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.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +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.itemsIndexed +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.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 +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.launch + +data class NuvioDropdownOption( + val key: String, + val label: String, +) + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun NuvioDropdownChip( + title: String, + label: String, + selectedKey: String?, + options: List, + enabled: Boolean = true, + onSelected: (NuvioDropdownOption) -> Unit, + modifier: Modifier = Modifier, +) { + var isSheetVisible by remember { mutableStateOf(false) } + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + val coroutineScope = rememberCoroutineScope() + + Row( + modifier = modifier + .clip(RoundedCornerShape(12.dp)) + .background(MaterialTheme.colorScheme.surface) + .then( + if (enabled) { + Modifier.clickable { isSheetVisible = true } + } else { + Modifier + }, + ) + .padding(horizontal = 12.dp, vertical = 8.dp), + horizontalArrangement = Arrangement.spacedBy(6.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = label, + style = MaterialTheme.typography.labelLarge, + color = if (enabled) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Icon( + imageVector = Icons.Rounded.KeyboardArrowDown, + contentDescription = null, + modifier = Modifier.size(18.dp), + tint = if (enabled) MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.outline, + ) + } + + if (isSheetVisible) { + NuvioDropdownOptionsSheet( + 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 NuvioDropdownOptionsSheet( + title: String, + options: List, + selectedKey: String?, + sheetState: SheetState, + onDismiss: () -> Unit, + onSelected: (NuvioDropdownOption) -> Unit, +) { + NuvioModalBottomSheet( + onDismissRequest = onDismiss, + sheetState = sheetState, + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = nuvioSafeBottomPadding(16.dp)), + ) { + Text( + text = title, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 14.dp), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSurface, + ) + 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() + } + } + } + } + } +} diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryScreen.kt index 244f5f1e..3f196705 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryScreen.kt @@ -9,6 +9,7 @@ import androidx.compose.animation.core.tween import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -19,10 +20,10 @@ 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.lazy.LazyRow import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.InsertDriveFile @@ -57,6 +58,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.nuvio.app.core.i18n.localizedByteUnit import com.nuvio.app.core.network.NetworkCondition import com.nuvio.app.core.network.NetworkStatusRepository +import com.nuvio.app.core.ui.NuvioDropdownChip +import com.nuvio.app.core.ui.NuvioDropdownOption import com.nuvio.app.core.ui.NuvioScreen import com.nuvio.app.core.ui.NuvioNetworkOfflineCard import com.nuvio.app.core.ui.NuvioScreenHeader @@ -204,6 +207,7 @@ fun LibraryScreen( selectedCloudItemKey = selectedCloudItemKey, onProviderSelected = { selectedProviderId = it + selectedTypeName = null selectedCloudItemKey = null }, onTypeSelected = { @@ -337,9 +341,15 @@ private fun LazyListScope.cloudLibraryContent( } else -> { - val filteredItems = uiState.items + val providerItems = uiState.items .filter { item -> selectedProviderId == null || item.providerId == selectedProviderId } - .filter { item -> selectedType == null || item.type == selectedType } + val availableTypes = providerItems + .map { item -> item.type } + .distinct() + .sortedBy { type -> type.ordinal } + val effectiveSelectedType = selectedType?.takeIf { type -> type in availableTypes } + val filteredItems = providerItems + .filter { item -> effectiveSelectedType == null || item.type == effectiveSelectedType } val selectedItem = filteredItems.firstOrNull { it.stableKey == selectedCloudItemKey } if (selectedItem != null) { @@ -355,7 +365,8 @@ private fun LazyListScope.cloudLibraryContent( CloudLibraryToolbar( uiState = uiState, selectedProviderId = selectedProviderId, - selectedType = selectedType, + selectedType = effectiveSelectedType, + availableTypes = availableTypes, onProviderSelected = onProviderSelected, onTypeSelected = onTypeSelected, onRefresh = onRefresh, @@ -445,11 +456,41 @@ private fun CloudLibraryToolbar( uiState: CloudLibraryUiState, selectedProviderId: String?, selectedType: CloudLibraryItemType?, + availableTypes: List, onProviderSelected: (String?) -> Unit, onTypeSelected: (CloudLibraryItemType?) -> Unit, onRefresh: () -> Unit, modifier: Modifier = Modifier, ) { + val providerOptions = buildList { + add(NuvioDropdownOption(key = "", label = stringResource(Res.string.cloud_library_provider_all))) + addAll( + uiState.providers.map { provider -> + NuvioDropdownOption( + key = provider.providerId, + label = provider.providerName, + ) + }, + ) + } + val typeOptions = buildList { + add(NuvioDropdownOption(key = "", label = stringResource(Res.string.cloud_library_type_all))) + addAll( + availableTypes.map { type -> + NuvioDropdownOption( + key = type.name, + label = cloudLibraryTypeLabel(type), + ) + }, + ) + } + val selectedProviderName = uiState.providers + .firstOrNull { provider -> provider.providerId == selectedProviderId } + ?.providerName + ?: stringResource(Res.string.cloud_library_provider_all) + val selectedTypeLabel = selectedType?.let { type -> cloudLibraryTypeLabel(type) } + ?: stringResource(Res.string.cloud_library_type_all) + Column( modifier = modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp), @@ -460,30 +501,35 @@ private fun CloudLibraryToolbar( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), ) { - LazyRow( - modifier = Modifier.weight(1f), + Row( + modifier = Modifier + .weight(1f) + .horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(8.dp), - contentPadding = PaddingValues(end = 8.dp), ) { - item { - LibraryChip( - label = stringResource(Res.string.cloud_library_provider_all), - selected = selectedProviderId == null, - onClick = { onProviderSelected(null) }, - ) - } - items( - items = uiState.providers, - key = { provider -> provider.providerId }, - ) { provider -> - LibraryChip( - label = provider.providerName, - selected = selectedProviderId == provider.providerId, - loading = provider.isLoading, - error = !provider.errorMessage.isNullOrBlank(), - onClick = { onProviderSelected(provider.providerId) }, - ) - } + NuvioDropdownChip( + title = stringResource(Res.string.cloud_library_select_provider), + label = selectedProviderName, + selectedKey = selectedProviderId.orEmpty(), + options = providerOptions, + enabled = providerOptions.size > 1, + onSelected = { option -> + onProviderSelected(option.key.ifBlank { null }) + }, + ) + NuvioDropdownChip( + title = stringResource(Res.string.cloud_library_select_type), + label = selectedTypeLabel, + selectedKey = selectedType?.name.orEmpty(), + options = typeOptions, + enabled = typeOptions.size > 1, + onSelected = { option -> + val type = option.key + .takeIf { it.isNotBlank() } + ?.let(CloudLibraryItemType::valueOf) + onTypeSelected(type) + }, + ) } IconButton(onClick = onRefresh) { Icon( @@ -493,28 +539,6 @@ private fun CloudLibraryToolbar( ) } } - LazyRow( - horizontalArrangement = Arrangement.spacedBy(8.dp), - contentPadding = PaddingValues(end = 16.dp), - ) { - item { - LibraryChip( - label = stringResource(Res.string.cloud_library_type_all), - selected = selectedType == null, - onClick = { onTypeSelected(null) }, - ) - } - items( - items = CloudLibraryItemType.entries, - key = { type -> type.name }, - ) { type -> - LibraryChip( - label = cloudLibraryTypeLabel(type), - selected = selectedType == type, - onClick = { onTypeSelected(type) }, - ) - } - } } } @@ -841,24 +865,16 @@ private fun CloudLibrarySkeletonToolbar( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), ) { - CloudSkeletonBlock(brush = brush, width = 52.dp, height = 34.dp, cornerRadius = 18.dp) - CloudSkeletonBlock(brush = brush, width = 86.dp, height = 34.dp, cornerRadius = 18.dp) + CloudSkeletonBlock(brush = brush, width = 92.dp, height = 34.dp, cornerRadius = 12.dp) + CloudSkeletonBlock(brush = brush, width = 78.dp, height = 34.dp, cornerRadius = 12.dp) CloudSkeletonBlock( brush = brush, modifier = Modifier.weight(1f), height = 34.dp, - cornerRadius = 18.dp, + cornerRadius = 12.dp, ) CloudSkeletonBlock(brush = brush, width = 40.dp, height = 40.dp, cornerRadius = 20.dp) } - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - CloudSkeletonBlock(brush = brush, width = 52.dp, height = 34.dp, cornerRadius = 18.dp) - CloudSkeletonBlock(brush = brush, width = 82.dp, height = 34.dp, cornerRadius = 18.dp) - CloudSkeletonBlock(brush = brush, width = 72.dp, height = 34.dp, cornerRadius = 18.dp) - CloudSkeletonBlock(brush = brush, width = 60.dp, height = 34.dp, cornerRadius = 18.dp) - } } } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchDiscoverContent.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchDiscoverContent.kt index 3f1901f3..2cf53cba 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchDiscoverContent.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/search/SearchDiscoverContent.kt @@ -2,7 +2,6 @@ package com.nuvio.app.features.search import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -13,31 +12,16 @@ 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.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 import androidx.compose.ui.draw.clip @@ -49,12 +33,9 @@ import androidx.compose.ui.unit.sp import coil3.compose.AsyncImage import com.nuvio.app.core.network.NetworkCondition import com.nuvio.app.core.format.formatReleaseDateForDisplay +import com.nuvio.app.core.ui.NuvioDropdownChip +import com.nuvio.app.core.ui.NuvioDropdownOption import com.nuvio.app.core.ui.NuvioNetworkOfflineCard -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.nuvioSafeBottomPadding import com.nuvio.app.core.ui.NuvioPosterWatchedOverlay import com.nuvio.app.core.ui.rememberPosterCardStyleUiState import com.nuvio.app.core.ui.posterCardClickable @@ -62,7 +43,6 @@ 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 import nuvio.composeapp.generated.resources.* import org.jetbrains.compose.resources.stringResource @@ -174,19 +154,19 @@ private fun DiscoverFilterRow( modifier = modifier.horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(8.dp), ) { - DiscoverDropdownChip( + NuvioDropdownChip( title = stringResource(Res.string.discover_select_type), label = state.selectedType?.displayTypeLabel() ?: stringResource(Res.string.discover_type), selectedKey = state.selectedType, - options = state.typeOptions.map { DiscoverOptionItem(key = it, label = it.displayTypeLabel()) }, + options = state.typeOptions.map { NuvioDropdownOption(key = it, label = it.displayTypeLabel()) }, enabled = state.typeOptions.isNotEmpty(), onSelected = { onTypeSelected(it.key) }, ) - DiscoverDropdownChip( + NuvioDropdownChip( title = stringResource(Res.string.discover_select_catalog), label = state.selectedCatalog?.catalogName ?: stringResource(Res.string.discover_catalog), selectedKey = state.selectedCatalogKey, - options = state.catalogOptions.map { option -> DiscoverOptionItem(key = option.key, label = option.catalogName) }, + options = state.catalogOptions.map { option -> NuvioDropdownOption(key = option.key, label = option.catalogName) }, enabled = state.catalogOptions.isNotEmpty(), onSelected = { onCatalogSelected(it.key) }, ) @@ -194,11 +174,11 @@ private fun DiscoverFilterRow( val selectedCatalog = state.selectedCatalog val genreOptions = buildList { if (selectedCatalog?.genreRequired != true) { - add(DiscoverOptionItem(key = "", label = stringResource(Res.string.discover_all_genres))) + add(NuvioDropdownOption(key = "", label = stringResource(Res.string.discover_all_genres))) } - addAll(state.genreOptions.map { genre -> DiscoverOptionItem(key = genre, label = genre) }) + addAll(state.genreOptions.map { genre -> NuvioDropdownOption(key = genre, label = genre) }) } - DiscoverDropdownChip( + NuvioDropdownChip( title = stringResource(Res.string.discover_select_genre), label = state.selectedGenre ?: stringResource(Res.string.discover_all_genres), selectedKey = state.selectedGenre ?: "", @@ -211,132 +191,6 @@ private fun DiscoverFilterRow( } } -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun DiscoverDropdownChip( - title: String, - label: String, - selectedKey: String?, - options: List, - enabled: Boolean, - onSelected: (DiscoverOptionItem) -> Unit, -) { - var isSheetVisible by remember { mutableStateOf(false) } - val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) - val coroutineScope = rememberCoroutineScope() - - Row( - modifier = Modifier - .clip(RoundedCornerShape(12.dp)) - .background(MaterialTheme.colorScheme.surface) - .then( - if (enabled) { - Modifier.clickable { isSheetVisible = true } - } else { - Modifier - }, - ) - .padding(horizontal = 12.dp, vertical = 8.dp), - horizontalArrangement = Arrangement.spacedBy(6.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = label, - style = MaterialTheme.typography.labelLarge, - color = if (enabled) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurfaceVariant, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - Icon( - imageVector = Icons.Rounded.KeyboardArrowDown, - contentDescription = null, - modifier = Modifier.size(18.dp), - 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, - selectedKey: String?, - sheetState: SheetState, - onDismiss: () -> Unit, - onSelected: (DiscoverOptionItem) -> Unit, -) { - NuvioModalBottomSheet( - onDismissRequest = onDismiss, - sheetState = sheetState, - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(bottom = nuvioSafeBottomPadding(16.dp)), - ) { - Text( - text = title, - modifier = Modifier.padding(horizontal = 16.dp, vertical = 14.dp), - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.onSurface, - ) - 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() - } - } - } - } - } -} - @Composable private fun DiscoverGridRow( items: List, @@ -518,11 +372,6 @@ private fun DiscoverEmptyStateCard( ) } -private data class DiscoverOptionItem( - val key: String, - val label: String, -) - @Composable private fun String.displayTypeLabel(): String = when (lowercase()) {