fix: bad fix for multi-window
This commit is contained in:
@@ -18,6 +18,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
|||||||
private var updateManager: UpdateManager?
|
private var updateManager: UpdateManager?
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
private var hasStartedTimers = false
|
private var hasStartedTimers = false
|
||||||
|
private var isSettingsWindowOpen = false
|
||||||
|
private var isOnboardingWindowOpen = false
|
||||||
|
|
||||||
// Logging manager
|
// Logging manager
|
||||||
private let logger = LoggingManager.shared
|
private let logger = LoggingManager.shared
|
||||||
@@ -26,11 +28,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
|||||||
private var settingsManager: any SettingsProviding {
|
private var settingsManager: any SettingsProviding {
|
||||||
serviceContainer.settingsManager
|
serviceContainer.settingsManager
|
||||||
}
|
}
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
self.serviceContainer = ServiceContainer.shared
|
self.serviceContainer = ServiceContainer.shared
|
||||||
self.windowManager = WindowManager.shared
|
self.windowManager = WindowManager.shared
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
// Setup window close observers
|
||||||
|
setupWindowCloseObservers()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initializer for testing with injectable dependencies
|
/// Initializer for testing with injectable dependencies
|
||||||
@@ -39,7 +44,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
|||||||
self.windowManager = windowManager
|
self.windowManager = windowManager
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||||
// Set activation policy to hide dock icon
|
// Set activation policy to hide dock icon
|
||||||
NSApplication.shared.setActivationPolicy(.accessory)
|
NSApplication.shared.setActivationPolicy(.accessory)
|
||||||
@@ -54,6 +59,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
|||||||
// Setup smart mode services through container
|
// Setup smart mode services through container
|
||||||
serviceContainer.setupSmartModeServices()
|
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
|
// Initialize update manager after onboarding is complete
|
||||||
if settingsManager.settings.hasCompletedOnboarding {
|
if settingsManager.settings.hasCompletedOnboarding {
|
||||||
updateManager = UpdateManager.shared
|
updateManager = UpdateManager.shared
|
||||||
@@ -206,7 +217,20 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func openSettings(tab: Int = 0) {
|
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()
|
handleMenuDismissal()
|
||||||
|
isSettingsWindowOpen = true
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
@@ -215,7 +239,18 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func openOnboarding() {
|
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()
|
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
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
@@ -227,5 +262,32 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
|
|||||||
NotificationCenter.default.post(name: Notification.Name("CloseMenuBarPopover"), object: nil)
|
NotificationCenter.default.post(name: Notification.Name("CloseMenuBarPopover"), object: nil)
|
||||||
windowManager.dismissOverlayReminder()
|
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
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,8 +39,16 @@ final class OnboardingWindowPresenter {
|
|||||||
windowController = nil
|
windowController = nil
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure the window is brought to front and focused properly for menu bar apps
|
||||||
window.makeKeyAndOrderFront(nil)
|
window.makeKeyAndOrderFront(nil)
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
|
|
||||||
|
// Additional focus handling for menu bar applications
|
||||||
|
if let window = windowController?.window {
|
||||||
|
window.makeMain()
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +96,9 @@ final class OnboardingWindowPresenter {
|
|||||||
NotificationCenter.default.removeObserver(closeObserver)
|
NotificationCenter.default.removeObserver(closeObserver)
|
||||||
}
|
}
|
||||||
self?.closeObserver = nil
|
self?.closeObserver = nil
|
||||||
|
|
||||||
|
// Notify AppDelegate that onboarding window closed
|
||||||
|
NotificationCenter.default.post(name: Notification.Name("OnboardingWindowDidClose"), object: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,9 @@ final class SettingsWindowPresenter {
|
|||||||
Task { @MainActor [weak self] in
|
Task { @MainActor [weak self] in
|
||||||
self?.windowController = nil
|
self?.windowController = nil
|
||||||
self?.removeCloseObserver()
|
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
|
#if DEBUG
|
||||||
private func retriggerOnboarding() {
|
private func retriggerOnboarding() {
|
||||||
OnboardingWindowPresenter.shared.close()
|
|
||||||
SettingsWindowPresenter.shared.close()
|
SettingsWindowPresenter.shared.close()
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
||||||
OnboardingWindowPresenter.shared.show(settingsManager: self.settingsManager)
|
settingsManager.settings.hasCompletedOnboarding = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -88,24 +88,27 @@ struct MenuBarContentView: View {
|
|||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
.padding(.top, 8)
|
.padding(.top, 8)
|
||||||
|
|
||||||
ForEach(getSortedTimerIdentifiers(timerEngine: timerEngine), id: \.self) {
|
ForEach(
|
||||||
|
timerEngine.map { getSortedTimerIdentifiers(timerEngine: $0) } ?? [],
|
||||||
|
id: \.self
|
||||||
|
) {
|
||||||
identifier in
|
identifier in
|
||||||
if timerEngine.timerStates[identifier] != nil {
|
if let engine = timerEngine, engine.timerStates[identifier] != nil {
|
||||||
TimerStatusRowWithIndividualControls(
|
TimerStatusRowWithIndividualControls(
|
||||||
identifier: identifier,
|
identifier: identifier,
|
||||||
timerEngine: timerEngine,
|
timerEngine: engine,
|
||||||
settingsManager: settingsManager,
|
settingsManager: settingsManager,
|
||||||
onSkip: {
|
onSkip: {
|
||||||
timerEngine.skipNext(identifier: identifier)
|
engine.skipNext(identifier: identifier)
|
||||||
},
|
},
|
||||||
onDevTrigger: {
|
onDevTrigger: {
|
||||||
timerEngine.triggerReminder(for: identifier)
|
engine.triggerReminder(for: identifier)
|
||||||
},
|
},
|
||||||
onTogglePause: { isPaused in
|
onTogglePause: { isPaused in
|
||||||
if isPaused {
|
if isPaused {
|
||||||
timerEngine.pauseTimer(identifier: identifier)
|
engine.pauseTimer(identifier: identifier)
|
||||||
} else {
|
} else {
|
||||||
timerEngine.resumeTimer(identifier: identifier)
|
engine.resumeTimer(identifier: identifier)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onTap: {
|
onTap: {
|
||||||
@@ -127,18 +130,21 @@ struct MenuBarContentView: View {
|
|||||||
// Controls
|
// Controls
|
||||||
VStack(spacing: 4) {
|
VStack(spacing: 4) {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
if isAllPaused(timerEngine: timerEngine) {
|
if let engine = timerEngine {
|
||||||
timerEngine.resume()
|
if isAllPaused(timerEngine: engine) {
|
||||||
} else {
|
engine.resume()
|
||||||
timerEngine.pause()
|
} else {
|
||||||
|
engine.pause()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
HStack {
|
HStack {
|
||||||
Image(
|
Image(
|
||||||
systemName: isAllPaused(timerEngine: timerEngine)
|
systemName: timerEngine.map { isAllPaused(timerEngine: $0) }
|
||||||
|
?? false
|
||||||
? "play.circle" : "pause.circle")
|
? "play.circle" : "pause.circle")
|
||||||
Text(
|
Text(
|
||||||
isAllPaused(timerEngine: timerEngine)
|
timerEngine.map { isAllPaused(timerEngine: $0) } ?? false
|
||||||
? "Resume All Timers" : "Pause All Timers")
|
? "Resume All Timers" : "Pause All Timers")
|
||||||
Spacer()
|
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
|
// 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 }
|
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
|
guard let engine = timerEngine else { return [] }
|
||||||
|
return engine.timerStates.keys.sorted { id1, id2 in
|
||||||
// Sort built-in timers before user timers
|
// Sort built-in timers before user timers
|
||||||
switch (id1, id2) {
|
switch (id1, id2) {
|
||||||
case (.builtIn(let t1), .builtIn(let t2)):
|
case (.builtIn(let t1), .builtIn(let t2)):
|
||||||
|
|||||||
23
run
23
run
@@ -102,20 +102,13 @@ launch_app() {
|
|||||||
if [ -d "$app_path" ]; then
|
if [ -d "$app_path" ]; then
|
||||||
echo "🚀 Launching: $app_path"
|
echo "🚀 Launching: $app_path"
|
||||||
|
|
||||||
if [ "$VERBOSE" = true ]; then
|
echo "📝 Capturing application logs (Ctrl+C to stop - won't kill app)..."
|
||||||
echo "📝 Capturing application logs in terminal (Ctrl+C to stop)..."
|
open "$app_path" &
|
||||||
open "$app_path" &
|
|
||||||
sleep 2
|
sleep 2
|
||||||
|
echo "================================================================"
|
||||||
echo "Logs from Gaze.app will appear below (Ctrl+C to stop):"
|
/usr/bin/log stream --predicate "subsystem contains \"$APP_SUBSYSTEM\"" \
|
||||||
echo "================================================================"
|
--style compact 2>/dev/null
|
||||||
/usr/bin/log stream --predicate "subsystem contains \"$APP_SUBSYSTEM\"" \
|
|
||||||
--style compact 2>/dev/null
|
|
||||||
echo "================================================================"
|
|
||||||
echo "Application runtime logging stopped."
|
|
||||||
else
|
|
||||||
open "$app_path"
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
echo "⚠️ App not found at expected location, trying fallback..."
|
echo "⚠️ App not found at expected location, trying fallback..."
|
||||||
open "$HOME/Library/Developer/Xcode/DerivedData/Gaze-*/Build/Products/Debug/Gaze.app"
|
open "$HOME/Library/Developer/Xcode/DerivedData/Gaze-*/Build/Products/Debug/Gaze.app"
|
||||||
@@ -205,8 +198,6 @@ done
|
|||||||
# Default to run if no action specified
|
# Default to run if no action specified
|
||||||
if [ -z "$ACTION" ]; then
|
if [ -z "$ACTION" ]; then
|
||||||
ACTION="run"
|
ACTION="run"
|
||||||
# Default run action is always verbose with full logging
|
|
||||||
VERBOSE=true
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Main execution
|
# Main execution
|
||||||
|
|||||||
Reference in New Issue
Block a user