general: settings menu cleanup
This commit is contained in:
44
Gaze/Models/SettingsSection.swift
Normal file
44
Gaze/Models/SettingsSection.swift
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
//
|
||||||
|
// SettingsSection.swift
|
||||||
|
// Gaze
|
||||||
|
//
|
||||||
|
// Created by Mike Freno on 1/14/26.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum SettingsSection: Int, CaseIterable, Identifiable {
|
||||||
|
case general = 0
|
||||||
|
case lookAway = 1
|
||||||
|
case blink = 2
|
||||||
|
case posture = 3
|
||||||
|
case enforceMode = 4
|
||||||
|
case userTimers = 5
|
||||||
|
case smartMode = 6
|
||||||
|
|
||||||
|
var id: Int { rawValue }
|
||||||
|
|
||||||
|
var title: String {
|
||||||
|
switch self {
|
||||||
|
case .general: return "General"
|
||||||
|
case .lookAway: return "Look Away"
|
||||||
|
case .blink: return "Blink"
|
||||||
|
case .posture: return "Posture"
|
||||||
|
case .enforceMode: return "Enforce Mode"
|
||||||
|
case .userTimers: return "User Timers"
|
||||||
|
case .smartMode: return "Smart Mode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var iconName: String {
|
||||||
|
switch self {
|
||||||
|
case .general: return "gearshape.fill"
|
||||||
|
case .lookAway: return "eye.fill"
|
||||||
|
case .blink: return "eye.circle.fill"
|
||||||
|
case .posture: return "figure.stand"
|
||||||
|
case .enforceMode: return "video.fill"
|
||||||
|
case .userTimers: return "plus.circle"
|
||||||
|
case .smartMode: return "brain.fill"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,64 +9,92 @@ import SwiftUI
|
|||||||
|
|
||||||
struct SettingsWindowView: View {
|
struct SettingsWindowView: View {
|
||||||
@ObservedObject var settingsManager: SettingsManager
|
@ObservedObject var settingsManager: SettingsManager
|
||||||
@State private var currentTab: Int
|
@State private var selectedSection: SettingsSection
|
||||||
|
|
||||||
init(settingsManager: SettingsManager, initialTab: Int = 0) {
|
init(settingsManager: SettingsManager, initialTab: Int = 0) {
|
||||||
self.settingsManager = settingsManager
|
self.settingsManager = settingsManager
|
||||||
_currentTab = State(initialValue: initialTab)
|
_selectedSection = State(initialValue: SettingsSection(rawValue: initialTab) ?? .general)
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
TabView(selection: $currentTab) {
|
if #available(macOS 15.0, *) {
|
||||||
LookAwaySetupView(settingsManager: settingsManager)
|
TabView(selection: $selectedSection) {
|
||||||
.tag(0)
|
Tab(
|
||||||
.tabItem {
|
SettingsSection.general.title,
|
||||||
Label("Look Away", systemImage: "eye.fill")
|
systemImage: SettingsSection.general.iconName,
|
||||||
|
value: SettingsSection.general
|
||||||
|
) {
|
||||||
|
GeneralSetupView(
|
||||||
|
settingsManager: settingsManager,
|
||||||
|
isOnboarding: false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
BlinkSetupView(settingsManager: settingsManager)
|
Tab(
|
||||||
.tag(1)
|
SettingsSection.lookAway.title,
|
||||||
.tabItem {
|
systemImage: SettingsSection.lookAway.iconName,
|
||||||
Label("Blink", systemImage: "eye.circle.fill")
|
value: SettingsSection.lookAway
|
||||||
|
) {
|
||||||
|
LookAwaySetupView(settingsManager: settingsManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
PostureSetupView(settingsManager: settingsManager)
|
Tab(
|
||||||
.tag(2)
|
SettingsSection.blink.title, systemImage: SettingsSection.blink.iconName,
|
||||||
.tabItem {
|
value: SettingsSection.blink
|
||||||
Label("Posture", systemImage: "figure.stand")
|
) {
|
||||||
|
BlinkSetupView(settingsManager: settingsManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
EnforceModeSetupView(settingsManager: settingsManager)
|
Tab(
|
||||||
.tag(3)
|
SettingsSection.posture.title,
|
||||||
.tabItem {
|
systemImage: SettingsSection.posture.iconName,
|
||||||
Label("Enforce Mode", systemImage: "video.fill")
|
value: SettingsSection.posture
|
||||||
|
) {
|
||||||
|
PostureSetupView(settingsManager: settingsManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
UserTimersView(
|
Tab(
|
||||||
userTimers: Binding(
|
SettingsSection.userTimers.title,
|
||||||
get: { settingsManager.settings.userTimers },
|
systemImage: SettingsSection.userTimers.iconName,
|
||||||
set: { settingsManager.settings.userTimers = $0 }
|
value: SettingsSection.userTimers
|
||||||
)
|
) {
|
||||||
)
|
UserTimersView(
|
||||||
.tag(4)
|
userTimers: Binding(
|
||||||
.tabItem {
|
get: { settingsManager.settings.userTimers },
|
||||||
Label("User Timers", systemImage: "plus.circle")
|
set: { settingsManager.settings.userTimers = $0 }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Tab(
|
||||||
|
SettingsSection.enforceMode.title,
|
||||||
|
systemImage: SettingsSection.enforceMode.iconName,
|
||||||
|
value: SettingsSection.enforceMode
|
||||||
|
) {
|
||||||
|
EnforceModeSetupView(settingsManager: settingsManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
Tab(
|
||||||
|
SettingsSection.smartMode.title,
|
||||||
|
systemImage: SettingsSection.smartMode.iconName,
|
||||||
|
value: SettingsSection.smartMode
|
||||||
|
) {
|
||||||
|
SmartModeSetupView(settingsManager: settingsManager)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.tabViewStyle(.sidebarAdaptable)
|
||||||
SmartModeSetupView(settingsManager: settingsManager)
|
} else {
|
||||||
.tag(5)
|
// Fallback for macOS 14 and earlier
|
||||||
.tabItem {
|
NavigationSplitView {
|
||||||
Label("Smart Mode", systemImage: "brain.fill")
|
List(SettingsSection.allCases, selection: $selectedSection) { section in
|
||||||
|
NavigationLink(value: section) {
|
||||||
|
Label(section.title, systemImage: section.iconName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.navigationTitle("Settings")
|
||||||
GeneralSetupView(
|
.listStyle(.sidebar)
|
||||||
settingsManager: settingsManager,
|
} detail: {
|
||||||
isOnboarding: false
|
detailView(for: selectedSection)
|
||||||
)
|
|
||||||
.tag(6)
|
|
||||||
.tabItem {
|
|
||||||
Label("General", systemImage: "gearshape.fill")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,24 +120,54 @@ struct SettingsWindowView: View {
|
|||||||
}
|
}
|
||||||
#if APPSTORE
|
#if APPSTORE
|
||||||
.frame(
|
.frame(
|
||||||
minWidth: 750,
|
minWidth: 1000,
|
||||||
minHeight: 700
|
minHeight: 700
|
||||||
)
|
)
|
||||||
#else
|
#else
|
||||||
.frame(
|
.frame(
|
||||||
minWidth: 750,
|
minWidth: 1000,
|
||||||
minHeight: 900
|
minHeight: 900
|
||||||
)
|
)
|
||||||
#endif
|
#endif
|
||||||
.onReceive(
|
.onReceive(
|
||||||
NotificationCenter.default.publisher(for: Notification.Name("SwitchToSettingsTab"))
|
NotificationCenter.default.publisher(for: Notification.Name("SwitchToSettingsTab"))
|
||||||
) { notification in
|
) { notification in
|
||||||
if let tab = notification.object as? Int {
|
if let tab = notification.object as? Int,
|
||||||
currentTab = tab
|
let section = SettingsSection(rawValue: tab)
|
||||||
|
{
|
||||||
|
selectedSection = section
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private func detailView(for section: SettingsSection) -> some View {
|
||||||
|
switch section {
|
||||||
|
case .general:
|
||||||
|
GeneralSetupView(
|
||||||
|
settingsManager: settingsManager,
|
||||||
|
isOnboarding: false
|
||||||
|
)
|
||||||
|
case .lookAway:
|
||||||
|
LookAwaySetupView(settingsManager: settingsManager)
|
||||||
|
case .blink:
|
||||||
|
BlinkSetupView(settingsManager: settingsManager)
|
||||||
|
case .posture:
|
||||||
|
PostureSetupView(settingsManager: settingsManager)
|
||||||
|
case .enforceMode:
|
||||||
|
EnforceModeSetupView(settingsManager: settingsManager)
|
||||||
|
case .userTimers:
|
||||||
|
UserTimersView(
|
||||||
|
userTimers: Binding(
|
||||||
|
get: { settingsManager.settings.userTimers },
|
||||||
|
set: { settingsManager.settings.userTimers = $0 }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
case .smartMode:
|
||||||
|
SmartModeSetupView(settingsManager: settingsManager)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func closeWindow() {
|
private func closeWindow() {
|
||||||
if let window = NSApplication.shared.windows.first(where: { $0.title == "Settings" }) {
|
if let window = NSApplication.shared.windows.first(where: { $0.title == "Settings" }) {
|
||||||
window.close()
|
window.close()
|
||||||
|
|||||||
@@ -143,7 +143,6 @@ struct MenuBarContentView: View {
|
|||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
|
|
||||||
// Quit
|
|
||||||
Button(action: onQuit) {
|
Button(action: onQuit) {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "power")
|
Image(systemName: "power")
|
||||||
@@ -190,7 +189,8 @@ struct MenuBarContentView: View {
|
|||||||
.padding(.top, 8)
|
.padding(.top, 8)
|
||||||
|
|
||||||
// Show all timers using unified identifier system
|
// Show all timers using unified identifier system
|
||||||
ForEach(getSortedTimerIdentifiers(timerEngine: timerEngine), id: \.self) { identifier in
|
ForEach(getSortedTimerIdentifiers(timerEngine: timerEngine), id: \.self) {
|
||||||
|
identifier in
|
||||||
if timerEngine.timerStates[identifier] != nil {
|
if timerEngine.timerStates[identifier] != nil {
|
||||||
TimerStatusRowWithIndividualControls(
|
TimerStatusRowWithIndividualControls(
|
||||||
identifier: identifier,
|
identifier: identifier,
|
||||||
@@ -294,7 +294,7 @@ struct MenuBarContentView: View {
|
|||||||
let activeStates = timerEngine.timerStates.values.filter { $0.isActive }
|
let activeStates = timerEngine.timerStates.values.filter { $0.isActive }
|
||||||
return !activeStates.isEmpty && activeStates.allSatisfy { $0.isPaused }
|
return !activeStates.isEmpty && activeStates.allSatisfy { $0.isPaused }
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getSortedTimerIdentifiers(timerEngine: TimerEngine) -> [TimerIdentifier] {
|
private func getSortedTimerIdentifiers(timerEngine: TimerEngine) -> [TimerIdentifier] {
|
||||||
return timerEngine.timerStates.keys.sorted { id1, id2 in
|
return timerEngine.timerStates.keys.sorted { id1, id2 in
|
||||||
// Sort built-in timers before user timers
|
// Sort built-in timers before user timers
|
||||||
@@ -333,16 +333,17 @@ struct TimerStatusRowWithIndividualControls: View {
|
|||||||
private var isPaused: Bool {
|
private var isPaused: Bool {
|
||||||
return state?.isPaused ?? false
|
return state?.isPaused ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
private var displayName: String {
|
private var displayName: String {
|
||||||
switch identifier {
|
switch identifier {
|
||||||
case .builtIn(let type):
|
case .builtIn(let type):
|
||||||
return type.displayName
|
return type.displayName
|
||||||
case .user(let id):
|
case .user(let id):
|
||||||
return settingsManager.settings.userTimers.first(where: { $0.id == id })?.title ?? "User Timer"
|
return settingsManager.settings.userTimers.first(where: { $0.id == id })?.title
|
||||||
|
?? "User Timer"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var iconName: String {
|
private var iconName: String {
|
||||||
switch identifier {
|
switch identifier {
|
||||||
case .builtIn(let type):
|
case .builtIn(let type):
|
||||||
@@ -351,7 +352,7 @@ struct TimerStatusRowWithIndividualControls: View {
|
|||||||
return "clock.fill"
|
return "clock.fill"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var color: Color {
|
private var color: Color {
|
||||||
switch identifier {
|
switch identifier {
|
||||||
case .builtIn(let type):
|
case .builtIn(let type):
|
||||||
@@ -361,16 +362,18 @@ struct TimerStatusRowWithIndividualControls: View {
|
|||||||
case .posture: return .orange
|
case .posture: return .orange
|
||||||
}
|
}
|
||||||
case .user(let id):
|
case .user(let id):
|
||||||
return settingsManager.settings.userTimers.first(where: { $0.id == id })?.color ?? .purple
|
return settingsManager.settings.userTimers.first(where: { $0.id == id })?.color
|
||||||
|
?? .purple
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var tooltipText: String {
|
private var tooltipText: String {
|
||||||
switch identifier {
|
switch identifier {
|
||||||
case .builtIn(let type):
|
case .builtIn(let type):
|
||||||
return type.tooltipText
|
return type.tooltipText
|
||||||
case .user(let id):
|
case .user(let id):
|
||||||
guard let timer = settingsManager.settings.userTimers.first(where: { $0.id == id }) else {
|
guard let timer = settingsManager.settings.userTimers.first(where: { $0.id == id })
|
||||||
|
else {
|
||||||
return "User Timer"
|
return "User Timer"
|
||||||
}
|
}
|
||||||
let typeText = timer.type == .subtle ? "Subtle" : "Overlay"
|
let typeText = timer.type == .subtle ? "Subtle" : "Overlay"
|
||||||
@@ -379,7 +382,7 @@ struct TimerStatusRowWithIndividualControls: View {
|
|||||||
return "\(typeText) timer - \(durationText)\(statusText)"
|
return "\(typeText) timer - \(durationText)\(statusText)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var userTimer: UserTimer? {
|
private var userTimer: UserTimer? {
|
||||||
if case .user(let id) = identifier {
|
if case .user(let id) = identifier {
|
||||||
return settingsManager.settings.userTimers.first(where: { $0.id == id })
|
return settingsManager.settings.userTimers.first(where: { $0.id == id })
|
||||||
@@ -440,7 +443,9 @@ struct TimerStatusRowWithIndividualControls: View {
|
|||||||
colorScheme: colorScheme
|
colorScheme: colorScheme
|
||||||
)
|
)
|
||||||
.help("Trigger \(displayName) reminder now (dev)")
|
.help("Trigger \(displayName) reminder now (dev)")
|
||||||
.accessibilityIdentifier("trigger_\(displayName.replacingOccurrences(of: " ", with: "_"))")
|
.accessibilityIdentifier(
|
||||||
|
"trigger_\(displayName.replacingOccurrences(of: " ", with: "_"))"
|
||||||
|
)
|
||||||
.onHover { hovering in
|
.onHover { hovering in
|
||||||
isHoveredDevTrigger = hovering
|
isHoveredDevTrigger = hovering
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import SwiftUI
|
|||||||
|
|
||||||
struct SmartModeSetupView: View {
|
struct SmartModeSetupView: View {
|
||||||
@ObservedObject var settingsManager: SettingsManager
|
@ObservedObject var settingsManager: SettingsManager
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
// Fixed header section
|
// Fixed header section
|
||||||
@@ -17,140 +17,177 @@ struct SmartModeSetupView: View {
|
|||||||
Image(systemName: "brain.fill")
|
Image(systemName: "brain.fill")
|
||||||
.font(.system(size: 60))
|
.font(.system(size: 60))
|
||||||
.foregroundColor(.purple)
|
.foregroundColor(.purple)
|
||||||
|
|
||||||
Text("Smart Mode")
|
Text("Smart Mode")
|
||||||
.font(.system(size: 28, weight: .bold))
|
.font(.system(size: 28, weight: .bold))
|
||||||
|
|
||||||
Text("Automatically manage timers based on your activity")
|
Text("Automatically manage timers based on your activity")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
.padding(.bottom, 30)
|
.padding(.bottom, 30)
|
||||||
|
|
||||||
// Vertically centered content
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
VStack(spacing: 24) {
|
VStack(spacing: 24) {
|
||||||
// Auto-pause on fullscreen toggle
|
// Auto-pause on fullscreen toggle
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
Toggle(isOn: Binding(
|
HStack {
|
||||||
get: { settingsManager.settings.smartMode.autoPauseOnFullscreen },
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
set: { settingsManager.settings.smartMode.autoPauseOnFullscreen = $0 }
|
HStack {
|
||||||
)) {
|
Image(systemName: "arrow.up.left.and.arrow.down.right")
|
||||||
HStack {
|
.foregroundColor(.blue)
|
||||||
Image(systemName: "arrow.up.left.and.arrow.down.right")
|
Text("Auto-pause on Fullscreen")
|
||||||
.foregroundColor(.blue)
|
.font(.headline)
|
||||||
Text("Auto-pause on Fullscreen")
|
}
|
||||||
.font(.headline)
|
Text(
|
||||||
|
"Timers will automatically pause when you enter fullscreen mode (videos, games, presentations)"
|
||||||
|
)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
|
Spacer()
|
||||||
|
Toggle(
|
||||||
|
"",
|
||||||
|
isOn: Binding(
|
||||||
|
get: { settingsManager.settings.smartMode.autoPauseOnFullscreen },
|
||||||
|
set: {
|
||||||
|
settingsManager.settings.smartMode.autoPauseOnFullscreen = $0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.labelsHidden()
|
||||||
}
|
}
|
||||||
.toggleStyle(.switch)
|
|
||||||
|
|
||||||
Text("Timers will automatically pause when you enter fullscreen mode (videos, games, presentations)")
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
.padding(.leading, 28)
|
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 8))
|
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 8))
|
||||||
|
|
||||||
// Auto-pause on idle toggle with threshold slider
|
// Auto-pause on idle toggle with threshold slider
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
Toggle(isOn: Binding(
|
HStack {
|
||||||
get: { settingsManager.settings.smartMode.autoPauseOnIdle },
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
set: { settingsManager.settings.smartMode.autoPauseOnIdle = $0 }
|
HStack {
|
||||||
)) {
|
Image(systemName: "moon.zzz.fill")
|
||||||
HStack {
|
.foregroundColor(.indigo)
|
||||||
Image(systemName: "moon.zzz.fill")
|
Text("Auto-pause on Idle")
|
||||||
.foregroundColor(.indigo)
|
.font(.headline)
|
||||||
Text("Auto-pause on Idle")
|
}
|
||||||
.font(.headline)
|
Text(
|
||||||
|
"Timers will pause when you're inactive for more than the threshold below"
|
||||||
|
)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
|
Spacer()
|
||||||
|
Toggle(
|
||||||
|
"",
|
||||||
|
isOn: Binding(
|
||||||
|
get: { settingsManager.settings.smartMode.autoPauseOnIdle },
|
||||||
|
set: { settingsManager.settings.smartMode.autoPauseOnIdle = $0 }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.labelsHidden()
|
||||||
}
|
}
|
||||||
.toggleStyle(.switch)
|
|
||||||
|
|
||||||
Text("Timers will pause when you're inactive for more than the threshold below")
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
.padding(.leading, 28)
|
|
||||||
|
|
||||||
if settingsManager.settings.smartMode.autoPauseOnIdle {
|
if settingsManager.settings.smartMode.autoPauseOnIdle {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Idle Threshold:")
|
Text("Idle Threshold:")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("\(settingsManager.settings.smartMode.idleThresholdMinutes) min")
|
Text(
|
||||||
.font(.subheadline)
|
"\(settingsManager.settings.smartMode.idleThresholdMinutes) min"
|
||||||
.foregroundColor(.secondary)
|
)
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
Slider(
|
Slider(
|
||||||
value: Binding(
|
value: Binding(
|
||||||
get: { Double(settingsManager.settings.smartMode.idleThresholdMinutes) },
|
get: {
|
||||||
set: { settingsManager.settings.smartMode.idleThresholdMinutes = Int($0) }
|
Double(
|
||||||
|
settingsManager.settings.smartMode.idleThresholdMinutes)
|
||||||
|
},
|
||||||
|
set: {
|
||||||
|
settingsManager.settings.smartMode.idleThresholdMinutes =
|
||||||
|
Int($0)
|
||||||
|
}
|
||||||
),
|
),
|
||||||
in: 1...30,
|
in: 1...30,
|
||||||
step: 1
|
step: 1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.padding(.top, 8)
|
.padding(.top, 8)
|
||||||
.padding(.leading, 28)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 8))
|
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 8))
|
||||||
|
|
||||||
// Usage tracking toggle with reset threshold
|
// Usage tracking toggle with reset threshold
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
Toggle(isOn: Binding(
|
HStack {
|
||||||
get: { settingsManager.settings.smartMode.trackUsage },
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
set: { settingsManager.settings.smartMode.trackUsage = $0 }
|
HStack {
|
||||||
)) {
|
Image(systemName: "chart.line.uptrend.xyaxis")
|
||||||
HStack {
|
.foregroundColor(.green)
|
||||||
Image(systemName: "chart.line.uptrend.xyaxis")
|
Text("Track Usage Statistics")
|
||||||
.foregroundColor(.green)
|
.font(.headline)
|
||||||
Text("Track Usage Statistics")
|
}
|
||||||
.font(.headline)
|
Text(
|
||||||
|
"Monitor active and idle time, with automatic reset after the specified duration"
|
||||||
|
)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
|
Spacer()
|
||||||
|
Toggle(
|
||||||
|
"",
|
||||||
|
isOn: Binding(
|
||||||
|
get: { settingsManager.settings.smartMode.trackUsage },
|
||||||
|
set: { settingsManager.settings.smartMode.trackUsage = $0 }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.labelsHidden()
|
||||||
}
|
}
|
||||||
.toggleStyle(.switch)
|
|
||||||
|
|
||||||
Text("Monitor active and idle time, with automatic reset after the specified duration")
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
.padding(.leading, 28)
|
|
||||||
|
|
||||||
if settingsManager.settings.smartMode.trackUsage {
|
if settingsManager.settings.smartMode.trackUsage {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Reset After:")
|
Text("Reset After:")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("\(settingsManager.settings.smartMode.usageResetAfterMinutes) min")
|
Text(
|
||||||
.font(.subheadline)
|
"\(settingsManager.settings.smartMode.usageResetAfterMinutes) min"
|
||||||
.foregroundColor(.secondary)
|
)
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
Slider(
|
Slider(
|
||||||
value: Binding(
|
value: Binding(
|
||||||
get: { Double(settingsManager.settings.smartMode.usageResetAfterMinutes) },
|
get: {
|
||||||
set: { settingsManager.settings.smartMode.usageResetAfterMinutes = Int($0) }
|
Double(
|
||||||
|
settingsManager.settings.smartMode
|
||||||
|
.usageResetAfterMinutes)
|
||||||
|
},
|
||||||
|
set: {
|
||||||
|
settingsManager.settings.smartMode.usageResetAfterMinutes =
|
||||||
|
Int($0)
|
||||||
|
}
|
||||||
),
|
),
|
||||||
in: 15...240,
|
in: 15...240,
|
||||||
step: 15
|
step: 15
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.padding(.top, 8)
|
.padding(.top, 8)
|
||||||
.padding(.leading, 28)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 8))
|
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 8))
|
||||||
}
|
}
|
||||||
.frame(maxWidth: 600)
|
.frame(maxWidth: 600)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
|||||||
Reference in New Issue
Block a user