diff --git a/Gaze/Models/TimerState.swift b/Gaze/Models/TimerState.swift index 6730db7..2f53462 100644 --- a/Gaze/Models/TimerState.swift +++ b/Gaze/Models/TimerState.swift @@ -7,16 +7,18 @@ import Foundation -struct TimerState: Equatable { +struct TimerState { let type: TimerType var remainingSeconds: Int var isPaused: Bool var isActive: Bool - + var targetDate: Date + init(type: TimerType, intervalSeconds: Int, isPaused: Bool = false, isActive: Bool = true) { self.type = type self.remainingSeconds = intervalSeconds self.isPaused = isPaused self.isActive = isActive + self.targetDate = Date().addingTimeInterval(Double(intervalSeconds)) } } diff --git a/Gaze/Services/TimerEngine.swift b/Gaze/Services/TimerEngine.swift index 935787c..2aca8bf 100644 --- a/Gaze/Services/TimerEngine.swift +++ b/Gaze/Services/TimerEngine.swift @@ -5,24 +5,24 @@ // Created by Mike Freno on 1/7/26. // -import Foundation import Combine +import Foundation @MainActor class TimerEngine: ObservableObject { @Published var timerStates: [TimerType: TimerState] = [:] @Published var activeReminder: ReminderEvent? - + private var timerSubscription: AnyCancellable? private let settingsManager: SettingsManager - + init(settingsManager: SettingsManager) { self.settingsManager = settingsManager } - + func start() { stop() - + for timerType in TimerType.allCases { let config = settingsManager.timerConfiguration(for: timerType) if config.enabled { @@ -34,7 +34,7 @@ class TimerEngine: ObservableObject { ) } } - + timerSubscription = Timer.publish(every: 1.0, on: .main, in: .common) .autoconnect() .sink { [weak self] _ in @@ -43,25 +43,25 @@ class TimerEngine: ObservableObject { } } } - + func stop() { timerSubscription?.cancel() timerSubscription = nil timerStates.removeAll() } - + func pause() { for (type, _) in timerStates { timerStates[type]?.isPaused = true } } - + func resume() { for (type, _) in timerStates { timerStates[type]?.isPaused = false } } - + func skipNext(type: TimerType) { guard let state = timerStates[type] else { return } let config = settingsManager.timerConfiguration(for: type) @@ -72,55 +72,69 @@ class TimerEngine: ObservableObject { isActive: state.isActive ) } - + func dismissReminder() { guard let reminder = activeReminder else { return } activeReminder = nil - + skipNext(type: reminder.type) - + if case .lookAwayTriggered = reminder { resume() } } - + private func handleTick() { guard activeReminder == nil else { return } - + for (type, state) in timerStates { guard state.isActive && !state.isPaused else { continue } - + // prevent overshoot - in case user closes laptop while timer is running, we don't want to + // trigger on open, + if state.targetDate < Date() - 3.0 { // slight grace + // Reset the timer when it has overshot its interval + let config = settingsManager.timerConfiguration(for: type) + timerStates[type] = TimerState( + type: type, + intervalSeconds: config.intervalSeconds, + isPaused: state.isPaused, + isActive: state.isActive + ) + continue // Skip normal countdown logic after reset + } + timerStates[type]?.remainingSeconds -= 1 - + if let updatedState = timerStates[type], updatedState.remainingSeconds <= 0 { triggerReminder(for: type) break } } } - + func triggerReminder(for type: TimerType) { switch type { case .lookAway: pause() - activeReminder = .lookAwayTriggered(countdownSeconds: settingsManager.settings.lookAwayCountdownSeconds) + activeReminder = .lookAwayTriggered( + countdownSeconds: settingsManager.settings.lookAwayCountdownSeconds) case .blink: activeReminder = .blinkTriggered case .posture: activeReminder = .postureTriggered } } - + func getTimeRemaining(for type: TimerType) -> TimeInterval { guard let state = timerStates[type] else { return 0 } return TimeInterval(state.remainingSeconds) } - + func getFormattedTimeRemaining(for type: TimerType) -> String { let seconds = Int(getTimeRemaining(for: type)) let minutes = seconds / 60 let remainingSeconds = seconds % 60 - + if minutes >= 60 { let hours = minutes / 60 let remainingMinutes = minutes % 60 diff --git a/buildServer.json b/buildServer.json new file mode 100644 index 0000000..e4b5e56 --- /dev/null +++ b/buildServer.json @@ -0,0 +1,19 @@ +{ + "name": "xcode build server", + "version": "1.3.0", + "bspVersion": "2.2.0", + "languages": [ + "c", + "cpp", + "objective-c", + "objective-cpp", + "swift" + ], + "argv": [ + "/opt/homebrew/bin/xcode-build-server" + ], + "workspace": "/Users/mike/Code/Gaze/Gaze.xcodeproj/project.xcworkspace", + "build_root": "/Users/mike/Library/Developer/Xcode/DerivedData/Gaze-ahifnboyyfcsskfhqfbfzxarhabs", + "scheme": "Gaze", + "kind": "xcode" +} \ No newline at end of file diff --git a/run b/run index f6b7444..c5ed7cc 100755 --- a/run +++ b/run @@ -1,13 +1,10 @@ #!/bin/bash - # Build and run Gaze application # Usage: ./run [build|test|run] - # Default action is build and run ACTION=${1:-run} VERBOSE=false OUTPUT_FILE="" - # Function to kill any existing Gaze processes kill_existing_gaze_processes() { echo "🔍 Checking for existing Gaze processes..." @@ -23,7 +20,25 @@ kill_existing_gaze_processes() { echo "✅ No existing Gaze processes found" fi } - +# Function to update LSP configuration +update_lsp_config() { + echo "🔧 Updating LSP configuration..." + + # Check if xcode-build-server is installed + if command -v xcode-build-server &> /dev/null; then + # Generate buildServer.json for LSP + xcode-build-server config -project Gaze.xcodeproj -scheme Gaze > /dev/null 2>&1 + + if [ $? -eq 0 ]; then + echo "✅ LSP configuration updated (buildServer.json created)" + else + echo "⚠️ Could not update LSP configuration" + fi + else + echo "⚠️ xcode-build-server not found. Install with: brew install xcode-build-server" + echo " This helps Neovim's LSP recognize your Swift modules" + fi +} # Parse command line arguments while [[ $# -gt 0 ]]; do case $1 in @@ -36,13 +51,16 @@ while [[ $# -gt 0 ]]; do VERBOSE=true shift 2 ;; + --no-lsp) + SKIP_LSP=true + shift + ;; *) ACTION="$1" shift ;; esac done - # Function to run command with output control run_with_output() { local cmd="$1" @@ -60,9 +78,7 @@ run_with_output() { eval "$cmd" > /dev/null 2>&1 fi } - echo "=== Gaze Application Script ===" - if [ "$ACTION" = "build" ]; then echo "Building Gaze project..." run_with_output "xcodebuild -project Gaze.xcodeproj -scheme Gaze -configuration Debug build" @@ -70,6 +86,11 @@ if [ "$ACTION" = "build" ]; then if [ $? -eq 0 ]; then echo "✅ Build succeeded!" echo "💡 The app is located at: build/Debug/Gaze.app" + + # Update LSP config after successful build + if [ "$SKIP_LSP" != true ]; then + update_lsp_config + fi else echo "❌ Build failed!" exit 1 @@ -85,7 +106,6 @@ elif [ "$ACTION" = "test" ]; then echo "❌ Tests failed!" exit 1 fi - elif [ "$ACTION" = "run" ]; then echo "Building and running Gaze application..." @@ -97,6 +117,12 @@ elif [ "$ACTION" = "run" ]; then if [ $? -eq 0 ]; then echo "✅ Build succeeded!" + + # Update LSP config after successful build + if [ "$SKIP_LSP" != true ]; then + update_lsp_config + fi + # Get the actual build output directory from xcodebuild BUILD_DIR="$(xcodebuild -project Gaze.xcodeproj -scheme Gaze -configuration Debug -showBuildSettings 2>/dev/null | grep -m 1 "BUILT_PRODUCTS_DIR" | sed 's/.*= //')" APP_PATH="${BUILD_DIR}/Gaze.app" @@ -113,17 +139,23 @@ elif [ "$ACTION" = "run" ]; then echo "❌ Build failed!" exit 1 fi +elif [ "$ACTION" = "lsp" ]; then + # New command to just update LSP config + echo "Updating LSP configuration only..." + update_lsp_config else - echo "Usage: $0 [build|test|run] [-v|--verbose] [-o|--output ]" + echo "Usage: $0 [build|test|run|lsp] [-v|--verbose] [-o|--output ] [--no-lsp]" echo "" echo "Commands:" echo " build - Build the application" echo " test - Run unit tests" echo " run - Build and run the application (default)" + echo " lsp - Update LSP configuration only (buildServer.json)" echo "" echo "Options:" echo " -v, --verbose - Show output in stdout" echo " -o, --output - Write output to log file" + echo " --no-lsp - Skip LSP configuration update" exit 1 -fi \ No newline at end of file +fi