Add UI to configure Spotify Client ID in Settings
- Add expandable Configuration section in Spotify settings - Store Client ID in UserDefaults - Show Connect button only when Client ID is configured - Add helper text pointing to Spotify Developer Dashboard 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,6 @@ import Foundation
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
enum SpotifyConfig {
|
enum SpotifyConfig {
|
||||||
static let clientId = "YOUR_SPOTIFY_CLIENT_ID"
|
|
||||||
static let redirectUri = "iris-spotify-auth://callback"
|
static let redirectUri = "iris-spotify-auth://callback"
|
||||||
static let scopes = "user-read-playback-state user-read-currently-playing"
|
static let scopes = "user-read-playback-state user-read-currently-playing"
|
||||||
static let authUrl = "https://accounts.spotify.com/authorize"
|
static let authUrl = "https://accounts.spotify.com/authorize"
|
||||||
@@ -24,17 +23,31 @@ final class SpotifyAuthManager: NSObject, ObservableObject {
|
|||||||
@Published private(set) var isConnected: Bool = false
|
@Published private(set) var isConnected: Bool = false
|
||||||
@Published private(set) var isAuthenticating: Bool = false
|
@Published private(set) var isAuthenticating: Bool = false
|
||||||
@Published private(set) var error: String? = nil
|
@Published private(set) var error: String? = nil
|
||||||
|
@Published private(set) var clientId: String = ""
|
||||||
|
|
||||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "iris", category: "SpotifyAuth")
|
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "iris", category: "SpotifyAuth")
|
||||||
private let tokensKey = "spotify_tokens"
|
private let tokensKey = "spotify_tokens"
|
||||||
|
private let clientIdKey = "spotify_client_id"
|
||||||
private var codeVerifier: String? = nil
|
private var codeVerifier: String? = nil
|
||||||
private var authSession: ASWebAuthenticationSession? = nil
|
private var authSession: ASWebAuthenticationSession? = nil
|
||||||
|
|
||||||
|
var isConfigured: Bool {
|
||||||
|
!clientId.isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
|
clientId = UserDefaults.standard.string(forKey: clientIdKey) ?? ""
|
||||||
loadTokens()
|
loadTokens()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setClientId(_ id: String) {
|
||||||
|
let trimmed = id.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
clientId = trimmed
|
||||||
|
UserDefaults.standard.set(trimmed, forKey: clientIdKey)
|
||||||
|
logger.info("Spotify Client ID updated")
|
||||||
|
}
|
||||||
|
|
||||||
var accessToken: String? {
|
var accessToken: String? {
|
||||||
loadStoredTokens()?.accessToken
|
loadStoredTokens()?.accessToken
|
||||||
}
|
}
|
||||||
@@ -50,7 +63,7 @@ final class SpotifyAuthManager: NSObject, ObservableObject {
|
|||||||
|
|
||||||
var components = URLComponents(string: SpotifyConfig.authUrl)!
|
var components = URLComponents(string: SpotifyConfig.authUrl)!
|
||||||
components.queryItems = [
|
components.queryItems = [
|
||||||
URLQueryItem(name: "client_id", value: SpotifyConfig.clientId),
|
URLQueryItem(name: "client_id", value: clientId),
|
||||||
URLQueryItem(name: "response_type", value: "code"),
|
URLQueryItem(name: "response_type", value: "code"),
|
||||||
URLQueryItem(name: "redirect_uri", value: SpotifyConfig.redirectUri),
|
URLQueryItem(name: "redirect_uri", value: SpotifyConfig.redirectUri),
|
||||||
URLQueryItem(name: "scope", value: SpotifyConfig.scopes),
|
URLQueryItem(name: "scope", value: SpotifyConfig.scopes),
|
||||||
@@ -145,7 +158,7 @@ final class SpotifyAuthManager: NSObject, ObservableObject {
|
|||||||
"grant_type": "authorization_code",
|
"grant_type": "authorization_code",
|
||||||
"code": code,
|
"code": code,
|
||||||
"redirect_uri": SpotifyConfig.redirectUri,
|
"redirect_uri": SpotifyConfig.redirectUri,
|
||||||
"client_id": SpotifyConfig.clientId,
|
"client_id": clientId,
|
||||||
"code_verifier": verifier
|
"code_verifier": verifier
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -172,7 +185,7 @@ final class SpotifyAuthManager: NSObject, ObservableObject {
|
|||||||
let body = [
|
let body = [
|
||||||
"grant_type": "refresh_token",
|
"grant_type": "refresh_token",
|
||||||
"refresh_token": refreshToken,
|
"refresh_token": refreshToken,
|
||||||
"client_id": SpotifyConfig.clientId
|
"client_id": clientId
|
||||||
]
|
]
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import SwiftUI
|
|||||||
struct SettingsView: View {
|
struct SettingsView: View {
|
||||||
@EnvironmentObject private var orchestrator: ContextOrchestrator
|
@EnvironmentObject private var orchestrator: ContextOrchestrator
|
||||||
@EnvironmentObject private var spotifyAuth: SpotifyAuthManager
|
@EnvironmentObject private var spotifyAuth: SpotifyAuthManager
|
||||||
|
@State private var isConfigExpanded: Bool = false
|
||||||
|
@State private var clientIdInput: String = ""
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
@@ -40,6 +42,26 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
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 {
|
if spotifyAuth.isConnected {
|
||||||
HStack {
|
HStack {
|
||||||
Label("Connected", systemImage: "checkmark.circle.fill")
|
Label("Connected", systemImage: "checkmark.circle.fill")
|
||||||
@@ -52,7 +74,7 @@ struct SettingsView: View {
|
|||||||
orchestrator.musicSource = .appleMusic
|
orchestrator.musicSource = .appleMusic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if spotifyAuth.isConfigured {
|
||||||
Button {
|
Button {
|
||||||
spotifyAuth.startAuth()
|
spotifyAuth.startAuth()
|
||||||
} label: {
|
} label: {
|
||||||
@@ -65,6 +87,10 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.disabled(spotifyAuth.isAuthenticating)
|
.disabled(spotifyAuth.isAuthenticating)
|
||||||
|
} else {
|
||||||
|
Text("Enter your Client ID above to connect")
|
||||||
|
.font(.callout)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let error = spotifyAuth.error {
|
if let error = spotifyAuth.error {
|
||||||
@@ -75,7 +101,7 @@ struct SettingsView: View {
|
|||||||
} header: {
|
} header: {
|
||||||
Text("Spotify")
|
Text("Spotify")
|
||||||
} footer: {
|
} footer: {
|
||||||
if !spotifyAuth.isConnected {
|
if !spotifyAuth.isConnected && spotifyAuth.isConfigured {
|
||||||
Text("Connect your Spotify account to display current track on Glass.")
|
Text("Connect your Spotify account to display current track on Glass.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user