actual view trigger

This commit is contained in:
Michael Freno
2026-01-10 09:54:47 -05:00
parent 0bfbe30350
commit eebccc55d9
6 changed files with 233 additions and 54 deletions

View File

@@ -37,18 +37,16 @@ struct GazeApp: App {
CommandGroup(replacing: .newItem) { } CommandGroup(replacing: .newItem) { }
} }
// Menu bar extra (always present once onboarding is complete) // Menu bar extra (always present)
MenuBarExtra("Gaze", systemImage: "eye.fill") { MenuBarExtra("Gaze", systemImage: "eye.fill") {
if let timerEngine = appDelegate.timerEngine { MenuBarContentView(
MenuBarContentView( timerEngine: appDelegate.timerEngine,
timerEngine: timerEngine, settingsManager: settingsManager,
settingsManager: settingsManager, onQuit: { NSApplication.shared.terminate(nil) },
onQuit: { NSApplication.shared.terminate(nil) }, onOpenSettings: { appDelegate.openSettings() },
onOpenSettings: { appDelegate.openSettings() }, onOpenSettingsTab: { tab in appDelegate.openSettings(tab: tab) },
onOpenSettingsTab: { tab in appDelegate.openSettings(tab: tab) }, onOpenOnboarding: { appDelegate.openOnboarding() }
onOpenOnboarding: { appDelegate.openOnboarding() } )
)
}
} }
.menuBarExtraStyle(.window) .menuBarExtraStyle(.window)
} }

View File

@@ -44,7 +44,7 @@ struct MenuBarHoverButtonStyle: ButtonStyle {
} }
struct MenuBarContentView: View { struct MenuBarContentView: View {
@ObservedObject var timerEngine: TimerEngine var timerEngine: TimerEngine?
@ObservedObject var settingsManager: SettingsManager @ObservedObject var settingsManager: SettingsManager
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
var onQuit: () -> Void var onQuit: () -> Void
@@ -53,6 +53,92 @@ struct MenuBarContentView: View {
var onOpenOnboarding: () -> Void var onOpenOnboarding: () -> Void
var body: some View { 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) { VStack(alignment: .leading, spacing: 0) {
// Header // Header
HStack { HStack {
@@ -117,33 +203,16 @@ struct MenuBarContentView: View {
// Controls // Controls
VStack(spacing: 4) { 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: { Button(action: {
if timerEngine.timerStates.values.first?.isPaused == true { if isPaused(timerEngine: timerEngine) {
timerEngine.resume() timerEngine.resume()
} else { } else {
timerEngine.pause() timerEngine.pause()
} }
}) { }) {
HStack { HStack {
Image(systemName: isPaused ? "play.circle" : "pause.circle") Image(systemName: isPaused(timerEngine: timerEngine) ? "play.circle" : "pause.circle")
Text(isPaused ? "Resume All Timers" : "Pause All Timers") Text(isPaused(timerEngine: timerEngine) ? "Resume All Timers" : "Pause All Timers")
Spacer() Spacer()
} }
.padding(.horizontal, 8) .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 timerEngine.timerStates.values.first?.isPaused ?? false
} }
} }

View File

@@ -6,10 +6,12 @@
// //
import SwiftUI import SwiftUI
import AppKit
struct BlinkSetupView: View { struct BlinkSetupView: View {
@Binding var enabled: Bool @Binding var enabled: Bool
@Binding var intervalMinutes: Int @Binding var intervalMinutes: Int
@State private var previewWindowController: NSWindowController?
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
@@ -94,6 +96,20 @@ struct BlinkSetupView: View {
.font(.caption) .font(.caption)
.foregroundColor(.secondary) .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() Spacer()
@@ -102,6 +118,36 @@ struct BlinkSetupView: View {
.padding() .padding()
.background(.clear) .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") { #Preview("Blink Setup - Enabled") {

View File

@@ -6,17 +6,17 @@
// //
import SwiftUI import SwiftUI
import AppKit
#if os(iOS) #if os(iOS)
import UIKit import UIKit
#else
import AppKit
#endif #endif
struct LookAwaySetupView: View { struct LookAwaySetupView: View {
@Binding var enabled: Bool @Binding var enabled: Bool
@Binding var intervalMinutes: Int @Binding var intervalMinutes: Int
@Binding var countdownSeconds: Int @Binding var countdownSeconds: Int
@State private var previewWindowController: NSWindowController?
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
@@ -117,6 +117,20 @@ struct LookAwaySetupView: View {
.font(.caption) .font(.caption)
.foregroundColor(.secondary) .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() Spacer()
@@ -125,6 +139,36 @@ struct LookAwaySetupView: View {
.padding() .padding()
.background(.clear) .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") { #Preview("Look Away Setup View") {

View File

@@ -6,12 +6,12 @@
// //
import SwiftUI import SwiftUI
import AppKit
struct PostureSetupView: View { struct PostureSetupView: View {
@Binding var enabled: Bool @Binding var enabled: Bool
@Binding var intervalMinutes: Int @Binding var intervalMinutes: Int
@State private var previewWindowController: NSWindowController?
@State private var isPreviewShowing = false
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
@@ -99,26 +99,17 @@ struct PostureSetupView: View {
// Preview button // Preview button
Button(action: { Button(action: {
isPreviewShowing = true showPreviewWindow()
}) { }) {
HStack { HStack {
Image(systemName: "eye") Image(systemName: "eye")
.foregroundColor(.white) .foregroundColor(.accentColor)
Text("Preview Reminder") Text("Preview Reminder")
.foregroundColor(.white) .font(.headline)
} }
.padding(.horizontal, 20) .frame(maxWidth: .infinity, minHeight: 44, alignment: .center)
.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))
} }
.glassEffect(.regular.tint(.accentColor).interactive(), in: .rect(cornerRadius: 10))
} }
Spacer() Spacer()
@@ -127,6 +118,36 @@ struct PostureSetupView: View {
.padding() .padding()
.background(.clear) .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") { #Preview("Posture Setup - Enabled") {

View File

@@ -25,8 +25,9 @@ struct LookAwayReminderView: View {
var body: some View { var body: some View {
ZStack { ZStack {
// Semi-transparent dark background VisualEffectView(material: .hudWindow, blendingMode: .behindWindow)
Color.black.opacity(0.85) .ignoresSafeArea()
Color.black.opacity(0.5)
.ignoresSafeArea() .ignoresSafeArea()
VStack(spacing: 40) { VStack(spacing: 40) {