From 09d2e49225abe50912511e6b70b277ab1c6adac2 Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Sun, 11 Jan 2026 16:22:06 -0500 Subject: [PATCH] fix: timers update properly, cleanup coloring --- Gaze/AppDelegate.swift | 4 +- Gaze/GazeApp.swift | 4 +- Gaze/Views/MenuBar/MenuBarContentView.swift | 84 +++++++++++++++------ 3 files changed, 63 insertions(+), 29 deletions(-) diff --git a/Gaze/AppDelegate.swift b/Gaze/AppDelegate.swift index 425ab51..687627f 100644 --- a/Gaze/AppDelegate.swift +++ b/Gaze/AppDelegate.swift @@ -10,8 +10,8 @@ import AppKit import Combine @MainActor -class AppDelegate: NSObject, NSApplicationDelegate { - var timerEngine: TimerEngine? +class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { + @Published var timerEngine: TimerEngine? private var settingsManager: SettingsManager? private var reminderWindowController: NSWindowController? private var settingsWindowController: NSWindowController? diff --git a/Gaze/GazeApp.swift b/Gaze/GazeApp.swift index 7e72991..23ce3a5 100644 --- a/Gaze/GazeApp.swift +++ b/Gaze/GazeApp.swift @@ -39,8 +39,8 @@ struct GazeApp: App { // Menu bar extra (always present) MenuBarExtra("Gaze", systemImage: "eye.fill") { - MenuBarContentView( - timerEngine: appDelegate.timerEngine, + MenuBarContentWrapper( + appDelegate: appDelegate, settingsManager: settingsManager, onQuit: { NSApplication.shared.terminate(nil) }, onOpenSettings: { appDelegate.openSettings() }, diff --git a/Gaze/Views/MenuBar/MenuBarContentView.swift b/Gaze/Views/MenuBar/MenuBarContentView.swift index fc555fa..eb67b7c 100644 --- a/Gaze/Views/MenuBar/MenuBarContentView.swift +++ b/Gaze/Views/MenuBar/MenuBarContentView.swift @@ -7,6 +7,27 @@ import SwiftUI +// Wrapper to properly observe AppDelegate changes in MenuBarExtra +struct MenuBarContentWrapper: View { + @ObservedObject var appDelegate: AppDelegate + @ObservedObject var settingsManager: SettingsManager + var onQuit: () -> Void + var onOpenSettings: () -> Void + var onOpenSettingsTab: (Int) -> Void + var onOpenOnboarding: () -> Void + + var body: some View { + MenuBarContentView( + timerEngine: appDelegate.timerEngine, + settingsManager: settingsManager, + onQuit: onQuit, + onOpenSettings: onOpenSettings, + onOpenSettingsTab: onOpenSettingsTab, + onOpenOnboarding: onOpenOnboarding + ) + } +} + // Hover button style for menubar items struct MenuBarButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { @@ -29,8 +50,11 @@ struct MenuBarHoverButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label + .foregroundColor(isHovered ? .white : .primary) .glassEffectIfAvailable( - isHovered ? GlassStyle.regular.tint(.accentColor.opacity(0.5)).interactive() : GlassStyle.regular, + isHovered + ? GlassStyle.regular.tint(.accentColor).interactive() + : GlassStyle.regular, in: .rect(cornerRadius: 6) ) .contentShape(Rectangle()) @@ -164,10 +188,10 @@ struct MenuBarContentView: View { .padding(.top, 8) ForEach(TimerType.allCases) { timerType in - if let state = timerEngine.timerStates[timerType] { + if timerEngine.timerStates[timerType] != nil { TimerStatusRow( type: timerType, - state: state, + timerEngine: timerEngine, onSkip: { timerEngine.skipNext(type: timerType) }, @@ -275,7 +299,7 @@ struct MenuBarContentView: View { struct TimerStatusRow: View { let type: TimerType - let state: TimerState + @ObservedObject var timerEngine: TimerEngine var onSkip: () -> Void var onDevTrigger: (() -> Void)? = nil var onTap: (() -> Void)? = nil @@ -283,21 +307,28 @@ struct TimerStatusRow: View { @State private var isHoveredDevTrigger = false @State private var isHoveredBody = false + private var state: TimerState? { + timerEngine.timerStates[type] + } + var body: some View { HStack { HStack { Image(systemName: type.iconName) - .foregroundColor(iconColor) + .foregroundColor(isHoveredBody ? .white : iconColor) .frame(width: 20) VStack(alignment: .leading, spacing: 2) { Text(type.displayName) .font(.subheadline) .fontWeight(.medium) - Text(timeRemaining) - .font(.caption) - .foregroundColor(.secondary) - .monospacedDigit() + .foregroundColor(isHoveredBody ? .white : .primary) + if let state = state { + Text(timeRemaining(state)) + .font(.caption) + .foregroundColor(isHoveredBody ? .white.opacity(0.8) : .secondary) + .monospacedDigit() + } } Spacer() @@ -312,13 +343,14 @@ struct TimerStatusRow: View { Button(action: onDevTrigger) { Image(systemName: "bolt.fill") .font(.caption) - .foregroundColor(.yellow) + .foregroundColor(isHoveredDevTrigger ? .white : .yellow) .padding(6) .contentShape(Circle()) } .buttonStyle(.plain) .glassEffectIfAvailable( - isHoveredDevTrigger ? GlassStyle.regular.tint(.yellow.opacity(0.5)) : GlassStyle.regular, + isHoveredDevTrigger + ? GlassStyle.regular.tint(.yellow) : GlassStyle.regular, in: .circle ) .help("Trigger \(type.displayName) reminder now (dev)") @@ -331,13 +363,14 @@ struct TimerStatusRow: View { Button(action: onSkip) { Image(systemName: "forward.fill") .font(.caption) - .foregroundColor(.accentColor) + .foregroundColor(isHoveredSkip ? .white : .accentColor) .padding(6) .contentShape(Circle()) } .buttonStyle(.plain) .glassEffectIfAvailable( - isHoveredSkip ? GlassStyle.regular.tint(.accentColor.opacity(0.5)) : GlassStyle.regular, + isHoveredSkip + ? GlassStyle.regular.tint(.accentColor) : GlassStyle.regular, in: .circle ) .help("Skip to next \(type.displayName) reminder") @@ -348,7 +381,7 @@ struct TimerStatusRow: View { .padding(.horizontal, 8) .padding(.vertical, 6) .glassEffectIfAvailable( - isHoveredBody ? GlassStyle.regular.tint(.accentColor.opacity(0.5)) : GlassStyle.regular, + isHoveredBody ? GlassStyle.regular.tint(.accentColor) : GlassStyle.regular, in: .rect(cornerRadius: 6) ) .padding(.horizontal, 8) @@ -370,7 +403,7 @@ struct TimerStatusRow: View { } } - private var timeRemaining: String { + private func timeRemaining(_ state: TimerState) -> String { let seconds = state.remainingSeconds let minutes = seconds / 60 let remainingSeconds = seconds % 60 @@ -396,21 +429,21 @@ struct InactiveTimerRow: View { Button(action: onTap) { HStack { Image(systemName: type.iconName) - .foregroundColor(.secondary) + .foregroundColor(isHovered ? .white : .secondary) .frame(width: 20) VStack(alignment: .leading, spacing: 2) { Text(type.displayName) .font(.subheadline) .fontWeight(.medium) - .foregroundColor(.secondary) + .foregroundColor(isHovered ? .white : .secondary) } Spacer() Image(systemName: "plus.circle") .font(.title3) - .foregroundColor(.accentColor) + .foregroundColor(isHovered ? .white : .accentColor) .padding(6) } .padding(.horizontal, 8) @@ -419,7 +452,7 @@ struct InactiveTimerRow: View { } .buttonStyle(.plain) .glassEffectIfAvailable( - isHovered ? GlassStyle.regular.tint(.accentColor.opacity(0.5)) : GlassStyle.regular, + isHovered ? GlassStyle.regular.tint(.accentColor) : GlassStyle.regular, in: .rect(cornerRadius: 6) ) .padding(.horizontal, 8) @@ -440,28 +473,29 @@ struct UserTimerStatusRow: View { Button(action: onTap) { HStack { Circle() - .fill(timer.color) + .fill(isHovered ? .white : timer.color) .frame(width: 8, height: 8) Image(systemName: "clock.fill") - .foregroundColor(timer.color) + .foregroundColor(isHovered ? .white : timer.color) .frame(width: 20) VStack(alignment: .leading, spacing: 2) { Text(timer.title) .font(.subheadline) .fontWeight(.medium) + .foregroundColor(isHovered ? .white : .primary) .lineLimit(1) if let state = state { Text(timeRemaining(state)) .font(.caption) - .foregroundColor(.secondary) + .foregroundColor(isHovered ? .white.opacity(0.8) : .secondary) .monospacedDigit() } else { Text(timer.enabled ? "Not active" : "Disabled") .font(.caption) - .foregroundColor(.secondary) + .foregroundColor(isHovered ? .white.opacity(0.8) : .secondary) } } @@ -469,7 +503,7 @@ struct UserTimerStatusRow: View { Image(systemName: timer.type == .subtle ? "eye.circle" : "rectangle.on.rectangle") .font(.caption) - .foregroundColor(.secondary) + .foregroundColor(isHovered ? .white : .secondary) .padding(6) } .padding(.horizontal, 8) @@ -478,7 +512,7 @@ struct UserTimerStatusRow: View { } .buttonStyle(.plain) .glassEffectIfAvailable( - isHovered ? GlassStyle.regular.tint(timer.color.opacity(0.3)) : GlassStyle.regular, + isHovered ? GlassStyle.regular.tint(timer.color) : GlassStyle.regular, in: .rect(cornerRadius: 6) ) .padding(.horizontal, 8)