165 lines
5.4 KiB
Swift
165 lines
5.4 KiB
Swift
//
|
|
// 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())
|
|
}
|
|
|