Skip to content

feat: ignore custom apps#92

Merged
pablopunk merged 3 commits intomainfrom
ignore-custom-apps
May 12, 2025
Merged

feat: ignore custom apps#92
pablopunk merged 3 commits intomainfrom
ignore-custom-apps

Conversation

@pablopunk
Copy link
Owner

@pablopunk pablopunk commented May 12, 2025

fix #45

CleanShot.2025-05-12.at.18.27.49.yafw.balanced.mp4

Summary by CodeRabbit

  • New Features
    • Added an "Ignored Apps" tab to manage applications excluded from Swift Shift shortcuts.
    • Users can add or remove custom ignored apps alongside system-default ignored apps.
  • Improvements
    • Updated app exclusion logic to honor user-configured ignored applications dynamically.
  • Documentation
    • Updated feature list to highlight the ability to ignore custom apps.

@codesandbox
Copy link

codesandbox bot commented May 12, 2025

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders

Open Preview

@coderabbitai
Copy link

coderabbitai bot commented May 12, 2025

Walkthrough

A new SwiftUI view, IgnoredAppsTabView, was added to enable users to manage a list of ignored applications for Swift Shift shortcuts. Supporting logic was introduced in the preferences manager to allow dynamic storage and retrieval of ignored app bundle identifiers. UI and control flow were updated to integrate this new functionality into the application.

Changes

File(s) Change Summary
Swift Shift.xcodeproj/project.pbxproj Registered the new IgnoredAppsTabView.swift file in the project: added to build files, file references, source group, and build phase.
Swift Shift/src/Constants.swift Removed a leading blank line at the start of the file. No changes to code or declarations.
Swift Shift/src/Manager/Preferences.swift Added preference management for ignored apps: new enum case, methods to get/set/add/remove ignored apps, and a method to check if an app is ignored. User modifications are stored in UserDefaults and combined with default ignored apps for queries.
Swift Shift/src/Manager/MouseTracker.swift
Swift Shift/src/Manager/WindowManager.swift
Updated logic to check ignored applications using the new PreferencesManager.isAppIgnored(bundleId) method instead of the static IGNORE_APP_BUNDLE_ID array. Improved safety by avoiding force-unwrapping.
Swift Shift/src/View/AppView.swift Extended the Tab enum to include ignoredApps and updated the tab bar and content switch to render the new IgnoredAppsTabView when selected.
Swift Shift/src/View/IgnoredAppsTabView.swift Introduced a new SwiftUI view for managing ignored applications. Allows adding/removing apps to the ignored list, persists user changes, and displays both default and user-ignored apps with their names and bundle identifiers. Handles UI, app selection, and preference updates.

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
Loading

Assessment against linked issues

Objective Addressed Explanation
Provide a UI in settings to include other apps to be ignored (#45)

Poem

🐇
A tab for ignored apps now appears,
Where users can manage with clicks and cheers.
Add or remove, your list’s in your paws,
Preferences saved without a pause.
Swift Shift’s control, now more refined—
Hopping forward, with peace of mind!
🌱

Tip

⚡️ Faster reviews with caching
  • CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.

Enjoy the performance boost—your workflow just got faster.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b9ea7f4 and 1961c7d.

📒 Files selected for processing (1)
  • Swift Shift/src/View/IgnoredAppsTabView.swift (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • Swift Shift/src/View/IgnoredAppsTabView.swift
✨ Finishing Touches
  • 📝 Generate Docstrings

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@pablopunk pablopunk requested a review from Copilot May 12, 2025 16:33
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {
Copy link

Copilot AI May 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider deduplicating the union of defaultIgnoredApps and ignoredApps (for instance, by converting them into a Set) to avoid redundant application lookups.

Suggested change
for bundleId in (defaultIgnoredApps + ignoredApps) {
let uniqueBundleIds = Set(defaultIgnoredApps + ignoredApps)
for bundleId in uniqueBundleIds {

Copilot uses AI. Check for mistakes.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ignoredApps case 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_ID which appears to be a global constant. Consider encapsulating this within the PreferencesManager class 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

📥 Commits

Reviewing files that changed from the base of the PR and between 8ebe083 and a47ac5a.

📒 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 extension

The 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 choice

Good 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 switcher

The 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 ignoring

Good 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 handling

Good improvement to:

  1. Safely unwrap the bundle identifier before checking
  2. Use the dynamic PreferencesManager.isAppIgnored(bundleId) method instead of a hardcoded list
  3. 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 file

The new IgnoredAppsTabView.swift file has been correctly added to:

  1. PBXBuildFile section
  2. PBXFileReference section
  3. View group in the project navigator
  4. 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)

Comment on lines +93 to +121
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()
}
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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)
        }
    }
}

@pablopunk pablopunk merged commit 1265b10 into main May 12, 2025
3 checks passed
@pablopunk pablopunk deleted the ignore-custom-apps branch May 12, 2025 17:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ignore custom apps

2 participants