243 lines
7.8 KiB
Swift
243 lines
7.8 KiB
Swift
import SwiftUI
|
|
|
|
struct PreferencesView: View {
|
|
private enum Tab: CaseIterable {
|
|
case monitor, notifications, alerts
|
|
|
|
var title: String {
|
|
switch self {
|
|
case .monitor: return "Monitor"
|
|
case .notifications: return "Notifications"
|
|
case .alerts: return "Alerts"
|
|
}
|
|
}
|
|
|
|
var icon: String {
|
|
switch self {
|
|
case .monitor: return "waveform.path.ecg"
|
|
case .notifications: return "bell.badge"
|
|
case .alerts: return "exclamationmark.triangle"
|
|
}
|
|
}
|
|
}
|
|
|
|
@AppStorage("pingInterval") private var storedPingInterval: Int = 10
|
|
@AppStorage("refreshInterval") private var storedRefreshInterval: Int = 60
|
|
@AppStorage("showIntervalIndicator") private var showIntervalIndicator: Bool = true
|
|
|
|
@State private var pingIntervalSlider: Double = 10
|
|
@State private var refreshIntervalSlider: Double = 60
|
|
@State private var selection: Tab = .monitor
|
|
|
|
private let minimumInterval: Double = 10
|
|
private let maximumPingInterval: Double = 600
|
|
private let maximumRefreshInterval: Double = 600
|
|
|
|
var body: some View {
|
|
HStack(spacing: 0) {
|
|
sidebar
|
|
Divider()
|
|
ScrollView {
|
|
detailContent(for: selection)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.padding()
|
|
}
|
|
.frame(minWidth: 360, maxWidth: .infinity, maxHeight: .infinity)
|
|
}
|
|
.frame(minWidth: 560, minHeight: 360)
|
|
.onAppear {
|
|
pingIntervalSlider = Double(storedPingInterval)
|
|
refreshIntervalSlider = Double(storedRefreshInterval)
|
|
}
|
|
.onChange(of: pingIntervalSlider) { _, newValue in
|
|
storedPingInterval = Int(newValue)
|
|
}
|
|
.onChange(of: refreshIntervalSlider) { _, newValue in
|
|
storedRefreshInterval = Int(newValue)
|
|
}
|
|
}
|
|
|
|
private var sidebar: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
Text("Preferences")
|
|
.font(.headline)
|
|
.padding(.bottom, 8)
|
|
|
|
ForEach(Tab.allCases, id: \.self) { tab in
|
|
Button {
|
|
selection = tab
|
|
} label: {
|
|
HStack(spacing: 10) {
|
|
Image(systemName: tab.icon)
|
|
.frame(width: 20)
|
|
Text(tab.title)
|
|
Spacer()
|
|
}
|
|
.padding(.vertical, 8)
|
|
.padding(.horizontal, 6)
|
|
.background(
|
|
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
|
.fill(selection == tab ? Color.accentColor.opacity(0.25) : Color.clear)
|
|
)
|
|
}
|
|
.buttonStyle(.plain)
|
|
}
|
|
|
|
Spacer()
|
|
}
|
|
.padding()
|
|
.frame(width: 180, alignment: .top)
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func detailContent(for tab: Tab) -> some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
Text(tab.title)
|
|
.font(.title2)
|
|
.padding(.bottom, 12)
|
|
|
|
switch tab {
|
|
case .monitor:
|
|
MonitorPreferencesView(
|
|
pingIntervalSlider: $pingIntervalSlider,
|
|
refreshIntervalSlider: $refreshIntervalSlider,
|
|
showIntervalIndicator: $showIntervalIndicator,
|
|
minimumInterval: minimumInterval,
|
|
maximumPingInterval: maximumPingInterval,
|
|
maximumRefreshInterval: maximumRefreshInterval,
|
|
pingChanged: handlePingSliderEditing(_:),
|
|
refreshChanged: handleRefreshSliderEditing(_:)
|
|
)
|
|
case .notifications:
|
|
NotificationsPreferencesView()
|
|
case .alerts:
|
|
AlertsPreferencesView()
|
|
}
|
|
}
|
|
}
|
|
|
|
private func handlePingSliderEditing(_ editing: Bool) {
|
|
if !editing {
|
|
storedPingInterval = Int(pingIntervalSlider)
|
|
}
|
|
}
|
|
|
|
private func handleRefreshSliderEditing(_ editing: Bool) {
|
|
if !editing {
|
|
storedRefreshInterval = Int(refreshIntervalSlider)
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct MonitorPreferencesView: View {
|
|
@Binding var pingIntervalSlider: Double
|
|
@Binding var refreshIntervalSlider: Double
|
|
@Binding var showIntervalIndicator: Bool
|
|
|
|
let minimumInterval: Double
|
|
let maximumPingInterval: Double
|
|
let maximumRefreshInterval: Double
|
|
let pingChanged: (Bool) -> Void
|
|
let refreshChanged: (Bool) -> Void
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 18) {
|
|
Group {
|
|
Text("Ping interval")
|
|
.font(.headline)
|
|
Slider(
|
|
value: $pingIntervalSlider,
|
|
in: minimumInterval...maximumPingInterval,
|
|
step: 5
|
|
) {
|
|
Text("Ping interval")
|
|
} minimumValueLabel: {
|
|
Text("\(Int(minimumInterval))s")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
} maximumValueLabel: {
|
|
Text("\(Int(maximumPingInterval))s")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
} onEditingChanged: { editing in
|
|
pingChanged(editing)
|
|
}
|
|
Text("Current: \(Int(pingIntervalSlider)) seconds")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
|
|
Group {
|
|
Text("Refresh interval")
|
|
.font(.headline)
|
|
Slider(
|
|
value: $refreshIntervalSlider,
|
|
in: minimumInterval...maximumRefreshInterval,
|
|
step: 5
|
|
) {
|
|
Text("Refresh interval")
|
|
} minimumValueLabel: {
|
|
Text("\(Int(minimumInterval))s")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
} maximumValueLabel: {
|
|
Text("\(Int(maximumRefreshInterval))s")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
} onEditingChanged: { editing in
|
|
refreshChanged(editing)
|
|
}
|
|
Text("Current: \(Int(refreshIntervalSlider)) seconds")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
|
|
Divider()
|
|
|
|
Toggle("Show interval indicator", isOn: $showIntervalIndicator)
|
|
.toggleStyle(.switch)
|
|
|
|
Spacer()
|
|
}
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
}
|
|
}
|
|
|
|
private struct NotificationsPreferencesView: View {
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
Text("Notifications")
|
|
.font(.headline)
|
|
.padding(.bottom)
|
|
|
|
Text("Configure notification behavior here.")
|
|
.foregroundColor(.secondary)
|
|
|
|
Spacer()
|
|
}
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.padding()
|
|
}
|
|
}
|
|
|
|
private struct AlertsPreferencesView: View {
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
Text("Alerts")
|
|
.font(.headline)
|
|
.padding(.bottom)
|
|
|
|
Text("Configure alert thresholds and behavior.")
|
|
.foregroundColor(.secondary)
|
|
|
|
Spacer()
|
|
}
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.padding()
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
PreferencesView()
|
|
}
|