Files
MobileMusicAssistant/Mobile Music Assistant/ViewsLibraryLibraryView.swift
T
2026-04-05 19:44:30 +02:00

131 lines
4.3 KiB
Swift

//
// LibraryView.swift
// Mobile Music Assistant
//
// Created by Sven Hanold on 26.03.26.
//
import SwiftUI
enum LibraryTab: String, CaseIterable {
case artists = "Artists"
case albums = "Albums"
case playlists = "Playlists"
case radio = "Radio"
}
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
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
}
}
var body: some View {
NavigationStack {
Group {
switch selectedTab {
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
Text(tab.rawValue).tag(tab)
}
}
.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
}
}
}
#Preview {
LibraryView()
.environment(MAService())
}