// // SettingsView.swift // iris // // Settings UI for music source selection and Spotify connection. // import MusicKit import SwiftUI @available(iOS 16.0, *) struct SettingsView: View { @EnvironmentObject private var orchestrator: ContextOrchestrator @EnvironmentObject private var spotifyAuth: SpotifyAuthManager @State private var isConfigExpanded: Bool = false @State private var clientIdInput: String = "" var body: some View { NavigationStack { List { Section("Music Source") { Picker("Source", selection: Binding( get: { orchestrator.musicSource }, set: { newValue in if newValue == .spotify && !spotifyAuth.isConnected { return } orchestrator.musicSource = newValue } )) { ForEach(MusicSource.allCases, id: \.self) { source in Text(source.displayName).tag(source) } } .pickerStyle(.segmented) if !spotifyAuth.isConnected { Text("Connect Spotify below to enable it as a source") .font(.caption) .foregroundStyle(.secondary) } } Section { DisclosureGroup("Configuration", isExpanded: $isConfigExpanded) { TextField("Client ID", text: $clientIdInput) .textInputAutocapitalization(.never) .autocorrectionDisabled() .font(.system(.body, design: .monospaced)) Text("Get your Client ID from developer.spotify.com/dashboard") .font(.caption) .foregroundStyle(.secondary) Button("Save") { spotifyAuth.setClientId(clientIdInput) isConfigExpanded = false } .disabled(clientIdInput.trimmingCharacters(in: .whitespaces).isEmpty) } .onAppear { clientIdInput = spotifyAuth.clientId } if spotifyAuth.isConnected { HStack { Label("Connected", systemImage: "checkmark.circle.fill") .foregroundColor(.green) Spacer() } Button("Disconnect", role: .destructive) { spotifyAuth.disconnect() if orchestrator.musicSource == .spotify { orchestrator.musicSource = .appleMusic } } } else if spotifyAuth.isConfigured { Button { spotifyAuth.startAuth() } label: { HStack { Label("Connect to Spotify", systemImage: "link") Spacer() if spotifyAuth.isAuthenticating { ProgressView() } } } .disabled(spotifyAuth.isAuthenticating) } else { Text("Enter your Client ID above to connect") .font(.callout) .foregroundStyle(.secondary) } if let error = spotifyAuth.error { Text(error) .font(.caption) .foregroundColor(.red) } } header: { Text("Spotify") } footer: { if !spotifyAuth.isConnected && spotifyAuth.isConfigured { Text("Connect your Spotify account to display current track on Glass.") } } Section { HStack { Text("Authorization") Spacer() Text(authStatusText(orchestrator.musicAuthorization)) .foregroundStyle(.secondary) } } header: { Text("Apple Music") } Section { if let nowPlayingInfo = currentNowPlaying { VStack(alignment: .leading, spacing: 4) { Text(nowPlayingInfo.title) .font(.headline) if let artist = nowPlayingInfo.artist { Text(artist) .font(.subheadline) .foregroundStyle(.secondary) } } } else { Text("Nothing playing") .foregroundStyle(.secondary) } } header: { Text("Now Playing") } footer: { Text("Source: \(orchestrator.musicSource.displayName)") } } .navigationTitle("Settings") } } private var currentNowPlaying: (title: String, artist: String?)? { switch orchestrator.musicSource { case .appleMusic: guard let np = orchestrator.nowPlaying else { return nil } return (np.title, np.artist) case .spotify: guard let np = orchestrator.spotifyNowPlaying else { return nil } return (np.title, np.artist) } } private func authStatusText(_ status: MusicAuthorization.Status) -> String { switch status { case .notDetermined: return "Not Determined" case .denied: return "Denied" case .restricted: return "Restricted" case .authorized: return "Authorized" @unknown default: return "Unknown" } } } @available(iOS 16.0, *) struct SettingsView_Previews: PreviewProvider { static var previews: some View { let ble = BlePeripheralManager() let spotifyAuth = SpotifyAuthManager() let orchestrator = ContextOrchestrator(ble: ble, spotifyAuth: spotifyAuth) SettingsView() .environmentObject(orchestrator) .environmentObject(spotifyAuth) } }