mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-08-04 10:36:56 +00:00
feat(library): search the cloud library
The Cloud view only had provider and type filters, so finding a specific release in a large debrid account meant scrolling the whole list, even though the item names and file names are already loaded on the client. Adds a local search field under the toolbar that narrows the already loaded items. It matches the item name or any of its file names, since the useful identifier is often in the filename rather than the title, and it stacks on top of the existing provider and type filters.
This commit is contained in:
parent
b1c9d08435
commit
79269f461a
2 changed files with 64 additions and 1 deletions
|
|
@ -1657,6 +1657,7 @@
|
|||
<string name="cloud_library_playable_file_count">%1$d playable files</string>
|
||||
<string name="cloud_library_provider_all">All</string>
|
||||
<string name="cloud_library_refresh">Refresh cloud library</string>
|
||||
<string name="cloud_library_search_label">Search cloud library</string>
|
||||
<string name="cloud_library_select_provider">Select provider</string>
|
||||
<string name="cloud_library_select_type">Select type</string>
|
||||
<string name="cloud_library_status_ready">Ready to play</string>
|
||||
|
|
|
|||
|
|
@ -31,15 +31,18 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.InsertDriveFile
|
||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||
import androidx.compose.material.icons.rounded.Close
|
||||
import androidx.compose.material.icons.rounded.GridView
|
||||
import androidx.compose.material.icons.rounded.PlayArrow
|
||||
import androidx.compose.material.icons.rounded.Refresh
|
||||
import androidx.compose.material.icons.rounded.Search
|
||||
import androidx.compose.material.icons.rounded.ViewAgenda
|
||||
import com.nuvio.app.core.ui.NuvioLoadingIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -132,6 +135,7 @@ fun LibraryScreen(
|
|||
}
|
||||
var selectedProviderId by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
var selectedTypeName by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
var cloudSearchQuery by rememberSaveable { mutableStateOf("") }
|
||||
val selectedType = remember(selectedTypeName) {
|
||||
selectedTypeName?.let { runCatching { CloudLibraryItemType.valueOf(it) }.getOrNull() }
|
||||
}
|
||||
|
|
@ -305,6 +309,11 @@ fun LibraryScreen(
|
|||
selectedProviderId = selectedProviderId,
|
||||
selectedType = selectedType,
|
||||
selectedCloudItemKey = selectedCloudItemKey,
|
||||
searchQuery = cloudSearchQuery,
|
||||
onSearchQueryChange = {
|
||||
cloudSearchQuery = it
|
||||
selectedCloudItemKey = null
|
||||
},
|
||||
onProviderSelected = {
|
||||
selectedProviderId = it
|
||||
selectedTypeName = null
|
||||
|
|
@ -443,6 +452,8 @@ private fun LazyListScope.cloudLibraryContent(
|
|||
selectedProviderId: String?,
|
||||
selectedType: CloudLibraryItemType?,
|
||||
selectedCloudItemKey: String?,
|
||||
searchQuery: String,
|
||||
onSearchQueryChange: (String) -> Unit,
|
||||
onProviderSelected: (String?) -> Unit,
|
||||
onTypeSelected: (CloudLibraryItemType?) -> Unit,
|
||||
onItemSelected: (CloudLibraryItem) -> Unit,
|
||||
|
|
@ -488,8 +499,19 @@ private fun LazyListScope.cloudLibraryContent(
|
|||
.distinct()
|
||||
.sortedBy { type -> type.ordinal }
|
||||
val effectiveSelectedType = selectedType?.takeIf { type -> type in availableTypes }
|
||||
val filteredItems = providerItems
|
||||
val typeFilteredItems = providerItems
|
||||
.filter { item -> effectiveSelectedType == null || item.type == effectiveSelectedType }
|
||||
// Local filter over the already-loaded library. Matches the item name or any of its
|
||||
// file names, since the useful identifier is often in the filename, not the title.
|
||||
val trimmedQuery = searchQuery.trim()
|
||||
val filteredItems = if (trimmedQuery.isEmpty()) {
|
||||
typeFilteredItems
|
||||
} else {
|
||||
typeFilteredItems.filter { item ->
|
||||
item.name.contains(trimmedQuery, ignoreCase = true) ||
|
||||
item.files.any { file -> file.name.contains(trimmedQuery, ignoreCase = true) }
|
||||
}
|
||||
}
|
||||
val selectedItem = filteredItems.firstOrNull { it.stableKey == selectedCloudItemKey }
|
||||
|
||||
if (selectedItem != null) {
|
||||
|
|
@ -514,6 +536,14 @@ private fun LazyListScope.cloudLibraryContent(
|
|||
)
|
||||
}
|
||||
|
||||
item(key = "cloud-library-search") {
|
||||
CloudLibrarySearchField(
|
||||
query = searchQuery,
|
||||
onQueryChange = onSearchQueryChange,
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
)
|
||||
}
|
||||
|
||||
uiState.providers
|
||||
.filter { providerState -> selectedProviderId == null || providerState.providerId == selectedProviderId }
|
||||
.filter { providerState -> !providerState.errorMessage.isNullOrBlank() && providerState.items.isEmpty() }
|
||||
|
|
@ -557,6 +587,38 @@ private fun LazyListScope.cloudLibraryContent(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CloudLibrarySearchField(
|
||||
query: String,
|
||||
onQueryChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = query,
|
||||
onValueChange = onQueryChange,
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
placeholder = { Text(stringResource(Res.string.cloud_library_search_label)) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Search,
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
trailingIcon = {
|
||||
if (query.isNotEmpty()) {
|
||||
IconButton(onClick = { onQueryChange("") }) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Close,
|
||||
contentDescription = stringResource(Res.string.compose_search_clear),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun LazyListScope.cloudLibrarySkeletonItems() {
|
||||
item(key = "cloud-library-skeleton-toolbar") {
|
||||
CloudLibrarySkeletonToolbar(
|
||||
|
|
|
|||
Loading…
Reference in a new issue