import AppKit import SwiftUI import Sparkle struct SettingsView: View { @EnvironmentObject private var app: AppModel @AppStorage("reader.route") private var defaultRoute = "tonuino" @State private var acoustIDClientKey = "" @State private var acoustIDKeyMessage: String? @State private var profileStorageMessage: String? var body: some View { Form { Section("Standardwerte") { Picker("Bevorzugter Kartenleser", selection: $defaultRoute) { Text("TonUINO über USB").tag("tonuino") Text("ACR122U").tag("acr122u") } Toggle("Update-Prüfung beim Start", isOn: Binding( get: { AppDelegate.updater?.updater.automaticallyChecksForUpdates ?? true }, set: { AppDelegate.updater?.updater.automaticallyChecksForUpdates = $0 } )) } Section("Hardware") { LabeledContent("TonUINO-Verbindung", value: app.cards.serialConnected ? app.cards.selectedSerialPort : "Nicht verbunden") LabeledContent("Verfügbare serielle Ports", value: "\(app.cards.serialPorts.count)") LabeledContent("PC/SC Reader", value: "\(app.cards.readerNames.count)") Button("Geräte erneut suchen", systemImage: "arrow.clockwise") { app.cards.refreshSerialPorts(); app.cards.refreshReaders() } } Section("Lokale Profile") { VStack(alignment: .leading, spacing: 4) { Text("Speicherort") .font(.caption) .foregroundStyle(.secondary) Text(app.library.profileStorageURL.path) .textSelection(.enabled) .font(.system(.body, design: .monospaced)) .lineLimit(3) } if let error = app.library.profileStorageError { Label("Profilspeicher nicht erreichbar", systemImage: "externaldrive.badge.exclamationmark") .foregroundStyle(.orange) Text("\(error) Die vorhandenen Profile bleiben gespeichert; verbinde das Laufwerk oder wähle einen anderen Speicherort.") .font(.caption) .foregroundStyle(.secondary) } HStack { Button("Speicherort ändern…", systemImage: "folder") { chooseProfileStorageLocation() } if app.library.isProfileStorageCustom { Button("Standardort verwenden", systemImage: "arrow.uturn.backward") { changeProfileStorage(to: nil) } } } .disabled(app.library.isSyncing) if app.library.isSyncing { ProgressView(app.library.syncProgress) .font(.caption) } if let profileStorageMessage { Text(profileStorageMessage) .font(.caption) .foregroundStyle(.secondary) } Text("Profile und ihre Audiodateien werden beim Wechsel vollständig kopiert. Ein externes Laufwerk muss beim Start verfügbar sein; nach dem erneuten Anschließen wird der Speicher automatisch neu eingelesen.") .font(.caption) .foregroundStyle(.secondary) } Section("Musikerkennung") { SecureField("AcoustID-Anwendungsschlüssel", text: $acoustIDClientKey) .textContentType(.password) HStack { Button("Schlüssel speichern", systemImage: "key") { do { try AcoustIDCredentials.saveClientKey(acoustIDClientKey) acoustIDKeyMessage = acoustIDClientKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? "Schlüssel entfernt." : "Schlüssel sicher im macOS-Schlüsselbund gespeichert." } catch { acoustIDKeyMessage = error.localizedDescription } } if let acoustIDKeyMessage { Text(acoustIDKeyMessage).font(.caption).foregroundStyle(.secondary) } } Text("Registriere die App bei AcoustID und trage hier den Anwendungsschlüssel ein. Er wird im macOS-Schlüsselbund gespeichert.") .font(.caption).foregroundStyle(.secondary) Link("AcoustID-Anwendung registrieren", destination: URL(string: "https://acoustid.org/application")!) } Section("Updates") { Text("Version \(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unbekannt")") .foregroundStyle(.secondary) Button("Auf Updates prüfen", systemImage: "arrow.clockwise") { AppDelegate.updater?.checkForUpdates(nil) } .disabled(AppDelegate.updater?.updater.canCheckForUpdates != true) } Section("Über die App") { Text("Tonuino Manager für macOS") Text("Verwaltet TonUINO Musik-SD-Karten und programmiert RFID/NFC-Karten.").foregroundStyle(.secondary) Link("TonUINO-TNG Firmware", destination: URL(string: "https://github.com/tonuino/TonUINO-TNG")!) } } .formStyle(.grouped) .padding(.top, 10) .navigationTitle("Einstellungen") .onAppear { acoustIDClientKey = AcoustIDCredentials.loadClientKey() ?? "" } } private func chooseProfileStorageLocation() { let panel = NSOpenPanel() panel.title = "Speicherort für lokale Profile auswählen" panel.prompt = "Diesen Ordner verwenden" panel.canChooseFiles = false panel.canChooseDirectories = true panel.canCreateDirectories = true panel.allowsMultipleSelection = false if panel.runModal() == .OK, let url = panel.url { changeProfileStorage(to: url) } } private func changeProfileStorage(to url: URL?) { profileStorageMessage = nil Task { do { try await app.library.changeProfileStorageBase(to: url) profileStorageMessage = "Speicherort geändert." } catch { profileStorageMessage = error.localizedDescription } } } }