import SwiftData import SwiftUI /// Root view — Playlists as the main screen, Library and Settings accessible from toolbar. struct ContentView: View { @Environment(PlayerViewModel.self) private var playerVM @Environment(PlaylistViewModel.self) private var playlistVM @EnvironmentObject private var libraryManager: LibraryManager @EnvironmentObject private var theme: AppTheme @EnvironmentObject private var syncManager: SyncManager @Environment(\.modelContext) private var modelContext @State private var showLibrary = false @State private var showSettings = false @State private var showCloudBrowser = false @Query(sort: \Playlist.dateModified, order: .reverse) private var playlists: [Playlist] var body: some View { VStack(spacing: 0) { // Main content: Playlists PlaylistListView() // Mini player at bottom if playerVM.currentTrack != nil || playerVM.isCloudPlayback { MiniPlayerView() } } .accessibilityIdentifier("ContentView") .overlay(alignment: .bottom) { // Undo queue replacement toast if playerVM.showUndoToast { HStack(spacing: 8) { Text(playerVM.undoMessage) .font(.subheadline) .foregroundStyle(theme.primaryText) Button("Undo") { playerVM.undoQueueReplacement() } .font(.subheadline.bold()) .foregroundStyle(theme.accent) } .padding(.horizontal, 16) .padding(.vertical, 10) .background(theme.cardBackground.opacity(0.95)) .clipShape(Capsule()) .shadow(radius: 8) .padding(.bottom, (playerVM.currentTrack != nil || playerVM.isCloudPlayback) ? 90 : 60) .transition(.move(edge: .bottom).combined(with: .opacity)) .animation(.easeInOut(duration: 0.3), value: playerVM.showUndoToast) } } .overlay(alignment: .bottom) { // Status toast if let status = playlistVM.statusMessage { HStack(spacing: 8) { Image(systemName: "checkmark.circle.fill") .foregroundStyle(theme.accent) Text(status) .font(.subheadline) .foregroundStyle(theme.primaryText) } .padding(.horizontal, 16) .padding(.vertical, 10) .background(theme.cardBackground.opacity(0.95)) .clipShape(Capsule()) .shadow(radius: 8) .padding(.bottom, (playerVM.currentTrack != nil || playerVM.isCloudPlayback) ? 90 : 60) .transition(.move(edge: .bottom).combined(with: .opacity)) .animation(.easeInOut(duration: 0.3), value: playlistVM.statusMessage) } } .onAppear { libraryManager.setModelContext(modelContext) libraryManager.fixBadPathsIfNeeded() playlistVM.restoreTargetPlaylist(from: playlists) playerVM.modelContext = modelContext } .onChange(of: playlists.count) { _, newCount in guard newCount > 0 else { return } Task { try? await Task.sleep(for: .seconds(2)) syncManager.exportPlaylists(playlists) } } .fullScreenCover(isPresented: Binding( get: { playerVM.showNowPlaying }, set: { playerVM.showNowPlaying = $0 } )) { NowPlayingView() .environmentObject(theme) .environmentObject(libraryManager) } .sheet(isPresented: $showLibrary) { NavigationStack { LibraryView() } .environmentObject(theme) } .sheet(isPresented: $showSettings) { SettingsView() .environmentObject(theme) .environmentObject(syncManager) } .sheet(isPresented: $showCloudBrowser) { CloudBrowserView() .environmentObject(theme) } .sheet(isPresented: Bindable(playerVM).showQueue) { QueueView() .environment(playerVM) .environmentObject(theme) } } }