// // MainTabView.swift // Mobile Music Assistant // // Created by Sven Hanold on 26.03.26. // import SwiftUI struct MainTabView: View { @Environment(MAService.self) private var service var body: some View { TabView { Tab("Players", systemImage: "speaker.wave.2.fill") { PlayerListView() } Tab("Library", systemImage: "music.note.list") { LibraryView() } Tab("Settings", systemImage: "gear") { SettingsView() } } .task { // Start listening to player events when main view appears service.playerManager.startListening() } .onDisappear { // Stop listening when view disappears service.playerManager.stopListening() } } } // MARK: - Placeholder Views (to be implemented in Phase 2+) struct PlayerListView: View { @Environment(MAService.self) private var service @State private var isLoading = false @State private var errorMessage: String? private var players: [MAPlayer] { Array(service.playerManager.players.values).sorted { $0.name < $1.name } } var body: some View { NavigationStack { Group { if isLoading { ProgressView() } else if let errorMessage { ContentUnavailableView( "Error Loading Players", systemImage: "exclamationmark.triangle", description: Text(errorMessage) ) } else if players.isEmpty { ContentUnavailableView( "No Players Found", systemImage: "speaker.slash", description: Text("Make sure your Music Assistant server has configured players") ) } else { List(players) { player in NavigationLink(value: player.playerId) { PlayerRow(player: player) } } .navigationDestination(for: String.self) { playerId in PlayerView(playerId: playerId) } } } .navigationTitle("Players") .toolbar { ToolbarItem(placement: .primaryAction) { Button { Task { await loadPlayers() } } label: { Label("Refresh", systemImage: "arrow.clockwise") } } } .task { await loadPlayers() } } } private func loadPlayers() async { print("🔵 PlayerListView: Starting to load players...") isLoading = true errorMessage = nil do { print("🔵 PlayerListView: Calling playerManager.loadPlayers()") try await service.playerManager.loadPlayers() print("✅ PlayerListView: Successfully loaded \(players.count) players") } catch { print("❌ PlayerListView: Failed to load players: \(error)") errorMessage = error.localizedDescription } isLoading = false } } struct PlayerRow: View { @Environment(MAService.self) private var service let player: MAPlayer var body: some View { HStack(spacing: 12) { // Album Art Thumbnail if let item = player.currentItem, let mediaItem = item.mediaItem, let imageUrl = mediaItem.imageUrl { let coverURL = service.imageProxyURL(path: imageUrl, size: 64) CachedAsyncImage(url: coverURL) { image in image .resizable() .aspectRatio(contentMode: .fill) } placeholder: { Rectangle() .fill(Color.gray.opacity(0.2)) } .frame(width: 48, height: 48) .clipShape(RoundedRectangle(cornerRadius: 6)) } else { RoundedRectangle(cornerRadius: 6) .fill(Color.gray.opacity(0.2)) .frame(width: 48, height: 48) .overlay { Image(systemName: "music.note") .foregroundStyle(.secondary) .font(.caption) } } // Player Info VStack(alignment: .leading, spacing: 4) { Text(player.name) .font(.headline) HStack(spacing: 6) { Image(systemName: stateIcon) .foregroundStyle(stateColor) .font(.caption) Text(player.state.rawValue.capitalized) .font(.caption) .foregroundStyle(.secondary) if let item = player.currentItem { Text("• \(item.name)") .font(.caption) .foregroundStyle(.secondary) .lineLimit(1) } } } Spacer() // Volume Indicator if player.available { VStack(spacing: 2) { Image(systemName: "speaker.wave.2.fill") .font(.caption) .foregroundStyle(.secondary) Text("\(player.volume)%") .font(.caption2) .foregroundStyle(.secondary) } } } .padding(.vertical, 4) } private var stateIcon: String { switch player.state { case .playing: return "play.circle.fill" case .paused: return "pause.circle.fill" case .idle: return "stop.circle" case .off: return "power.circle" } } private var stateColor: Color { switch player.state { case .playing: return .green case .paused: return .orange case .idle: return .gray case .off: return .red } } } // Removed - Now using dedicated PlayerView.swift file // Removed - Now using dedicated LibraryView.swift file struct SettingsView: View { @Environment(MAService.self) private var service var body: some View { NavigationStack { Form { Section { if let serverURL = service.authManager.serverURL { LabeledContent("Server", value: serverURL.absoluteString) } LabeledContent("Status") { HStack { Circle() .fill(service.isConnected ? .green : .red) .frame(width: 8, height: 8) Text(service.isConnected ? "Connected" : "Disconnected") } } } Section { Button(role: .destructive) { service.disconnect() service.authManager.logout() } label: { Label("Disconnect", systemImage: "arrow.right.square") } } } .navigationTitle("Settings") } } } #Preview { MainTabView() .environment(MAService()) }