// // LoginView.swift // Mobile Music Assistant // // Created by Sven Hanold on 26.03.26. // import SwiftUI struct LoginView: View { @Environment(MAService.self) private var service @State private var serverURL = "https://" @State private var token = "" @State private var showToken = false @State private var isLoading = false @State private var errorMessage: String? @State private var showError = false var body: some View { NavigationStack { Form { // Server URL Section Section { TextField("Server URL", text: $serverURL) .textContentType(.URL) .keyboardType(.URL) .autocapitalization(.none) .autocorrectionDisabled() } header: { Text("Server") } footer: { Text("Enter your Music Assistant server URL (e.g., https://music.example.com)") } // Token Section Section { HStack { Group { if showToken { TextField("Long-Lived Access Token", text: $token) .textContentType(.password) .autocapitalization(.none) .autocorrectionDisabled() } else { SecureField("Long-Lived Access Token", text: $token) .textContentType(.password) } } Button { showToken.toggle() } label: { Image(systemName: showToken ? "eye.slash.fill" : "eye.fill") .foregroundStyle(.secondary) } .buttonStyle(.plain) } } header: { Text("Authentication") } footer: { VStack(alignment: .leading, spacing: 8) { Text("How to get a token:") Text("1. Open Music Assistant in a browser") Text("2. Go to Settings → Users") Text("3. Create a new long-lived access token") Text("4. Copy and paste the token here") } .font(.caption) .foregroundStyle(.secondary) } // Connect Button Section { Button { Task { await login() } } label: { if isLoading { HStack { Spacer() ProgressView() Text("Connecting...") .padding(.leading, 8) Spacer() } } else { HStack { Spacer() Text("Connect") .fontWeight(.semibold) Spacer() } } } .disabled(isLoading || !isFormValid) } } .navigationTitle("Music Assistant") .alert("Connection Error", isPresented: $showError) { Button("OK", role: .cancel) { } } message: { if let errorMessage { Text(errorMessage) } } } .applyTheme() } // MARK: - Computed Properties private var isFormValid: Bool { !serverURL.isEmpty && serverURL.starts(with: "http") && !token.isEmpty } // MARK: - Actions private func login() async { guard let url = URL(string: serverURL) else { showError(message: "Invalid server URL") return } isLoading = true errorMessage = nil print("🔵 LoginView: Starting login with long-lived token") print("🔵 LoginView: Server URL = \(url.absoluteString)") print("🔵 LoginView: Token length = \(token.count)") do { // Save token to keychain try service.authManager.saveToken(serverURL: url, token: token) print("✅ LoginView: Token saved to keychain") // Connect WebSocket with token print("🔵 LoginView: Connecting WebSocket") try await service.connect(serverURL: url, token: token) print("✅ LoginView: Connected successfully") isLoading = false } catch { print("❌ LoginView: Login failed - \(error)") isLoading = false showError(message: error.localizedDescription) } } private func showError(message: String) { errorMessage = message showError = true } } #Preview { LoginView() .environment(MAService()) }