Add Tonuino Manager website and Sparkle updates
This commit is contained in:
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user