Conversation
Review or Edit in CodeSandboxOpen the branch in Web Editor • VS Code • Insiders |
WalkthroughA new SwiftUI view, Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant AppView
participant IgnoredAppsTabView
participant PreferencesManager
participant System (NSWorkspace/UserDefaults)
User->>AppView: Selects "Ignored Apps" tab
AppView->>IgnoredAppsTabView: Displays view
IgnoredAppsTabView->>PreferencesManager: getIgnoredApps()
PreferencesManager->>System: Read UserDefaults, combine with defaults
PreferencesManager-->>IgnoredAppsTabView: Return ignored app bundle IDs
IgnoredAppsTabView->>System: Resolve app names from bundle IDs
IgnoredAppsTabView-->>User: Show list of ignored apps
User->>IgnoredAppsTabView: Clicks "Add" and selects app
IgnoredAppsTabView->>System: Get bundle ID from app
IgnoredAppsTabView->>PreferencesManager: addIgnoredApp(bundleId)
PreferencesManager->>System: Update UserDefaults
IgnoredAppsTabView->>PreferencesManager: getIgnoredApps()
PreferencesManager-->>IgnoredAppsTabView: Return updated list
IgnoredAppsTabView-->>User: Update UI
User->>IgnoredAppsTabView: Clicks "Delete" on an app
IgnoredAppsTabView->>PreferencesManager: removeIgnoredApp(bundleId)
PreferencesManager->>System: Update UserDefaults
IgnoredAppsTabView->>PreferencesManager: getIgnoredApps()
PreferencesManager-->>IgnoredAppsTabView: Return updated list
IgnoredAppsTabView-->>User: Update UI
Assessment against linked issues
Poem
Tip ⚡️ Faster reviews with caching
Enjoy the performance boost—your workflow just got faster. 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Pull Request Overview
A new feature to ignore custom applications has been introduced, addressing issue #45 by adding an "Ignored Apps" tab and integrating ignored apps handling across the UI and underlying managers.
- Introduces an IgnoredAppsTabView for managing ignored apps.
- Updates PreferencesManager, WindowManager, and MouseTracker to support user-defined ignored apps.
- Updates AppView and project configuration to integrate the new tab.
Reviewed Changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| Swift Shift/src/View/IgnoredAppsTabView.swift | New view to manage the list of ignored apps and resolve app names. |
| Swift Shift/src/View/AppView.swift | Updated tab enum and view switching to include ignored apps. |
| Swift Shift/src/Manager/WindowManager.swift | Uses PreferencesManager API to check if an app should be ignored. |
| Swift Shift/src/Manager/Preferences.swift | Added functions to manage and query ignored apps. |
| Swift Shift/src/Manager/MouseTracker.swift | Updated logic to use PreferencesManager for ignored apps check. |
| Swift Shift/src/Constants.swift | Defines the default ignored app bundle IDs. |
| Swift Shift.xcodeproj/project.pbxproj | Updated project configuration to include the new view. |
Comments suppressed due to low confidence (1)
Swift Shift/src/Constants.swift:1
- [nitpick] If IGNORE_APP_BUNDLE_ID is intended to remain unchanged, consider declaring it as a constant using 'let' instead of 'var' to enhance immutability and clarity.
var IGNORE_APP_BUNDLE_ID = [
| ignoredApps = PreferencesManager.getUserIgnoredApps() | ||
|
|
||
| // Resolve bundle IDs to app names | ||
| for bundleId in (defaultIgnoredApps + ignoredApps) { |
There was a problem hiding this comment.
[nitpick] Consider deduplicating the union of defaultIgnoredApps and ignoredApps (for instance, by converting them into a Set) to avoid redundant application lookups.
| for bundleId in (defaultIgnoredApps + ignoredApps) { | |
| let uniqueBundleIds = Set(defaultIgnoredApps + ignoredApps) | |
| for bundleId in uniqueBundleIds { |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (8)
Swift Shift/src/View/IgnoredAppsTabView.swift (4)
54-58: Consider making the list height responsive.The list view has a fixed height of 200 points, which might be too small when many apps are added or too large for smaller displays. Consider making this responsive to the content size or window size constraints.
- .frame(height: 200) + .frame(minHeight: 100, idealHeight: 200, maxHeight: 300)
102-105: Consider app state preservation.The code activates the app to bring the NSOpenPanel to the front, which is generally good practice. However, this might disrupt user focus in some edge cases. Consider adding a comment explaining this design choice and potential alternatives.
// Activate the app first to ensure its windows are in front NSApp.activate(ignoringOtherApps: true) // Use runModal for sheet-style presentation that will come to the front +// Note: Using runModal instead of beginSheet to ensure dialog appears properly +// This approach may briefly disrupt user focus but ensures reliable panel presentation let response = openPanel.runModal()
41-41: Improve handling of missing app names.When displaying app items, if the app name isn't resolved, the code falls back to showing the bundle identifier, which isn't very user-friendly. Consider improving how unresolved app names are displayed.
-Text(appNames[bundleId] ?? bundleId) +Text(appNames[bundleId] ?? bundleId.components(separatedBy: ".").last?.capitalized ?? bundleId)
79-91: Add documentation for methods.The code would benefit from documentation comments for methods to explain their purpose and behavior, particularly for methods that interact with system APIs or handle preferences.
+/// Loads both default and user-defined ignored apps from preferences +/// and resolves their bundle identifiers to human-readable app names private func loadApps() { defaultIgnoredApps = IGNORE_APP_BUNDLE_ID ignoredApps = PreferencesManager.getUserIgnoredApps() // Resolve bundle IDs to app names for bundleId in (defaultIgnoredApps + ignoredApps) { if let appURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleId), let appBundle = Bundle(url: appURL), let appName = appBundle.object(forInfoDictionaryKey: "CFBundleName") as? String { appNames[bundleId] = appName } } }Swift Shift/src/Manager/Preferences.swift (4)
8-8: Fix redundant string enum value.The string value for the
ignoredAppscase is redundant as Swift will automatically use the case name when it matches. Consider removing the explicit raw value to address the SwiftLint warning.- case ignoredApps = "ignoredApps" + case ignoredApps🧰 Tools
🪛 SwiftLint (0.57.0)
[Warning] 8-8: String enum values can be omitted when they are equal to the enumcase name
(redundant_string_enum_value)
16-22: Consider using Set for efficiency.The
getIgnoredApps()method creates a Set to remove duplicates and then converts back to an Array. If this list is accessed frequently, consider returning a Set directly or caching the result to avoid repeated conversions.static func getIgnoredApps() -> [String] { // Include default apps + user-added apps if let savedApps = UserDefaults.standard.array(forKey: PreferenceKey.ignoredApps.rawValue) as? [String] { - return Array(Set(IGNORE_APP_BUNDLE_ID + savedApps)) // Ensure no duplicates + // Cache the combined list for better performance + let combinedList = Array(Set(IGNORE_APP_BUNDLE_ID + savedApps)) + return combinedList } return IGNORE_APP_BUNDLE_ID }
16-57: Consider encapsulating IGNORE_APP_BUNDLE_ID.The code directly accesses
IGNORE_APP_BUNDLE_IDwhich appears to be a global constant. Consider encapsulating this within thePreferencesManagerclass for better maintainability.+// Add this property to the PreferencesManager class +static private let defaultIgnoredApps = IGNORE_APP_BUNDLE_ID static func getIgnoredApps() -> [String] { // Include default apps + user-added apps if let savedApps = UserDefaults.standard.array(forKey: PreferenceKey.ignoredApps.rawValue) as? [String] { - return Array(Set(IGNORE_APP_BUNDLE_ID + savedApps)) // Ensure no duplicates + return Array(Set(defaultIgnoredApps + savedApps)) // Ensure no duplicates } - return IGNORE_APP_BUNDLE_ID + return defaultIgnoredApps } // Update other methods similarly
36-44: Add validation for bundle identifiers.Consider adding validation to ensure that added bundle identifiers conform to the expected format, which typically follows reverse-domain notation (e.g., "com.example.app").
static func addIgnoredApp(_ bundleId: String) { + // Validate bundle ID format (basic check for reverse domain notation) + guard bundleId.contains(".") && !bundleId.hasPrefix(".") && !bundleId.hasSuffix(".") else { + NSLog("Invalid bundle identifier format: \(bundleId)") + return + } + var currentList = getUserIgnoredApps() // Don't add apps that are already in default list if !IGNORE_APP_BUNDLE_ID.contains(bundleId) && !currentList.contains(bundleId) { currentList.append(bundleId) setUserIgnoredApps(currentList) } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
Swift Shift.xcodeproj/project.pbxproj(4 hunks)Swift Shift/src/Constants.swift(0 hunks)Swift Shift/src/Manager/MouseTracker.swift(1 hunks)Swift Shift/src/Manager/Preferences.swift(1 hunks)Swift Shift/src/Manager/WindowManager.swift(1 hunks)Swift Shift/src/View/AppView.swift(3 hunks)Swift Shift/src/View/IgnoredAppsTabView.swift(1 hunks)
💤 Files with no reviewable changes (1)
- Swift Shift/src/Constants.swift
🧰 Additional context used
🧬 Code Graph Analysis (3)
Swift Shift/src/Manager/MouseTracker.swift (1)
Swift Shift/src/Manager/Preferences.swift (1)
isAppIgnored(55-57)
Swift Shift/src/Manager/WindowManager.swift (1)
Swift Shift/src/Manager/Preferences.swift (1)
isAppIgnored(55-57)
Swift Shift/src/View/IgnoredAppsTabView.swift (1)
Swift Shift/src/Manager/Preferences.swift (3)
getUserIgnoredApps(24-30)addIgnoredApp(36-44)removeIgnoredApp(46-53)
🪛 SwiftLint (0.57.0)
Swift Shift/src/Manager/Preferences.swift
[Warning] 8-8: String enum values can be omitted when they are equal to the enumcase name
(redundant_string_enum_value)
🔇 Additional comments (8)
Swift Shift/src/View/AppView.swift (3)
6-6: Well-integrated enum extensionThe Tab enum has been properly extended to include the new "ignoredApps" case, maintaining the same style as the existing cases.
34-34: Clean UI integration with appropriate icon choiceGood choice of the "macwindow.on.rectangle" icon which visually represents the ignored apps functionality. The tab button follows the existing button pattern consistently.
43-44: Proper view integration in the tab switcherThe IgnoredAppsTabView is correctly integrated into the content area switch statement, maintaining the same structure as the other tabs.
Swift Shift/src/Manager/MouseTracker.swift (1)
127-127: Improved flexibility with preference-based app ignoringGood refactoring to replace the static check with the dynamic
PreferencesManager.isAppIgnored(bundleIdentifier)method. This change allows for user customization of ignored apps rather than relying on hardcoded values.Swift Shift/src/Manager/WindowManager.swift (1)
88-90: Enhanced flexibility with safer bundle ID handlingGood improvement to:
- Safely unwrap the bundle identifier before checking
- Use the dynamic
PreferencesManager.isAppIgnored(bundleId)method instead of a hardcoded list- Update the print statement to use the unwrapped variable
This change improves both safety and flexibility by allowing user-defined ignored apps.
Swift Shift.xcodeproj/project.pbxproj (1)
10-10: Proper project integration for the new view fileThe new IgnoredAppsTabView.swift file has been correctly added to:
- PBXBuildFile section
- PBXFileReference section
- View group in the project navigator
- Sources build phase
This ensures the file is properly included in the build process.
Also applies to: 38-38, 85-85, 254-254
Swift Shift/src/View/IgnoredAppsTabView.swift (1)
5-127: Overall implementation is well-structured and user-friendly.The IgnoredAppsTabView is well-implemented with clean code structure, good UI organization, and appropriate user interactions. The separation of default and user-defined ignored apps is well thought out.
Swift Shift/src/Manager/Preferences.swift (1)
8-57: Well-structured preference management implementation.The preference management implementation is well-structured with a clean separation between default and user-defined ignored apps. The methods are concise, focused, and handle the persistence logic appropriately.
🧰 Tools
🪛 SwiftLint (0.57.0)
[Warning] 8-8: String enum values can be omitted when they are equal to the enumcase name
(redundant_string_enum_value)
| private func selectApp() { | ||
| let openPanel = NSOpenPanel() | ||
| openPanel.canChooseFiles = true | ||
| openPanel.canChooseDirectories = false | ||
| openPanel.allowsMultipleSelection = false | ||
| openPanel.allowedContentTypes = [UTType.application] | ||
| openPanel.directoryURL = URL(fileURLWithPath: "/Applications") | ||
|
|
||
| // Activate the app first to ensure its windows are in front | ||
| NSApp.activate(ignoringOtherApps: true) | ||
|
|
||
| // Use runModal for sheet-style presentation that will come to the front | ||
| let response = openPanel.runModal() | ||
|
|
||
| if response == .OK, let selectedURL = openPanel.url { | ||
| if let bundle = Bundle(url: selectedURL), | ||
| let bundleIdentifier = bundle.bundleIdentifier { | ||
| let appName = bundle.object(forInfoDictionaryKey: "CFBundleName") as? String ?? bundleIdentifier | ||
|
|
||
| // Don't add if it's already in the default list | ||
| if !defaultIgnoredApps.contains(bundleIdentifier) { | ||
| // Add to ignored apps list | ||
| PreferencesManager.addIgnoredApp(bundleIdentifier) | ||
| appNames[bundleIdentifier] = appName | ||
| ignoredApps = PreferencesManager.getUserIgnoredApps() | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Add error handling and user feedback.
The app selection process doesn't provide user feedback when something goes wrong. Consider adding error handling and feedback for cases such as:
- When the selected file isn't a valid application
- When bundle identifier extraction fails
- When an app is already in the ignored list
private func selectApp() {
let openPanel = NSOpenPanel()
openPanel.canChooseFiles = true
openPanel.canChooseDirectories = false
openPanel.allowsMultipleSelection = false
openPanel.allowedContentTypes = [UTType.application]
openPanel.directoryURL = URL(fileURLWithPath: "/Applications")
// Activate the app first to ensure its windows are in front
NSApp.activate(ignoringOtherApps: true)
// Use runModal for sheet-style presentation that will come to the front
let response = openPanel.runModal()
if response == .OK, let selectedURL = openPanel.url {
if let bundle = Bundle(url: selectedURL),
let bundleIdentifier = bundle.bundleIdentifier {
let appName = bundle.object(forInfoDictionaryKey: "CFBundleName") as? String ?? bundleIdentifier
// Don't add if it's already in the default list
if !defaultIgnoredApps.contains(bundleIdentifier) {
// Add to ignored apps list
PreferencesManager.addIgnoredApp(bundleIdentifier)
appNames[bundleIdentifier] = appName
ignoredApps = PreferencesManager.getUserIgnoredApps()
+ // Provide success feedback
+ let notification = NSUserNotification()
+ notification.title = "Application Added"
+ notification.informativeText = "\(appName) added to ignored applications"
+ NSUserNotificationCenter.default.deliver(notification)
+ } else {
+ // Provide feedback that app is already in default list
+ let notification = NSUserNotification()
+ notification.title = "Cannot Add Application"
+ notification.informativeText = "\(appName) is already in the default ignored list"
+ NSUserNotificationCenter.default.deliver(notification)
}
+ } else {
+ // Provide feedback for invalid application
+ let notification = NSUserNotification()
+ notification.title = "Invalid Application"
+ notification.informativeText = "Could not get bundle identifier for the selected application"
+ NSUserNotificationCenter.default.deliver(notification)
}
}
}
fix #45
CleanShot.2025-05-12.at.18.27.49.yafw.balanced.mp4
Summary by CodeRabbit