Pull to reload, Umplatzierung Search-Button, Album-Artist

This commit is contained in:
2026-04-06 14:59:32 +02:00
parent 56199db301
commit 040917479e
9 changed files with 247 additions and 187 deletions
@@ -6,8 +6,10 @@
//
import SwiftUI
import UIKit
enum LibraryTab: String, CaseIterable {
case albumArtists = "Album Artists"
case artists = "Artists"
case albums = "Albums"
case playlists = "Playlists"
@@ -16,55 +18,28 @@ enum LibraryTab: String, CaseIterable {
struct LibraryView: View {
@Environment(MAService.self) private var service
@State private var selectedTab: LibraryTab = .artists
@State private var showSearch = false
@State private var refreshError: String?
@State private var showError = false
@State private var selectedTab: LibraryTab = .albumArtists
private var isRefreshing: Bool {
switch selectedTab {
case .artists: return service.libraryManager.isLoadingArtists
case .albums: return service.libraryManager.isLoadingAlbums
case .playlists: return service.libraryManager.isLoadingPlaylists
case .radio: return false
}
}
private var lastRefresh: Date? {
switch selectedTab {
case .artists: return service.libraryManager.lastArtistsRefresh
case .albums: return service.libraryManager.lastAlbumsRefresh
case .playlists: return service.libraryManager.lastPlaylistsRefresh
case .radio: return nil
}
init() {
UISegmentedControl.appearance().setTitleTextAttributes(
[.font: UIFont.systemFont(ofSize: 11, weight: .medium)],
for: .normal
)
}
var body: some View {
NavigationStack {
Group {
switch selectedTab {
case .artists: ArtistsView()
case .albums: AlbumsView()
case .playlists: PlaylistsView()
case .radio: RadiosView()
case .albumArtists: ArtistsView(albumArtistsOnly: true)
case .artists: ArtistsView()
case .albums: AlbumsView()
case .playlists: PlaylistsView()
case .radio: RadiosView()
}
}
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
Task { await refresh() }
} label: {
if isRefreshing {
ProgressView()
} else {
Image(systemName: "arrow.clockwise")
}
}
.disabled(isRefreshing || selectedTab == .radio)
.help(lastRefreshLabel)
}
ToolbarItem(placement: .principal) {
Picker("Library", selection: $selectedTab) {
ForEach(LibraryTab.allCases, id: \.self) { tab in
@@ -74,52 +49,8 @@ struct LibraryView: View {
.pickerStyle(.segmented)
.frame(maxWidth: 360)
}
ToolbarItem(placement: .primaryAction) {
Button {
showSearch = true
} label: {
Label("Search", systemImage: "magnifyingglass")
}
}
}
.withMANavigation()
.alert("Refresh Failed", isPresented: $showError) {
Button("OK", role: .cancel) { }
} message: {
if let refreshError { Text(refreshError) }
}
}
// Search presented as isolated sheet with its own NavigationStack.
// This prevents the main LibraryView stack from being affected by
// search-internal navigation (artist/album/playlist detail).
.sheet(isPresented: $showSearch) {
NavigationStack {
SearchView()
}
}
}
// MARK: - Helpers
private var lastRefreshLabel: String {
guard let date = lastRefresh else { return "Never refreshed" }
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .full
return "Last refreshed \(formatter.localizedString(for: date, relativeTo: .now))"
}
private func refresh() async {
do {
switch selectedTab {
case .artists: try await service.libraryManager.loadArtists(refresh: true)
case .albums: try await service.libraryManager.loadAlbums(refresh: true)
case .playlists: try await service.libraryManager.loadPlaylists(refresh: true)
case .radio: break
}
} catch {
refreshError = error.localizedDescription
showError = true
}
}
}