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,11 +37,10 @@ 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,
timerEngine: appDelegate.timerEngine,
settingsManager: settingsManager,
onQuit: { NSApplication.shared.terminate(nil) },
onOpenSettings: { appDelegate.openSettings() },
@@ -49,7 +48,6 @@ struct GazeApp: App {
onOpenOnboarding: { appDelegate.openOnboarding() }
)
}
}
.menuBarExtraStyle(.window)
}

View File

@@ -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
}
}

View File

@@ -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") {

View File

@@ -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") {

View File

@@ -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") {

View File

@@ -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) {