general: refinements to ui, run, and menubar settings triggers
This commit is contained in:
@@ -187,7 +187,7 @@ private func showReminderWindow(_ content: AnyView) {
|
||||
}
|
||||
|
||||
// Public method to open settings window
|
||||
func openSettings() {
|
||||
func openSettings(tab: Int = 0) {
|
||||
// If window already exists, just bring it to front
|
||||
if let existingWindow = settingsWindowController?.window {
|
||||
existingWindow.makeKeyAndOrderFront(nil)
|
||||
@@ -207,7 +207,7 @@ private func showReminderWindow(_ content: AnyView) {
|
||||
window.setFrameAutosaveName("SettingsWindow")
|
||||
window.isReleasedWhenClosed = false
|
||||
window.contentView = NSHostingView(
|
||||
rootView: SettingsWindowView(settingsManager: settingsManager!)
|
||||
rootView: SettingsWindowView(settingsManager: settingsManager!, initialTab: tab)
|
||||
)
|
||||
|
||||
let windowController = NSWindowController(window: window)
|
||||
|
||||
@@ -42,7 +42,8 @@ struct GazeApp: App {
|
||||
timerEngine: timerEngine,
|
||||
settingsManager: settingsManager,
|
||||
onQuit: { NSApplication.shared.terminate(nil) },
|
||||
onOpenSettings: { appDelegate.openSettings() }
|
||||
onOpenSettings: { appDelegate.openSettings() },
|
||||
onOpenSettingsTab: { tab in appDelegate.openSettings(tab: tab) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,4 +35,26 @@ enum TimerType: String, Codable, CaseIterable, Identifiable {
|
||||
return "figure.stand"
|
||||
}
|
||||
}
|
||||
|
||||
var tabIndex: Int {
|
||||
switch self {
|
||||
case .lookAway:
|
||||
return 0
|
||||
case .blink:
|
||||
return 1
|
||||
case .posture:
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
||||
var tooltipText: String {
|
||||
switch self {
|
||||
case .lookAway:
|
||||
return "Full screen reminder"
|
||||
case .blink:
|
||||
return "Subtle reminder"
|
||||
case .posture:
|
||||
return "Subtle reminder"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ struct MenuBarContentView: View {
|
||||
@ObservedObject var settingsManager: SettingsManager
|
||||
var onQuit: () -> Void
|
||||
var onOpenSettings: () -> Void
|
||||
var onOpenSettingsTab: (Int) -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
@@ -65,33 +66,41 @@ struct MenuBarContentView: View {
|
||||
Divider()
|
||||
|
||||
// Timer Status
|
||||
if !timerEngine.timerStates.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("Active Timers")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.horizontal)
|
||||
.padding(.top, 8)
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("Active Timers")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.horizontal)
|
||||
.padding(.top, 8)
|
||||
|
||||
ForEach(TimerType.allCases) { timerType in
|
||||
if let state = timerEngine.timerStates[timerType] {
|
||||
TimerStatusRow(
|
||||
type: timerType,
|
||||
state: state,
|
||||
onSkip: {
|
||||
timerEngine.skipNext(type: timerType)
|
||||
},
|
||||
onDevTrigger: {
|
||||
timerEngine.triggerReminder(for: timerType)
|
||||
}
|
||||
)
|
||||
}
|
||||
ForEach(TimerType.allCases) { timerType in
|
||||
if let state = timerEngine.timerStates[timerType] {
|
||||
TimerStatusRow(
|
||||
type: timerType,
|
||||
state: state,
|
||||
onSkip: {
|
||||
timerEngine.skipNext(type: timerType)
|
||||
},
|
||||
onDevTrigger: {
|
||||
timerEngine.triggerReminder(for: timerType)
|
||||
},
|
||||
onTap: {
|
||||
onOpenSettingsTab(timerType.tabIndex)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
InactiveTimerRow(
|
||||
type: timerType,
|
||||
onTap: {
|
||||
onOpenSettingsTab(timerType.tabIndex)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 8)
|
||||
|
||||
Divider()
|
||||
}
|
||||
.padding(.bottom, 8)
|
||||
|
||||
Divider()
|
||||
|
||||
// Controls
|
||||
VStack(spacing: 4) {
|
||||
@@ -158,74 +167,85 @@ struct TimerStatusRow: View {
|
||||
let state: TimerState
|
||||
var onSkip: () -> Void
|
||||
var onDevTrigger: (() -> Void)? = nil
|
||||
var onTap: (() -> Void)? = nil
|
||||
@State private var isHoveredSkip = false
|
||||
@State private var isHoveredDevTrigger = false
|
||||
@State private var isHoveredBody = false
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Image(systemName: type.iconName)
|
||||
.foregroundColor(iconColor)
|
||||
.frame(width: 20)
|
||||
Button(action: {
|
||||
onTap?()
|
||||
}) {
|
||||
HStack {
|
||||
Image(systemName: type.iconName)
|
||||
.foregroundColor(iconColor)
|
||||
.frame(width: 20)
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(type.displayName)
|
||||
.font(.subheadline)
|
||||
.fontWeight(.medium)
|
||||
Text(timeRemaining)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.monospacedDigit()
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
#if DEBUG
|
||||
if let onDevTrigger = onDevTrigger {
|
||||
Button(action: onDevTrigger) {
|
||||
Image(systemName: "bolt.fill")
|
||||
.font(.caption)
|
||||
.foregroundColor(.yellow)
|
||||
.padding(6)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.glassEffect(
|
||||
isHoveredDevTrigger ? .regular.tint(.yellow) : .regular,
|
||||
in: .circle
|
||||
)
|
||||
.help("Trigger \(type.displayName) reminder now (dev)")
|
||||
.onHover { hovering in
|
||||
isHoveredDevTrigger = hovering
|
||||
}
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(type.displayName)
|
||||
.font(.subheadline)
|
||||
.fontWeight(.medium)
|
||||
Text(timeRemaining)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.monospacedDigit()
|
||||
}
|
||||
#endif
|
||||
|
||||
Button(action: onSkip) {
|
||||
Image(systemName: "forward.fill")
|
||||
.font(.caption)
|
||||
.foregroundColor(.accentColor)
|
||||
.padding(6)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.glassEffect(
|
||||
isHoveredSkip ? .regular.tint(.accentColor) : .regular,
|
||||
in: .circle
|
||||
)
|
||||
.help("Skip to next \(type.displayName) reminder")
|
||||
.onHover { hovering in
|
||||
isHoveredSkip = hovering
|
||||
Spacer()
|
||||
|
||||
#if DEBUG
|
||||
if let onDevTrigger = onDevTrigger {
|
||||
Button(action: onDevTrigger) {
|
||||
Image(systemName: "bolt.fill")
|
||||
.font(.caption)
|
||||
.foregroundColor(.yellow)
|
||||
.padding(6)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.glassEffect(
|
||||
isHoveredDevTrigger ? .regular.tint(.yellow) : .regular,
|
||||
in: .circle
|
||||
)
|
||||
.help("Trigger \(type.displayName) reminder now (dev)")
|
||||
.onHover { hovering in
|
||||
isHoveredDevTrigger = hovering
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Button(action: onSkip) {
|
||||
Image(systemName: "forward.fill")
|
||||
.font(.caption)
|
||||
.foregroundColor(.accentColor)
|
||||
.padding(6)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.glassEffect(
|
||||
isHoveredSkip ? .regular.tint(.accentColor.opacity(0.5)) : .regular,
|
||||
in: .circle
|
||||
)
|
||||
.help("Skip to next \(type.displayName) reminder")
|
||||
.onHover { hovering in
|
||||
isHoveredSkip = hovering
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 6)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 6)
|
||||
.buttonStyle(.plain)
|
||||
.glassEffect(
|
||||
isHoveredBody ? .regular.tint(.accentColor) : .regular,
|
||||
isHoveredBody ? .regular.tint(.accentColor.opacity(0.5)) : .regular,
|
||||
in: .rect(cornerRadius: 6)
|
||||
)
|
||||
.padding(.horizontal, 8)
|
||||
.onHover { hovering in
|
||||
isHoveredBody = hovering
|
||||
}
|
||||
.help(tooltipText)
|
||||
}
|
||||
|
||||
private var tooltipText: String {
|
||||
type.tooltipText
|
||||
}
|
||||
|
||||
private var iconColor: Color {
|
||||
@@ -253,6 +273,48 @@ struct TimerStatusRow: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct InactiveTimerRow: View {
|
||||
let type: TimerType
|
||||
var onTap: () -> Void
|
||||
@State private var isHovered = false
|
||||
|
||||
var body: some View {
|
||||
Button(action: onTap) {
|
||||
HStack {
|
||||
Image(systemName: type.iconName)
|
||||
.foregroundColor(.secondary)
|
||||
.frame(width: 20)
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(type.displayName)
|
||||
.font(.subheadline)
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "plus.circle")
|
||||
.font(.title3)
|
||||
.foregroundColor(.accentColor)
|
||||
.padding(6)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 6)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.glassEffect(
|
||||
isHovered ? .regular.tint(.accentColor.opacity(0.5)) : .regular,
|
||||
in: .rect(cornerRadius: 6)
|
||||
)
|
||||
.padding(.horizontal, 8)
|
||||
.onHover { hovering in
|
||||
isHovered = hovering
|
||||
}
|
||||
.help("Enable \(type.displayName) reminders")
|
||||
}
|
||||
}
|
||||
|
||||
#Preview("Menu Bar Content") {
|
||||
let settingsManager = SettingsManager.shared
|
||||
let timerEngine = TimerEngine(settingsManager: settingsManager)
|
||||
@@ -260,6 +322,7 @@ struct TimerStatusRow: View {
|
||||
timerEngine: timerEngine,
|
||||
settingsManager: settingsManager,
|
||||
onQuit: {},
|
||||
onOpenSettings: {}
|
||||
onOpenSettings: {},
|
||||
onOpenSettingsTab: { _ in }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ struct OnboardingContainerView: View {
|
||||
@State private var lookAwayEnabled = true
|
||||
@State private var lookAwayIntervalMinutes = 20
|
||||
@State private var lookAwayCountdownSeconds = 20
|
||||
@State private var blinkEnabled = true
|
||||
@State private var blinkEnabled = false
|
||||
@State private var blinkIntervalMinutes = 5
|
||||
@State private var postureEnabled = true
|
||||
@State private var postureIntervalMinutes = 30
|
||||
|
||||
@@ -41,7 +41,7 @@ struct LookAwayReminderView: View {
|
||||
LottieView(
|
||||
animationName: AnimationAsset.lookAway.fileName,
|
||||
loopMode: .loop,
|
||||
animationSpeed: 1.0
|
||||
animationSpeed: 0.75
|
||||
)
|
||||
.frame(width: 200, height: 200)
|
||||
.padding(.vertical, 30)
|
||||
|
||||
@@ -9,7 +9,7 @@ import SwiftUI
|
||||
|
||||
struct SettingsWindowView: View {
|
||||
@ObservedObject var settingsManager: SettingsManager
|
||||
@State private var currentTab = 0
|
||||
@State private var currentTab: Int
|
||||
@State private var lookAwayEnabled: Bool
|
||||
@State private var lookAwayIntervalMinutes: Int
|
||||
@State private var lookAwayCountdownSeconds: Int
|
||||
@@ -19,9 +19,10 @@ struct SettingsWindowView: View {
|
||||
@State private var postureIntervalMinutes: Int
|
||||
@State private var launchAtLogin: Bool
|
||||
|
||||
init(settingsManager: SettingsManager) {
|
||||
init(settingsManager: SettingsManager, initialTab: Int = 0) {
|
||||
self.settingsManager = settingsManager
|
||||
|
||||
_currentTab = State(initialValue: initialTab)
|
||||
_lookAwayEnabled = State(initialValue: settingsManager.settings.lookAwayTimer.enabled)
|
||||
_lookAwayIntervalMinutes = State(initialValue: settingsManager.settings.lookAwayTimer.intervalSeconds / 60)
|
||||
_lookAwayCountdownSeconds = State(initialValue: settingsManager.settings.lookAwayCountdownSeconds)
|
||||
|
||||
20
run
20
run
@@ -8,6 +8,22 @@ ACTION=${1:-run}
|
||||
VERBOSE=false
|
||||
OUTPUT_FILE=""
|
||||
|
||||
# Function to kill any existing Gaze processes
|
||||
kill_existing_gaze_processes() {
|
||||
echo "🔍 Checking for existing Gaze processes..."
|
||||
|
||||
# Find and kill any running Gaze processes
|
||||
pids=$(pgrep -f "Gaze.app")
|
||||
if [ -n "$pids" ]; then
|
||||
echo "🛑 Killing existing Gaze processes (PID(s): $pids)..."
|
||||
kill $pids 2>/dev/null
|
||||
# Wait a moment for processes to terminate
|
||||
sleep 1
|
||||
else
|
||||
echo "✅ No existing Gaze processes found"
|
||||
fi
|
||||
}
|
||||
|
||||
# Parse command line arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
@@ -72,6 +88,10 @@ elif [ "$ACTION" = "test" ]; then
|
||||
|
||||
elif [ "$ACTION" = "run" ]; then
|
||||
echo "Building and running Gaze application..."
|
||||
|
||||
# Kill any existing Gaze processes first
|
||||
kill_existing_gaze_processes
|
||||
|
||||
# Always build first, then run
|
||||
run_with_output "xcodebuild -project Gaze.xcodeproj -scheme Gaze -configuration Debug build"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user