view cleanup

This commit is contained in:
Michael Freno
2026-01-12 20:03:05 -05:00
parent 359389f326
commit d5c8f49bf9
8 changed files with 135 additions and 273 deletions

View File

@@ -31,11 +31,7 @@ enum ReminderSize: String, Codable, CaseIterable {
}
}
// MARK: - Centralized Configuration System
/// Unified configuration class that manages all app settings in a centralized way
struct AppSettings: Codable, Equatable, Hashable {
// Timer configurations
var lookAwayTimer: TimerConfiguration
var lookAwayCountdownSeconds: Int
var blinkTimer: TimerConfiguration

View File

@@ -26,11 +26,11 @@ struct SliderSection: View {
guard enabled else {
return "\(type) reminders are currently disabled."
}
if countdownSettings.isNil && intervalSettings.isNil {
return "You will be reminded every \(intervalSettings.val) minutes"
if countdownSettings.isNil && !intervalSettings.isNil {
return "You will be reminded every \(intervalSettings.val ?? 0) minutes"
}
return
"You will be \(countdownSettings.isNil ? "subtly" : "") reminded every \(intervalSettings.val) minutes for \(countdownSettings.val!) seconds"
"You will be \(countdownSettings.isNil ? "subtly" : "") reminded every \(intervalSettings.val ?? 0) minutes for \(countdownSettings.val ?? 0) seconds"
}
var body: some View {
@@ -51,10 +51,10 @@ struct SliderSection: View {
),
in:
Double(
intervalSettings.range.bounds.lowerBound)...Double(
intervalSettings.range.bounds.upperBound),
intervalSettings.range?.bounds.lowerBound ?? 0)...Double(
intervalSettings.range?.bounds.upperBound ?? 100),
step: 5.0)
Text("\(intervalSettings.val) min")
Text("\(intervalSettings.val ?? 0) min")
.frame(width: 60, alignment: .trailing)
.monospacedDigit()
}
@@ -66,14 +66,14 @@ struct SliderSection: View {
HStack {
Slider(
value: Binding(
get: { Double(countdownSettings.seconds ?? 0) },
set: { countdownSettings.seconds = Int($0) }
get: { Double(countdownSettings.val ?? 0) },
set: { countdownSettings.val = Int($0) }
),
in:
Double(
range.bounds.lowerBound)...Double(range.bounds.upperBound),
step: 5.0)
Text("\(countdownSettings.seconds ?? 0) sec")
Text("\(countdownSettings.val ?? 0) sec")
.frame(width: 60, alignment: .trailing)
.monospacedDigit()
}
@@ -119,4 +119,3 @@ struct SliderSection: View {
)
}
}

View File

@@ -22,23 +22,8 @@ struct VisualEffectView: NSViewRepresentable {
struct OnboardingContainerView: View {
@ObservedObject var settingsManager: SettingsManager
@State private var currentPage = 0
@State private var lookAwayEnabled = true
@State private var lookAwayIntervalMinutes = 20
@State private var lookAwayCountdownSeconds = 20
@State private var blinkEnabled = false
@State private var blinkIntervalMinutes = 5
@State private var postureEnabled = true
@State private var postureIntervalMinutes = 30
@State private var launchAtLogin = false
@State private var subtleReminderSize: ReminderSize = .medium
@State private var isAppStoreVersion: Bool
@Environment(\.dismiss) private var dismiss
init(settingsManager: SettingsManager) {
self.settingsManager = settingsManager
_isAppStoreVersion = State(initialValue: settingsManager.settings.isAppStoreVersion)
}
var body: some View {
ZStack {
VisualEffectView(material: .hudWindow, blendingMode: .behindWindow)
@@ -51,40 +36,26 @@ struct OnboardingContainerView: View {
Image(systemName: "hand.wave.fill")
}
LookAwaySetupView(
enabled: $lookAwayEnabled,
intervalMinutes: $lookAwayIntervalMinutes,
countdownSeconds: $lookAwayCountdownSeconds
)
.tag(1)
.tabItem {
Image(systemName: "eye.fill")
}
LookAwaySetupView(settingsManager: settingsManager)
.tag(1)
.tabItem {
Image(systemName: "eye.fill")
}
BlinkSetupView(
enabled: $blinkEnabled,
intervalMinutes: $blinkIntervalMinutes,
subtleReminderSize: subtleReminderSize
)
.tag(2)
.tabItem {
Image(systemName: "eye.circle.fill")
}
BlinkSetupView(settingsManager: settingsManager)
.tag(2)
.tabItem {
Image(systemName: "eye.circle.fill")
}
PostureSetupView(
enabled: $postureEnabled,
intervalMinutes: $postureIntervalMinutes,
subtleReminderSize: subtleReminderSize
)
.tag(3)
.tabItem {
Image(systemName: "figure.stand")
}
PostureSetupView(settingsManager: settingsManager)
.tag(3)
.tabItem {
Image(systemName: "figure.stand")
}
GeneralSetupView(
launchAtLogin: $launchAtLogin,
subtleReminderSize: $subtleReminderSize,
isAppStoreVersion: .constant(isAppStoreVersion),
settingsManager: settingsManager,
isOnboarding: true
)
.tag(4)
@@ -155,46 +126,14 @@ struct OnboardingContainerView: View {
.frame(
minWidth: 1000,
minHeight: isAppStoreVersion ? 700 : 900
minHeight: settingsManager.settings.isAppStoreVersion ? 700 : 900
)
.onReceive(settingsManager.$settings) { newSettings in
isAppStoreVersion = newSettings.isAppStoreVersion
}
}
private func completeOnboarding() {
// Save settings
settingsManager.settings.lookAwayTimer = TimerConfiguration(
enabled: lookAwayEnabled,
intervalSeconds: lookAwayIntervalMinutes * 60
)
settingsManager.settings.lookAwayCountdownSeconds = lookAwayCountdownSeconds
settingsManager.settings.blinkTimer = TimerConfiguration(
enabled: blinkEnabled,
intervalSeconds: blinkIntervalMinutes * 60
)
settingsManager.settings.postureTimer = TimerConfiguration(
enabled: postureEnabled,
intervalSeconds: postureIntervalMinutes * 60
)
settingsManager.settings.launchAtLogin = launchAtLogin
settingsManager.settings.subtleReminderSize = subtleReminderSize
// Mark onboarding as complete - settings are already being updated in real-time
settingsManager.settings.hasCompletedOnboarding = true
// Apply launch at login setting
do {
if launchAtLogin {
try LaunchAtLoginManager.enable()
} else {
try LaunchAtLoginManager.disable()
}
} catch {
print("Failed to set launch at login: \(error)")
}
// Close window with standard macOS animation
dismiss()

View File

@@ -10,83 +10,44 @@ import SwiftUI
struct SettingsWindowView: View {
@ObservedObject var settingsManager: SettingsManager
@State private var currentTab: Int
@State private var lookAwayEnabled: Bool
@State private var lookAwayIntervalMinutes: Int
@State private var lookAwayCountdownSeconds: Int
@State private var blinkEnabled: Bool
@State private var blinkIntervalMinutes: Int
@State private var postureEnabled: Bool
@State private var postureIntervalMinutes: Int
@State private var launchAtLogin: Bool
@State private var subtleReminderSize: ReminderSize
@State private var userTimers: [UserTimer]
@State private var isAppStoreVersion: Bool
init(settingsManager: SettingsManager, initialTab: Int = 0) {
self.settingsManager = settingsManager
_currentTab = State(initialValue: initialTab)
_lookAwayEnabled = State(initialValue: settingsManager.settings.lookAwayTimer.enabled)
_lookAwayIntervalMinutes = State(
initialValue: settingsManager.settings.lookAwayTimer.intervalSeconds / 60)
_lookAwayCountdownSeconds = State(
initialValue: settingsManager.settings.lookAwayCountdownSeconds)
_blinkEnabled = State(initialValue: settingsManager.settings.blinkTimer.enabled)
_blinkIntervalMinutes = State(
initialValue: settingsManager.settings.blinkTimer.intervalSeconds / 60)
_postureEnabled = State(initialValue: settingsManager.settings.postureTimer.enabled)
_postureIntervalMinutes = State(
initialValue: settingsManager.settings.postureTimer.intervalSeconds / 60)
_launchAtLogin = State(initialValue: settingsManager.settings.launchAtLogin)
_subtleReminderSize = State(
initialValue: settingsManager.settings.subtleReminderSize)
_userTimers = State(initialValue: settingsManager.settings.userTimers)
_isAppStoreVersion = State(initialValue: settingsManager.settings.isAppStoreVersion)
}
var body: some View {
VStack(spacing: 0) {
TabView(selection: $currentTab) {
LookAwaySetupView(
enabled: $lookAwayEnabled,
intervalMinutes: $lookAwayIntervalMinutes,
countdownSeconds: $lookAwayCountdownSeconds
)
.tag(0)
.tabItem {
Label("Look Away", systemImage: "eye.fill")
}
LookAwaySetupView(settingsManager: settingsManager)
.tag(0)
.tabItem {
Label("Look Away", systemImage: "eye.fill")
}
BlinkSetupView(
enabled: $blinkEnabled,
intervalMinutes: $blinkIntervalMinutes,
subtleReminderSize: subtleReminderSize
)
.tag(1)
.tabItem {
Label("Blink", systemImage: "eye.circle.fill")
}
BlinkSetupView(settingsManager: settingsManager)
.tag(1)
.tabItem {
Label("Blink", systemImage: "eye.circle.fill")
}
PostureSetupView(
enabled: $postureEnabled,
intervalMinutes: $postureIntervalMinutes,
subtleReminderSize: subtleReminderSize
)
.tag(2)
.tabItem {
Label("Posture", systemImage: "figure.stand")
}
PostureSetupView(settingsManager: settingsManager)
.tag(2)
.tabItem {
Label("Posture", systemImage: "figure.stand")
}
UserTimersView(userTimers: $userTimers)
UserTimersView(userTimers: Binding(
get: { settingsManager.settings.userTimers },
set: { settingsManager.settings.userTimers = $0 }
))
.tag(3)
.tabItem {
Label("User Timers", systemImage: "plus.circle")
}
GeneralSetupView(
launchAtLogin: $launchAtLogin,
subtleReminderSize: $subtleReminderSize,
isAppStoreVersion: .constant(isAppStoreVersion),
settingsManager: settingsManager,
isOnboarding: false
)
.tag(4)
@@ -100,27 +61,18 @@ struct SettingsWindowView: View {
HStack {
Spacer()
Button("Cancel") {
Button("Close") {
closeWindow()
}
.keyboardShortcut(.escape)
Button("Apply") {
applySettings()
closeWindow()
}
.keyboardShortcut(.return)
.buttonStyle(.borderedProminent)
}
.padding()
}
.frame(
minWidth: 750,
minHeight: isAppStoreVersion ? 700 : 900
minHeight: settingsManager.settings.isAppStoreVersion ? 700 : 900
)
.onReceive(settingsManager.$settings) { newSettings in
isAppStoreVersion = newSettings.isAppStoreVersion
}
.onReceive(
NotificationCenter.default.publisher(for: Notification.Name("SwitchToSettingsTab"))
) { notification in
@@ -130,45 +82,6 @@ struct SettingsWindowView: View {
}
}
private func applySettings() {
// Create a new AppSettings object with updated values
// This triggers the didSet observer in SettingsManager
let updatedSettings = AppSettings(
lookAwayTimer: TimerConfiguration(
enabled: lookAwayEnabled,
intervalSeconds: lookAwayIntervalMinutes * 60
),
lookAwayCountdownSeconds: lookAwayCountdownSeconds,
blinkTimer: TimerConfiguration(
enabled: blinkEnabled,
intervalSeconds: blinkIntervalMinutes * 60
),
postureTimer: TimerConfiguration(
enabled: postureEnabled,
intervalSeconds: postureIntervalMinutes * 60
),
userTimers: userTimers,
subtleReminderSize: subtleReminderSize,
hasCompletedOnboarding: settingsManager.settings.hasCompletedOnboarding,
launchAtLogin: launchAtLogin,
playSounds: settingsManager.settings.playSounds,
isAppStoreVersion: isAppStoreVersion
)
// Assign the entire settings object to trigger didSet and observers
settingsManager.settings = updatedSettings
do {
if launchAtLogin {
try LaunchAtLoginManager.enable()
} else {
try LaunchAtLoginManager.disable()
}
} catch {
print("Failed to set launch at login: \(error)")
}
}
private func closeWindow() {
if let window = NSApplication.shared.windows.first(where: { $0.title == "Settings" }) {
window.close()

View File

@@ -9,9 +9,7 @@ import AppKit
import SwiftUI
struct BlinkSetupView: View {
@Binding var enabled: Bool
@Binding var intervalMinutes: Int
var subtleReminderSize: ReminderSize = .medium
@ObservedObject var settingsManager: SettingsManager
@State private var previewWindowController: NSWindowController?
var body: some View {
@@ -59,10 +57,13 @@ struct BlinkSetupView: View {
GlassStyle.regular.tint(.accentColor), in: .rect(cornerRadius: 8))
VStack(alignment: .leading, spacing: 20) {
Toggle("Enable Blink Reminders", isOn: $enabled)
Toggle("Enable Blink Reminders", isOn: Binding(
get: { settingsManager.settings.blinkTimer.enabled },
set: { settingsManager.settings.blinkTimer.enabled = $0 }
))
.font(.headline)
if enabled {
if settingsManager.settings.blinkTimer.enabled {
VStack(alignment: .leading, spacing: 12) {
Text("Remind me every:")
.font(.subheadline)
@@ -71,11 +72,11 @@ struct BlinkSetupView: View {
HStack {
Slider(
value: Binding(
get: { Double(intervalMinutes) },
set: { intervalMinutes = Int($0) }
get: { Double(settingsManager.settings.blinkTimer.intervalSeconds / 60) },
set: { settingsManager.settings.blinkTimer.intervalSeconds = Int($0) * 60 }
), in: 1...20, step: 1)
Text("\(intervalMinutes) min")
Text("\(settingsManager.settings.blinkTimer.intervalSeconds / 60) min")
.frame(width: 60, alignment: .trailing)
.monospacedDigit()
}
@@ -85,9 +86,9 @@ struct BlinkSetupView: View {
.padding()
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 12))
if enabled {
if settingsManager.settings.blinkTimer.enabled {
Text(
"You will be subtly reminded every \(intervalMinutes) minutes to blink"
"You will be subtly reminded every \(settingsManager.settings.blinkTimer.intervalSeconds / 60) minutes to blink"
)
.font(.subheadline)
.foregroundColor(.secondary)
@@ -143,7 +144,9 @@ struct BlinkSetupView: View {
window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
window.acceptsMouseMovedEvents = true
let contentView = BlinkReminderView(sizePercentage: subtleReminderSize.percentage) {
let contentView = BlinkReminderView(
sizePercentage: settingsManager.settings.subtleReminderSize.percentage
) {
[weak window] in
window?.close()
}
@@ -159,16 +162,6 @@ struct BlinkSetupView: View {
}
}
#Preview("Blink Setup - Enabled") {
BlinkSetupView(
enabled: .constant(true),
intervalMinutes: .constant(5)
)
}
#Preview("Blink Setup - Disabled") {
BlinkSetupView(
enabled: .constant(false),
intervalMinutes: .constant(5)
)
#Preview("Blink Setup") {
BlinkSetupView(settingsManager: SettingsManager.shared)
}

View File

@@ -8,9 +8,7 @@
import SwiftUI
struct GeneralSetupView: View {
@Binding var launchAtLogin: Bool
@Binding var subtleReminderSize: ReminderSize
@Binding var isAppStoreVersion: Bool
@ObservedObject var settingsManager: SettingsManager
@ObservedObject var updateManager = UpdateManager.shared
var isOnboarding: Bool = true
@@ -44,9 +42,12 @@ struct GeneralSetupView: View {
.foregroundColor(.secondary)
}
Spacer()
Toggle("", isOn: $launchAtLogin)
Toggle("", isOn: Binding(
get: { settingsManager.settings.launchAtLogin },
set: { settingsManager.settings.launchAtLogin = $0 }
))
.labelsHidden()
.onChange(of: launchAtLogin) { isEnabled in
.onChange(of: settingsManager.settings.launchAtLogin) { isEnabled in
applyLaunchAtLoginSetting(enabled: isEnabled)
}
}
@@ -54,7 +55,7 @@ struct GeneralSetupView: View {
.glassEffectIfAvailable(GlassStyle.regular, in: .rect(cornerRadius: 12))
// Software Updates Section
if !isAppStoreVersion {
if !settingsManager.settings.isAppStoreVersion {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text("Software Updates")
@@ -102,12 +103,12 @@ struct GeneralSetupView: View {
HStack(spacing: 12) {
ForEach(ReminderSize.allCases, id: \.self) { size in
Button(action: {
subtleReminderSize = size
settingsManager.settings.subtleReminderSize = size
}) {
VStack(spacing: 8) {
Circle()
.fill(
subtleReminderSize == size
settingsManager.settings.subtleReminderSize == size
? Color.accentColor
: Color.secondary.opacity(0.3)
)
@@ -118,16 +119,16 @@ struct GeneralSetupView: View {
Text(size.displayName)
.font(.caption)
.fontWeight(
subtleReminderSize == size ? .semibold : .regular
settingsManager.settings.subtleReminderSize == size ? .semibold : .regular
)
.foregroundColor(
subtleReminderSize == size ? .primary : .secondary)
settingsManager.settings.subtleReminderSize == size ? .primary : .secondary)
}
.frame(maxWidth: .infinity, minHeight: 60)
.padding(.vertical, 12)
}
.glassEffectIfAvailable(
subtleReminderSize == size
settingsManager.settings.subtleReminderSize == size
? GlassStyle.regular.tint(.accentColor.opacity(0.3))
: GlassStyle.regular,
in: .rect(cornerRadius: 10)
@@ -173,7 +174,7 @@ struct GeneralSetupView: View {
.glassEffectIfAvailable(
GlassStyle.regular.interactive(), in: .rect(cornerRadius: 10))
if !isAppStoreVersion {
if !settingsManager.settings.isAppStoreVersion {
Button(action: {
if let url = URL(string: "https://buymeacoffee.com/mikefreno") {
NSWorkspace.shared.open(url)
@@ -239,9 +240,7 @@ struct GeneralSetupView: View {
#Preview("Settings Onboarding") {
GeneralSetupView(
launchAtLogin: .constant(false),
subtleReminderSize: .constant(.medium),
isAppStoreVersion: .constant(false),
settingsManager: SettingsManager.shared,
isOnboarding: true
)
}

View File

@@ -13,9 +13,7 @@ import SwiftUI
#endif
struct LookAwaySetupView: View {
@Binding var enabled: Bool
@Binding var intervalSettings: RangeChoice
@Binding var countdownSettings: RangeChoice
@ObservedObject var settingsManager: SettingsManager
@State private var previewWindowController: NSWindowController?
var body: some View {
@@ -62,9 +60,32 @@ struct LookAwaySetupView: View {
GlassStyle.regular.tint(.accentColor), in: .rect(cornerRadius: 8))
SliderSection(
intervalSettings: $intervalSettings,
countdownSettings: $countdownSettings,
enabled: $enabled,
intervalSettings: Binding(
get: {
RangeChoice(
val: settingsManager.settings.lookAwayTimer.intervalSeconds / 60,
range: Range(bounds: 5...60, step: 5)
)
},
set: { newValue in
settingsManager.settings.lookAwayTimer.intervalSeconds = (newValue.val ?? 20) * 60
}
),
countdownSettings: Binding(
get: {
RangeChoice(
val: settingsManager.settings.lookAwayCountdownSeconds,
range: Range(bounds: 5...60, step: 5)
)
},
set: { newValue in
settingsManager.settings.lookAwayCountdownSeconds = newValue.val ?? 20
}
),
enabled: Binding(
get: { settingsManager.settings.lookAwayTimer.enabled },
set: { settingsManager.settings.lookAwayTimer.enabled = $0 }
),
type: "Look away",
previewFunc: showPreviewWindow
)
@@ -93,7 +114,9 @@ struct LookAwaySetupView: View {
window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
window.acceptsMouseMovedEvents = true
let contentView = LookAwayReminderView(countdownSeconds: countdownSettings.val ?? 20) {
let contentView = LookAwayReminderView(
countdownSeconds: settingsManager.settings.lookAwayCountdownSeconds
) {
[weak window] in
window?.close()
}
@@ -109,11 +132,6 @@ struct LookAwaySetupView: View {
}
}
//TODO: add this back
/*#Preview("Look Away Setup View") {*/
/*LookAwaySetupView(*/
/*enabled: .constant(true),*/
/*intervalMinutes: .constant(20),*/
/*countdownSeconds: .constant(20)*/
/*)*/
/*}*/
#Preview("Look Away Setup View") {
LookAwaySetupView(settingsManager: SettingsManager.shared)
}

View File

@@ -9,9 +9,8 @@ import AppKit
import SwiftUI
struct PostureSetupView: View {
@Binding var enabled: Bool
@Binding var intervalSettings: RangeChoice
var subtleReminderSize: ReminderSize = .medium
@ObservedObject var settingsManager: SettingsManager
@State private var previewWindowController: NSWindowController?
var body: some View {
@@ -61,8 +60,22 @@ struct PostureSetupView: View {
GlassStyle.regular.tint(.accentColor), in: .rect(cornerRadius: 8))
SliderSection(
intervalSettings: $intervalSettings,
enabled: $enabled,
intervalSettings: Binding(
get: {
RangeChoice(
val: settingsManager.settings.postureTimer.intervalSeconds / 60,
range: Range(bounds: 5...60, step: 5)
)
},
set: { newValue in
settingsManager.settings.postureTimer.intervalSeconds = (newValue.val ?? 30) * 60
}
),
countdownSettings: nil,
enabled: Binding(
get: { settingsManager.settings.postureTimer.enabled },
set: { settingsManager.settings.postureTimer.enabled = $0 }
),
type: "Posture",
previewFunc: showPreviewWindow
)
@@ -91,7 +104,9 @@ struct PostureSetupView: View {
window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
window.acceptsMouseMovedEvents = true
let contentView = PostureReminderView(sizePercentage: subtleReminderSize.percentage) {
let contentView = PostureReminderView(
sizePercentage: settingsManager.settings.subtleReminderSize.percentage
) {
[weak window] in
window?.close()
}
@@ -107,16 +122,6 @@ struct PostureSetupView: View {
}
}
#Preview("Posture Setup - Enabled") {
PostureSetupView(
enabled: .constant(true),
intervalMinutes: .constant(30)
)
}
#Preview("Posture Setup - Disabled") {
PostureSetupView(
enabled: .constant(false),
intervalMinutes: .constant(30)
)
#Preview("Posture Setup") {
PostureSetupView(settingsManager: SettingsManager.shared)
}