Add Tonuino Manager website and Sparkle updates

This commit is contained in:
maddin committed 2026-10-31 16:40:48 +01:00
commit 1cac427ff2
44 files changed
+5861

No files matched your search

@@ -0,0 +1,127 @@
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
}
}
}
}