From eebccc55d93382100a88fac7c3b641b9f949592d Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Sat, 10 Jan 2026 09:54:47 -0500 Subject: [PATCH] actual view trigger --- Gaze/GazeApp.swift | 20 ++- Gaze/Views/MenuBar/MenuBarContentView.swift | 115 ++++++++++++++---- Gaze/Views/Onboarding/BlinkSetupView.swift | 46 +++++++ Gaze/Views/Onboarding/LookAwaySetupView.swift | 48 +++++++- Gaze/Views/Onboarding/PostureSetupView.swift | 53 +++++--- .../Reminders/LookAwayReminderView.swift | 5 +- 6 files changed, 233 insertions(+), 54 deletions(-) diff --git a/Gaze/GazeApp.swift b/Gaze/GazeApp.swift index f152448..b909fc2 100644 --- a/Gaze/GazeApp.swift +++ b/Gaze/GazeApp.swift @@ -37,18 +37,16 @@ struct GazeApp: App { CommandGroup(replacing: .newItem) { } } - // Menu bar extra (always present once onboarding is complete) + // Menu bar extra (always present) MenuBarExtra("Gaze", systemImage: "eye.fill") { - if let timerEngine = appDelegate.timerEngine { - MenuBarContentView( - timerEngine: timerEngine, - settingsManager: settingsManager, - onQuit: { NSApplication.shared.terminate(nil) }, - onOpenSettings: { appDelegate.openSettings() }, - onOpenSettingsTab: { tab in appDelegate.openSettings(tab: tab) }, - onOpenOnboarding: { appDelegate.openOnboarding() } - ) - } + MenuBarContentView( + timerEngine: appDelegate.timerEngine, + settingsManager: settingsManager, + onQuit: { NSApplication.shared.terminate(nil) }, + onOpenSettings: { appDelegate.openSettings() }, + onOpenSettingsTab: { tab in appDelegate.openSettings(tab: tab) }, + onOpenOnboarding: { appDelegate.openOnboarding() } + ) } .menuBarExtraStyle(.window) } diff --git a/Gaze/Views/MenuBar/MenuBarContentView.swift b/Gaze/Views/MenuBar/MenuBarContentView.swift index 11adf6e..5158dc0 100644 --- a/Gaze/Views/MenuBar/MenuBarContentView.swift +++ b/Gaze/Views/MenuBar/MenuBarContentView.swift @@ -44,7 +44,7 @@ struct MenuBarHoverButtonStyle: ButtonStyle { } struct MenuBarContentView: View { - @ObservedObject var timerEngine: TimerEngine + var timerEngine: TimerEngine? @ObservedObject var settingsManager: SettingsManager @Environment(\.dismiss) private var dismiss var onQuit: () -> Void @@ -53,6 +53,92 @@ struct MenuBarContentView: View { var onOpenOnboarding: () -> Void var body: some View { + if !settingsManager.settings.hasCompletedOnboarding { + // Simplified view when onboarding is not complete + onboardingIncompleteView + } else if let timerEngine = timerEngine { + // Full view when onboarding is complete and timers are running + fullMenuBarView(timerEngine: timerEngine) + } else { + // Fallback view + EmptyView() + } + } + + private var onboardingIncompleteView: some View { + VStack(alignment: .leading, spacing: 0) { + // Header + HStack { + Image(systemName: "eye.fill") + .font(.title2) + .foregroundColor(.accentColor) + Text("Gaze") + .font(.title2) + .fontWeight(.semibold) + } + .padding() + + Divider() + + // Message + VStack(alignment: .leading, spacing: 12) { + Text("Welcome to Gaze!") + .font(.headline) + .padding(.horizontal) + .padding(.top, 16) + + Text("Please complete the onboarding to start using Gaze.") + .font(.subheadline) + .foregroundColor(.secondary) + .padding(.horizontal) + .padding(.bottom, 16) + } + + Divider() + + // Complete Onboarding Button + VStack(spacing: 4) { + Button(action: { + onOpenOnboarding() + }) { + HStack { + Image(systemName: "checkmark.circle.fill") + .foregroundColor(.accentColor) + Text("Complete Onboarding") + Spacer() + } + .padding(.horizontal, 8) + .padding(.vertical, 6) + } + .buttonStyle(MenuBarHoverButtonStyle()) + } + .padding(.vertical, 8) + .padding(.horizontal, 8) + + Divider() + + // Quit + Button(action: onQuit) { + HStack { + Image(systemName: "power") + .foregroundColor(.red) + Text("Quit Gaze") + Spacer() + } + .padding(.horizontal, 8) + .padding(.vertical, 6) + } + .buttonStyle(MenuBarHoverButtonStyle()) + .padding(.horizontal, 8) + .padding(.vertical, 8) + } + .frame(width: 300) + .onReceive(NotificationCenter.default.publisher(for: Notification.Name("CloseMenuBarPopover"))) { _ in + dismiss() + } + } + + private func fullMenuBarView(timerEngine: TimerEngine) -> some View { VStack(alignment: .leading, spacing: 0) { // Header HStack { @@ -117,33 +203,16 @@ struct MenuBarContentView: View { // Controls VStack(spacing: 4) { - // Show "Complete Onboarding" button if not completed - if !settingsManager.settings.hasCompletedOnboarding { - Button(action: { - onOpenOnboarding() - }) { - HStack { - Image(systemName: "checkmark.circle.fill") - .foregroundColor(.accentColor) - Text("Complete Onboarding") - Spacer() - } - .padding(.horizontal, 8) - .padding(.vertical, 6) - } - .buttonStyle(MenuBarHoverButtonStyle()) - } - Button(action: { - if timerEngine.timerStates.values.first?.isPaused == true { + if isPaused(timerEngine: timerEngine) { timerEngine.resume() } else { timerEngine.pause() } }) { HStack { - Image(systemName: isPaused ? "play.circle" : "pause.circle") - Text(isPaused ? "Resume All Timers" : "Pause All Timers") + Image(systemName: isPaused(timerEngine: timerEngine) ? "play.circle" : "pause.circle") + Text(isPaused(timerEngine: timerEngine) ? "Resume All Timers" : "Pause All Timers") Spacer() } .padding(.horizontal, 8) @@ -190,7 +259,7 @@ struct MenuBarContentView: View { } } - private var isPaused: Bool { + private func isPaused(timerEngine: TimerEngine) -> Bool { timerEngine.timerStates.values.first?.isPaused ?? false } } @@ -441,4 +510,4 @@ struct UserTimerStatusRow: View { onOpenSettingsTab: { _ in }, onOpenOnboarding: {} ) -} \ No newline at end of file +} diff --git a/Gaze/Views/Onboarding/BlinkSetupView.swift b/Gaze/Views/Onboarding/BlinkSetupView.swift index 1b04a4a..a73725a 100644 --- a/Gaze/Views/Onboarding/BlinkSetupView.swift +++ b/Gaze/Views/Onboarding/BlinkSetupView.swift @@ -6,10 +6,12 @@ // import SwiftUI +import AppKit struct BlinkSetupView: View { @Binding var enabled: Bool @Binding var intervalMinutes: Int + @State private var previewWindowController: NSWindowController? var body: some View { VStack(spacing: 0) { @@ -94,6 +96,20 @@ struct BlinkSetupView: View { .font(.caption) .foregroundColor(.secondary) } + + // Preview button + Button(action: { + showPreviewWindow() + }) { + HStack { + Image(systemName: "eye") + .foregroundColor(.accentColor) + Text("Preview Reminder") + .font(.headline) + } + .frame(maxWidth: .infinity, minHeight: 44, alignment: .center) + } + .glassEffect(.regular.tint(.accentColor).interactive(), in: .rect(cornerRadius: 10)) } Spacer() @@ -102,6 +118,36 @@ struct BlinkSetupView: View { .padding() .background(.clear) } + + private func showPreviewWindow() { + guard let screen = NSScreen.main else { return } + + let window = NSWindow( + contentRect: screen.frame, + styleMask: [.borderless, .fullSizeContentView], + backing: .buffered, + defer: false + ) + + window.level = .floating + window.isOpaque = false + window.backgroundColor = .clear + window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary] + window.acceptsMouseMovedEvents = true + + let contentView = BlinkReminderView(sizePercentage: 15.0) { [weak window] in + window?.close() + } + + window.contentView = NSHostingView(rootView: contentView) + window.makeFirstResponder(window.contentView) + + let windowController = NSWindowController(window: window) + windowController.showWindow(nil) + window.makeKeyAndOrderFront(nil) + + previewWindowController = windowController + } } #Preview("Blink Setup - Enabled") { diff --git a/Gaze/Views/Onboarding/LookAwaySetupView.swift b/Gaze/Views/Onboarding/LookAwaySetupView.swift index 9ed5742..fd18283 100644 --- a/Gaze/Views/Onboarding/LookAwaySetupView.swift +++ b/Gaze/Views/Onboarding/LookAwaySetupView.swift @@ -6,17 +6,17 @@ // import SwiftUI +import AppKit #if os(iOS) import UIKit -#else - import AppKit #endif struct LookAwaySetupView: View { @Binding var enabled: Bool @Binding var intervalMinutes: Int @Binding var countdownSeconds: Int + @State private var previewWindowController: NSWindowController? var body: some View { VStack(spacing: 0) { @@ -117,6 +117,20 @@ struct LookAwaySetupView: View { .font(.caption) .foregroundColor(.secondary) } + + // Preview button + Button(action: { + showPreviewWindow() + }) { + HStack { + Image(systemName: "eye") + .foregroundColor(.accentColor) + Text("Preview Reminder") + .font(.headline) + } + .frame(maxWidth: .infinity, minHeight: 44, alignment: .center) + } + .glassEffect(.regular.tint(.accentColor).interactive(), in: .rect(cornerRadius: 10)) } Spacer() @@ -125,6 +139,36 @@ struct LookAwaySetupView: View { .padding() .background(.clear) } + + private func showPreviewWindow() { + guard let screen = NSScreen.main else { return } + + let window = NSWindow( + contentRect: screen.frame, + styleMask: [.borderless, .fullSizeContentView], + backing: .buffered, + defer: false + ) + + window.level = .floating + window.isOpaque = false + window.backgroundColor = .clear + window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary] + window.acceptsMouseMovedEvents = true + + let contentView = LookAwayReminderView(countdownSeconds: countdownSeconds) { [weak window] in + window?.close() + } + + window.contentView = NSHostingView(rootView: contentView) + window.makeFirstResponder(window.contentView) + + let windowController = NSWindowController(window: window) + windowController.showWindow(nil) + window.makeKeyAndOrderFront(nil) + + previewWindowController = windowController + } } #Preview("Look Away Setup View") { diff --git a/Gaze/Views/Onboarding/PostureSetupView.swift b/Gaze/Views/Onboarding/PostureSetupView.swift index fefae65..7636f8f 100644 --- a/Gaze/Views/Onboarding/PostureSetupView.swift +++ b/Gaze/Views/Onboarding/PostureSetupView.swift @@ -6,12 +6,12 @@ // import SwiftUI +import AppKit struct PostureSetupView: View { @Binding var enabled: Bool @Binding var intervalMinutes: Int - - @State private var isPreviewShowing = false + @State private var previewWindowController: NSWindowController? var body: some View { VStack(spacing: 0) { @@ -99,26 +99,17 @@ struct PostureSetupView: View { // Preview button Button(action: { - isPreviewShowing = true + showPreviewWindow() }) { HStack { Image(systemName: "eye") - .foregroundColor(.white) + .foregroundColor(.accentColor) Text("Preview Reminder") - .foregroundColor(.white) + .font(.headline) } - .padding(.horizontal, 20) - .padding(.vertical, 10) - .background(.blue) - .cornerRadius(8) - } - .fullScreenCover(isPresented: $isPreviewShowing) { - PostureReminderView(sizePercentage: 10.0, onDismiss: { - isPreviewShowing = false - }) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(.black.opacity(0.85)) + .frame(maxWidth: .infinity, minHeight: 44, alignment: .center) } + .glassEffect(.regular.tint(.accentColor).interactive(), in: .rect(cornerRadius: 10)) } Spacer() @@ -127,6 +118,36 @@ struct PostureSetupView: View { .padding() .background(.clear) } + + private func showPreviewWindow() { + guard let screen = NSScreen.main else { return } + + let window = NSWindow( + contentRect: screen.frame, + styleMask: [.borderless, .fullSizeContentView], + backing: .buffered, + defer: false + ) + + window.level = .floating + window.isOpaque = false + window.backgroundColor = .clear + window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary] + window.acceptsMouseMovedEvents = true + + let contentView = PostureReminderView(sizePercentage: 10.0) { [weak window] in + window?.close() + } + + window.contentView = NSHostingView(rootView: contentView) + window.makeFirstResponder(window.contentView) + + let windowController = NSWindowController(window: window) + windowController.showWindow(nil) + window.makeKeyAndOrderFront(nil) + + previewWindowController = windowController + } } #Preview("Posture Setup - Enabled") { diff --git a/Gaze/Views/Reminders/LookAwayReminderView.swift b/Gaze/Views/Reminders/LookAwayReminderView.swift index 123690d..7603faf 100644 --- a/Gaze/Views/Reminders/LookAwayReminderView.swift +++ b/Gaze/Views/Reminders/LookAwayReminderView.swift @@ -25,8 +25,9 @@ struct LookAwayReminderView: View { var body: some View { ZStack { - // Semi-transparent dark background - Color.black.opacity(0.85) + VisualEffectView(material: .hudWindow, blendingMode: .behindWindow) + .ignoresSafeArea() + Color.black.opacity(0.5) .ignoresSafeArea() VStack(spacing: 40) {