fix: bad fix for multi-window

This commit is contained in:
Michael Freno
2026-01-15 21:49:27 -05:00
parent d7d009d27a
commit 77bc2f9a92
5 changed files with 111 additions and 37 deletions

View File

@@ -18,6 +18,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
private var updateManager: UpdateManager?
private var cancellables = Set<AnyCancellable>()
private var hasStartedTimers = false
private var isSettingsWindowOpen = false
private var isOnboardingWindowOpen = false
// Logging manager
private let logger = LoggingManager.shared
@@ -26,11 +28,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
private var settingsManager: any SettingsProviding {
serviceContainer.settingsManager
}
override init() {
self.serviceContainer = ServiceContainer.shared
self.windowManager = WindowManager.shared
super.init()
// Setup window close observers
setupWindowCloseObservers()
}
/// Initializer for testing with injectable dependencies
@@ -39,7 +44,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
self.windowManager = windowManager
super.init()
}
func applicationDidFinishLaunching(_ notification: Notification) {
// Set activation policy to hide dock icon
NSApplication.shared.setActivationPolicy(.accessory)
@@ -54,6 +59,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
// Setup smart mode services through container
serviceContainer.setupSmartModeServices()
// Check if onboarding needs to be shown automatically
if !settingsManager.settings.hasCompletedOnboarding {
// Set the flag to indicate we expect an onboarding window
isOnboardingWindowOpen = true
}
// Initialize update manager after onboarding is complete
if settingsManager.settings.hasCompletedOnboarding {
updateManager = UpdateManager.shared
@@ -206,7 +217,20 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
}
func openSettings(tab: Int = 0) {
// If settings window is already open, focus it instead of opening new one
if isSettingsWindowOpen {
// Try to focus existing window
DispatchQueue.main.async {
NotificationCenter.default.post(
name: Notification.Name("SwitchToSettingsTab"),
object: tab
)
}
return
}
handleMenuDismissal()
isSettingsWindowOpen = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
guard let self else { return }
@@ -215,7 +239,18 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
}
func openOnboarding() {
// If onboarding window is already open, focus it instead of opening new one
if isOnboardingWindowOpen {
// Try to activate existing window
DispatchQueue.main.async {
OnboardingWindowPresenter.shared.activateIfPresent()
}
return
}
handleMenuDismissal()
// Explicitly set the flag to true when we're about to show the onboarding window
isOnboardingWindowOpen = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
guard let self else { return }
@@ -227,5 +262,32 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
NotificationCenter.default.post(name: Notification.Name("CloseMenuBarPopover"), object: nil)
windowManager.dismissOverlayReminder()
}
private func setupWindowCloseObservers() {
// Observe settings window closing
NotificationCenter.default.addObserver(
self,
selector: #selector(settingsWindowDidClose),
name: Notification.Name("SettingsWindowDidClose"),
object: nil
)
// Observe onboarding window closing
NotificationCenter.default.addObserver(
self,
selector: #selector(onboardingWindowDidClose),
name: Notification.Name("OnboardingWindowDidClose"),
object: nil
)
}
@objc private func settingsWindowDidClose() {
isSettingsWindowOpen = false
}
@objc private func onboardingWindowDidClose() {
// Reset the flag when we receive the close notification
isOnboardingWindowOpen = false
}
}

View File

@@ -39,8 +39,16 @@ final class OnboardingWindowPresenter {
windowController = nil
return false
}
// Ensure the window is brought to front and focused properly for menu bar apps
window.makeKeyAndOrderFront(nil)
NSApp.activate(ignoringOtherApps: true)
// Additional focus handling for menu bar applications
if let window = windowController?.window {
window.makeMain()
}
return true
}
@@ -88,6 +96,9 @@ final class OnboardingWindowPresenter {
NotificationCenter.default.removeObserver(closeObserver)
}
self?.closeObserver = nil
// Notify AppDelegate that onboarding window closed
NotificationCenter.default.post(name: Notification.Name("OnboardingWindowDidClose"), object: nil)
}
}

View File

@@ -90,6 +90,9 @@ final class SettingsWindowPresenter {
Task { @MainActor [weak self] in
self?.windowController = nil
self?.removeCloseObserver()
// Notify AppDelegate that settings window closed
NotificationCenter.default.post(name: Notification.Name("SettingsWindowDidClose"), object: nil)
}
}
}
@@ -206,11 +209,10 @@ struct SettingsWindowView: View {
#if DEBUG
private func retriggerOnboarding() {
OnboardingWindowPresenter.shared.close()
SettingsWindowPresenter.shared.close()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
OnboardingWindowPresenter.shared.show(settingsManager: self.settingsManager)
settingsManager.settings.hasCompletedOnboarding = false
}
}
#endif

View File

@@ -88,24 +88,27 @@ struct MenuBarContentView: View {
.padding(.horizontal)
.padding(.top, 8)
ForEach(getSortedTimerIdentifiers(timerEngine: timerEngine), id: \.self) {
ForEach(
timerEngine.map { getSortedTimerIdentifiers(timerEngine: $0) } ?? [],
id: \.self
) {
identifier in
if timerEngine.timerStates[identifier] != nil {
if let engine = timerEngine, engine.timerStates[identifier] != nil {
TimerStatusRowWithIndividualControls(
identifier: identifier,
timerEngine: timerEngine,
timerEngine: engine,
settingsManager: settingsManager,
onSkip: {
timerEngine.skipNext(identifier: identifier)
engine.skipNext(identifier: identifier)
},
onDevTrigger: {
timerEngine.triggerReminder(for: identifier)
engine.triggerReminder(for: identifier)
},
onTogglePause: { isPaused in
if isPaused {
timerEngine.pauseTimer(identifier: identifier)
engine.pauseTimer(identifier: identifier)
} else {
timerEngine.resumeTimer(identifier: identifier)
engine.resumeTimer(identifier: identifier)
}
},
onTap: {
@@ -127,18 +130,21 @@ struct MenuBarContentView: View {
// Controls
VStack(spacing: 4) {
Button(action: {
if isAllPaused(timerEngine: timerEngine) {
timerEngine.resume()
} else {
timerEngine.pause()
if let engine = timerEngine {
if isAllPaused(timerEngine: engine) {
engine.resume()
} else {
engine.pause()
}
}
}) {
HStack {
Image(
systemName: isAllPaused(timerEngine: timerEngine)
systemName: timerEngine.map { isAllPaused(timerEngine: $0) }
?? false
? "play.circle" : "pause.circle")
Text(
isAllPaused(timerEngine: timerEngine)
timerEngine.map { isAllPaused(timerEngine: $0) } ?? false
? "Resume All Timers" : "Pause All Timers")
Spacer()
}
@@ -217,14 +223,16 @@ struct MenuBarContentView: View {
}
}
private func isAllPaused(timerEngine: TimerEngine) -> Bool {
private func isAllPaused(timerEngine: TimerEngine?) -> Bool {
// Check if all timers are paused
let activeStates = timerEngine.timerStates.values.filter { $0.isActive }
guard let engine = timerEngine else { return false }
let activeStates = engine.timerStates.values.filter { $0.isActive }
return !activeStates.isEmpty && activeStates.allSatisfy { $0.isPaused }
}
private func getSortedTimerIdentifiers(timerEngine: TimerEngine) -> [TimerIdentifier] {
return timerEngine.timerStates.keys.sorted { id1, id2 in
private func getSortedTimerIdentifiers(timerEngine: TimerEngine?) -> [TimerIdentifier] {
guard let engine = timerEngine else { return [] }
return engine.timerStates.keys.sorted { id1, id2 in
// Sort built-in timers before user timers
switch (id1, id2) {
case (.builtIn(let t1), .builtIn(let t2)):