-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Implement pause/resume UI controls for automation #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ class ClickItViewModel: ObservableObject { | |
| // MARK: - Published Properties | ||
| @Published var targetPoint: CGPoint? | ||
| @Published var isRunning = false | ||
| @Published var isPaused = false | ||
| @Published var appStatus: AppStatus = .ready | ||
|
|
||
| // Configuration Properties | ||
|
|
@@ -69,6 +70,14 @@ class ClickItViewModel: ObservableObject { | |
| return total >= 1 && total <= 3600 // 1 second to 60 minutes | ||
| } | ||
|
|
||
| var canPause: Bool { | ||
| isRunning && !isPaused | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
|
|
||
| var canResume: Bool { | ||
| isPaused && !isRunning | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
|
|
||
| // MARK: - Dependencies | ||
| private let clickCoordinator = ClickCoordinator.shared | ||
|
|
||
|
|
@@ -151,9 +160,83 @@ class ClickItViewModel: ObservableObject { | |
| clickCoordinator.stopAutomation() | ||
| cancelTimer() // Also cancel any active timer | ||
| isRunning = false | ||
| isPaused = false | ||
| appStatus = .ready | ||
| } | ||
|
|
||
| func pauseAutomation() { | ||
| guard isRunning && !isPaused else { return } | ||
|
|
||
| clickCoordinator.stopAutomation() | ||
| ElapsedTimeManager.shared.pauseTracking() | ||
|
|
||
| // Update visual feedback to show paused state (dimmed) | ||
| if showVisualFeedback, let point = targetPoint { | ||
| VisualFeedbackOverlay.shared.updateOverlay(at: point, isActive: false) | ||
| } | ||
|
|
||
| isRunning = false | ||
| isPaused = true | ||
| appStatus = .paused | ||
| } | ||
|
|
||
| func resumeAutomation() { | ||
| guard isPaused && !isRunning else { return } | ||
|
|
||
| // Resume elapsed time tracking | ||
| ElapsedTimeManager.shared.resumeTracking() | ||
|
|
||
| // Restart automation with current configuration | ||
| guard let point = targetPoint else { return } | ||
|
|
||
| let config = AutomationConfiguration( | ||
| location: point, | ||
| clickType: clickType, | ||
| clickInterval: Double(totalMilliseconds) / 1000.0, | ||
| targetApplication: nil, | ||
| maxClicks: durationMode == .clickCount ? maxClicks : nil, | ||
| maxDuration: durationMode == .timeLimit ? durationSeconds : nil, | ||
| stopOnError: stopOnError, | ||
| randomizeLocation: randomizeLocation, | ||
| locationVariance: CGFloat(randomizeLocation ? locationVariance : 0), | ||
| showVisualFeedback: showVisualFeedback, | ||
| useDynamicMouseTracking: false | ||
| ) | ||
|
Comment on lines
+192
to
+204
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This Style Guide ReferencesFootnotes |
||
|
|
||
| clickCoordinator.startAutomation(with: config) | ||
| isRunning = true | ||
| isPaused = false | ||
| appStatus = .running | ||
|
|
||
| // Update visual feedback to show active state | ||
| if showVisualFeedback { | ||
| VisualFeedbackOverlay.shared.updateOverlay(at: point, isActive: true) | ||
| } | ||
| } | ||
|
|
||
| // MARK: - Testing Methods | ||
| func startAutomationForTesting() { | ||
| guard let point = targetPoint else { return } | ||
|
|
||
| let config = AutomationConfiguration( | ||
| location: point, | ||
| clickType: clickType, | ||
| clickInterval: Double(totalMilliseconds) / 1000.0, | ||
| targetApplication: nil, | ||
| maxClicks: durationMode == .clickCount ? maxClicks : nil, | ||
| maxDuration: durationMode == .timeLimit ? durationSeconds : nil, | ||
| stopOnError: stopOnError, | ||
| randomizeLocation: randomizeLocation, | ||
| locationVariance: CGFloat(randomizeLocation ? locationVariance : 0), | ||
| showVisualFeedback: false, // Disable visual feedback for tests | ||
| useDynamicMouseTracking: false | ||
| ) | ||
|
|
||
| clickCoordinator.startAutomation(with: config) | ||
| isRunning = true | ||
| appStatus = .running | ||
| } | ||
|
|
||
| func resetConfiguration() { | ||
| intervalHours = 0 | ||
| intervalMinutes = 0 | ||
|
|
@@ -189,9 +272,10 @@ class ClickItViewModel: ObservableObject { | |
| guard let self = self else { return } | ||
|
|
||
| // Sync ViewModel state with ClickCoordinator state | ||
| if !isActive && self.isRunning { | ||
| if !isActive && (self.isRunning || self.isPaused) { | ||
| print("ClickItViewModel: Automation stopped externally (e.g., DELETE key), updating UI state") | ||
| self.isRunning = false | ||
| self.isPaused = false | ||
| self.appStatus = .ready | ||
| // Also cancel any active timer when automation stops | ||
| self.cancelTimer() | ||
|
|
@@ -349,6 +433,7 @@ enum TimerMode { | |
| enum AppStatus { | ||
| case ready | ||
| case running | ||
| case paused | ||
| case error(String) | ||
|
|
||
| var displayText: String { | ||
|
|
@@ -357,6 +442,8 @@ enum AppStatus { | |
| return "Ready" | ||
| case .running: | ||
| return "Running" | ||
| case .paused: | ||
| return "Paused" | ||
| case .error(let message): | ||
| return "Error: \(message)" | ||
| } | ||
|
|
@@ -368,6 +455,8 @@ enum AppStatus { | |
| return .green | ||
| case .running: | ||
| return .blue | ||
| case .paused: | ||
| return .orange | ||
| case .error: | ||
| return .red | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having both
isRunningandisPausedas separate@Publishedproperties can lead to inconsistent states. Consider using theappStatusenum as the single source of truth and deriveisRunningandisPausedas computed properties1.Style Guide References
Footnotes
Using a single source of truth for state management improves consistency and reduces potential errors. (link) ↩