Neues natives macOS-Settings-Fenster mit Sprache, Auto-Update-Check,
Farbschema (Standard/Kontrastreich), echter Textgrößen-Skalierung,
Bedienelement-Größe und LAN-Scanner-Refreshraten/Sparkline-Einstellungen.
- AppPreferences.swift: zentrale AppStorage-Keys, ColorTheme/AppTextSize/
UIDensity
- SettingsView.swift: 3-Tab-Settings-Scene
- .environment(\.dynamicTypeSize) erwies sich auf macOS als wirkungslos
(per ImageRenderer-Snapshot bewiesen) — durch eigenen appFontScale-
Mechanismus ersetzt, ~110 .font(...)-Aufrufe app-weit umgestellt
- Farbschema auf Wunsch auch auf Experte-Tab-Sidebar ausgeweitet
- Docs aktualisiert: README/HANDOFF/CHATLOG/Manual.md+PDF, found.md
Live bestätigt nach mehreren Nachbesserungsrunden ("passt, lassen wir so").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
150 lines
7.2 KiB
Swift
150 lines
7.2 KiB
Swift
import SwiftUI
|
|
|
|
/// The app's native Settings window (⌘,, wired up as a `Settings { }` scene in
|
|
/// `RouterOSAssistantApp`) — every control here is a plain `@AppStorage` binding under a key
|
|
/// catalogued in `AppPreferences`, so there's no view model: nothing here is anything other than
|
|
/// persisted preference state, and each tab's changes take effect live (no "Apply"/restart)
|
|
/// since every consumer (`OverviewView`, `DevicesView`, `DevicesViewModel`, ...) either observes
|
|
/// the same `@AppStorage` key itself or re-reads `UserDefaults.standard` on its own next tick.
|
|
struct SettingsView: View {
|
|
@AppStorage("appLanguage") private var appLanguage: String = "de"
|
|
|
|
var body: some View {
|
|
TabView {
|
|
GeneralSettingsView()
|
|
.tabItem { Label(L10n.t("Allgemein", appLanguage), systemImage: "gearshape") }
|
|
AppearanceSettingsView()
|
|
.tabItem { Label(L10n.t("Darstellung", appLanguage), systemImage: "paintpalette") }
|
|
NetworkSettingsView()
|
|
.tabItem { Label(L10n.t("Netzwerk", appLanguage), systemImage: "network") }
|
|
}
|
|
.frame(width: 480)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
}
|
|
}
|
|
|
|
private struct GeneralSettingsView: View {
|
|
@AppStorage("appLanguage") private var appLanguage: String = "de"
|
|
@AppStorage(AppPreferences.autoCheckUpdatesOnConnectKey) private var autoCheckUpdatesOnConnect: Bool = false
|
|
|
|
var body: some View {
|
|
Form {
|
|
Section(L10n.t("Sprache", appLanguage)) {
|
|
Picker(L10n.t("Sprache", appLanguage), selection: $appLanguage) {
|
|
Text("Deutsch").tag("de")
|
|
Text("English").tag("en")
|
|
}
|
|
.pickerStyle(.segmented)
|
|
.labelsHidden()
|
|
Text(L10n.t("Entspricht dem 🇩🇪/🇬🇧-Knopf in der Toolbar — beide steuern dieselbe Einstellung.", appLanguage))
|
|
.appFont(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
Section(L10n.t("RouterOS-Updates", appLanguage)) {
|
|
Toggle(L10n.t("Beim Verbinden automatisch nach Updates suchen", appLanguage), isOn: $autoCheckUpdatesOnConnect)
|
|
Text(L10n.t("Prüft nach jedem erfolgreichen Verbinden automatisch, ob eine neuere RouterOS-Version verfügbar ist — entspricht dem Button \"Nach Updates suchen\" im Tab \"Verbinden\".", appLanguage))
|
|
.appFont(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
.formStyle(.grouped)
|
|
.padding()
|
|
}
|
|
}
|
|
|
|
private struct AppearanceSettingsView: View {
|
|
@AppStorage("appLanguage") private var appLanguage: String = "de"
|
|
@AppStorage(AppPreferences.colorThemeKey) private var colorThemeRaw: String = ColorTheme.standard.rawValue
|
|
@AppStorage(AppPreferences.textSizeKey) private var textSizeRaw: String = AppTextSize.standard.rawValue
|
|
@AppStorage(AppPreferences.uiDensityKey) private var uiDensityRaw: String = UIDensity.standard.rawValue
|
|
|
|
private var colorTheme: Binding<ColorTheme> {
|
|
Binding(get: { ColorTheme(rawValue: colorThemeRaw) ?? .standard }, set: { colorThemeRaw = $0.rawValue })
|
|
}
|
|
private var textSize: Binding<AppTextSize> {
|
|
Binding(get: { AppTextSize(rawValue: textSizeRaw) ?? .standard }, set: { textSizeRaw = $0.rawValue })
|
|
}
|
|
private var uiDensity: Binding<UIDensity> {
|
|
Binding(get: { UIDensity(rawValue: uiDensityRaw) ?? .standard }, set: { uiDensityRaw = $0.rawValue })
|
|
}
|
|
|
|
var body: some View {
|
|
Form {
|
|
Section(L10n.t("Farbschema", appLanguage)) {
|
|
Picker(L10n.t("Farbschema", appLanguage), selection: colorTheme) {
|
|
Text(L10n.t("Standard", appLanguage)).tag(ColorTheme.standard)
|
|
Text(L10n.t("Kontrastreich", appLanguage)).tag(ColorTheme.highContrast)
|
|
}
|
|
.pickerStyle(.radioGroup)
|
|
Text(L10n.t("Gilt für die Diagrammfarben in der Übersicht und die Status-Farben im LAN-Scanner (Traffic aktiv, Fest/Dynamisch, offener/geschlossener Port).", appLanguage))
|
|
.appFont(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
Section(L10n.t("Textgröße", appLanguage)) {
|
|
Picker(L10n.t("Textgröße", appLanguage), selection: textSize) {
|
|
Text(L10n.t("Klein", appLanguage)).tag(AppTextSize.small)
|
|
Text(L10n.t("Standard", appLanguage)).tag(AppTextSize.standard)
|
|
Text(L10n.t("Groß", appLanguage)).tag(AppTextSize.large)
|
|
Text(L10n.t("Sehr groß", appLanguage)).tag(AppTextSize.extraLarge)
|
|
}
|
|
.pickerStyle(.segmented)
|
|
.labelsHidden()
|
|
}
|
|
Section(L10n.t("Bedienelemente", appLanguage)) {
|
|
Picker(L10n.t("Bedienelemente", appLanguage), selection: uiDensity) {
|
|
Text(L10n.t("Kompakt", appLanguage)).tag(UIDensity.compact)
|
|
Text(L10n.t("Standard", appLanguage)).tag(UIDensity.standard)
|
|
Text(L10n.t("Komfortabel", appLanguage)).tag(UIDensity.comfortable)
|
|
}
|
|
.pickerStyle(.segmented)
|
|
.labelsHidden()
|
|
Text(L10n.t("Größe von Buttons, Feldern und Abständen in der ganzen App (\"Responsiveness\").", appLanguage))
|
|
.appFont(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
.formStyle(.grouped)
|
|
.padding()
|
|
}
|
|
}
|
|
|
|
private struct NetworkSettingsView: View {
|
|
@AppStorage("appLanguage") private var appLanguage: String = "de"
|
|
@AppStorage(AppPreferences.lanScannerPollIntervalSecondsKey) private var pollInterval: Double = 0.1
|
|
@AppStorage(AppPreferences.lanScannerSparklineWindowSecondsKey) private var sparklineWindow: Double = 30
|
|
@AppStorage(AppPreferences.lanScannerSparklineWidthKey) private var sparklineWidth: Double = 200
|
|
|
|
var body: some View {
|
|
Form {
|
|
Section(L10n.t("LAN-Scanner: Traffic-Anzeige", appLanguage)) {
|
|
Picker(L10n.t("Aktualisierungsrate", appLanguage), selection: $pollInterval) {
|
|
Text("0,1 s").tag(0.1)
|
|
Text("0,5 s").tag(0.5)
|
|
Text("1 s").tag(1.0)
|
|
Text("3 s").tag(3.0)
|
|
}
|
|
Picker(L10n.t("Sparkline-Zeitfenster", appLanguage), selection: $sparklineWindow) {
|
|
Text("10 s").tag(10.0)
|
|
Text("30 s").tag(30.0)
|
|
Text("60 s").tag(60.0)
|
|
}
|
|
Picker(L10n.t("Sparkline-Breite", appLanguage), selection: $sparklineWidth) {
|
|
Text("100 pt").tag(100.0)
|
|
Text("150 pt").tag(150.0)
|
|
Text("200 pt").tag(200.0)
|
|
Text("300 pt").tag(300.0)
|
|
}
|
|
Text(L10n.t("Höhere Aktualisierungsraten zeigen den Traffic-Verlauf feiner, kosten aber mehr CPU bei vielen gleichzeitig aktiven Ports.", appLanguage))
|
|
.appFont(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
.formStyle(.grouped)
|
|
.padding()
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
SettingsView()
|
|
}
|