Add search to playlists list view

This commit is contained in:
Arkadiusz Fal
2026-08-01 09:42:51 +02:00
parent 908a916f76
commit 3f3709a37e
2 changed files with 48 additions and 6 deletions

View File

@@ -8240,6 +8240,16 @@
} }
} }
}, },
"playlists.search.placeholder" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Search playlists"
}
}
}
},
"playlists.title" : { "playlists.title" : {
"comment" : "Title for playlists tab in instance browse", "comment" : "Title for playlists tab in instance browse",
"localizations" : { "localizations" : {

View File

@@ -10,6 +10,7 @@ import SwiftUI
struct PlaylistsListView: View { struct PlaylistsListView: View {
@Environment(\.appEnvironment) private var appEnvironment @Environment(\.appEnvironment) private var appEnvironment
@State private var playlists: [LocalPlaylist] = [] @State private var playlists: [LocalPlaylist] = []
@State private var searchText = ""
@State private var showingNewPlaylist = false @State private var showingNewPlaylist = false
@State private var playlistToEdit: LocalPlaylist? @State private var playlistToEdit: LocalPlaylist?
#if os(tvOS) #if os(tvOS)
@@ -23,12 +24,22 @@ struct PlaylistsListView: View {
appEnvironment?.settingsManager.listStyle ?? .inset appEnvironment?.settingsManager.listStyle ?? .inset
} }
/// Playlists filtered by search.
private var filteredPlaylists: [LocalPlaylist] {
guard !searchText.isEmpty else { return playlists }
let query = searchText.lowercased()
return playlists.filter { playlist in
playlist.title.lowercased().contains(query) ||
(playlist.playlistDescription?.lowercased().contains(query) ?? false)
}
}
var body: some View { var body: some View {
Group { Group {
#if os(tvOS) #if os(tvOS)
tvOSContent tvOSContent
#else #else
if playlists.isEmpty { if filteredPlaylists.isEmpty {
emptyView emptyView
} else { } else {
listContent listContent
@@ -38,7 +49,15 @@ struct PlaylistsListView: View {
#if !os(tvOS) #if !os(tvOS)
.navigationTitle(String(localized: "home.playlists.title")) .navigationTitle(String(localized: "home.playlists.title"))
.toolbarTitleDisplayMode(.inlineLarge) .toolbarTitleDisplayMode(.inlineLarge)
.searchable(text: $searchText, prompt: Text(String(localized: "playlists.search.placeholder")))
.toolbar { .toolbar {
#if os(macOS)
// Pin the trailing group (search field + toolbar buttons) to the right edge,
// matching the global Search view.
if #available(macOS 26, *) {
ToolbarSpacer(.flexible, placement: .primaryAction)
}
#endif
ToolbarItem(placement: .primaryAction) { ToolbarItem(placement: .primaryAction) {
Button { Button {
showingNewPlaylist = true showingNewPlaylist = true
@@ -73,8 +92,10 @@ struct PlaylistsListView: View {
private var tvOSContent: some View { private var tvOSContent: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
HStack { HStack(spacing: 24) {
Spacer() TextField(String(localized: "playlists.search.placeholder"), text: $searchText)
.textFieldStyle(.plain)
Button { Button {
showingNewPlaylist = true showingNewPlaylist = true
} label: { } label: {
@@ -84,9 +105,10 @@ struct PlaylistsListView: View {
.focusSection() .focusSection()
.padding(.horizontal, 48) .padding(.horizontal, 48)
.padding(.top, 20) .padding(.top, 20)
.padding(.bottom, 20)
Group { Group {
if playlists.isEmpty { if filteredPlaylists.isEmpty {
emptyView emptyView
} else { } else {
listContent listContent
@@ -106,7 +128,17 @@ struct PlaylistsListView: View {
// MARK: - Empty View // MARK: - Empty View
@ViewBuilder
private var emptyView: some View { private var emptyView: some View {
if !searchText.isEmpty {
ContentUnavailableView.search(text: searchText)
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else {
noPlaylistsView
}
}
private var noPlaylistsView: some View {
ContentUnavailableView { ContentUnavailableView {
Label(String(localized: "home.playlists.title"), systemImage: "list.bullet.rectangle") Label(String(localized: "home.playlists.title"), systemImage: "list.bullet.rectangle")
} description: { } description: {
@@ -129,9 +161,9 @@ struct PlaylistsListView: View {
Spacer() Spacer()
.frame(height: 16) .frame(height: 16)
} content: { } content: {
ForEach(Array(playlists.enumerated()), id: \.element.id) { index, playlist in ForEach(Array(filteredPlaylists.enumerated()), id: \.element.id) { index, playlist in
VideoListRow( VideoListRow(
isLast: index == playlists.count - 1, isLast: index == filteredPlaylists.count - 1,
rowStyle: .regular, rowStyle: .regular,
listStyle: listStyle, listStyle: listStyle,
contentWidth: 80 // PlaylistRowView thumbnail width contentWidth: 80 // PlaylistRowView thumbnail width