Skip to content
This repository was archived by the owner on Nov 17, 2025. It is now read-only.

Commit 5106285

Browse files
steipeteclaude
andcommitted
Refactor status bar with native AppKit implementation and visual indicators
Replace simple text-based menu bar display with a sophisticated native macOS status bar implementation featuring the CodeLooper logo and colored process count badges. This overhaul improves visual feedback, fixes duplicate settings window issues, and provides a more professional appearance that better aligns with macOS design patterns. - Implement StatusBarController using NSStatusItem for native menu bar integration - Add StatusIndicators component with CodeLooper logo and colored badges - Create custom popover with NSVisualEffectView for proper macOS appearance - Fix duplicate settings windows by removing SwiftUI WindowGroup - Enable WindowScreenshotPopover functionality in cursor windows list - Improve menu bar highlight state tracking when popover is active - Clean up legacy MenuBarIconManager implementation and tests The new implementation provides better visual feedback about monitoring state while maintaining a subtle, professional appearance that integrates seamlessly with the macOS menu bar. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 00e2268 commit 5106285

30 files changed

Lines changed: 1385 additions & 1196 deletions

App/AppDelegate.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ public final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObjec
5252
public static var shared: AppDelegate? {
5353
NSApp.delegate as? AppDelegate
5454
}
55+
56+
// Status bar controller - initialized later
57+
private var statusBarController: StatusBarController?
5558

5659
// View models and coordinators
5760
public var mainSettingsCoordinator: MainSettingsCoordinator?
@@ -97,6 +100,9 @@ public final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObjec
97100

98101
// Initialize core services FIRST
99102
initializeServices() // Ensure windowManager and other services are ready
103+
104+
// Initialize status bar controller after services are ready
105+
statusBarController = StatusBarController.shared
100106

101107
// Sync login item state with user preference after services are up
102108
if !Constants.isTestEnvironment {
@@ -297,7 +303,7 @@ public final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObjec
297303
loginItemManager = LoginItemManager.shared
298304

299305
_ = self.locatorManager
300-
_ = MenuBarIconManager.shared // Ensure shared instance is initialized
306+
// Note: MenuBarIconManager removed - now using SwiftUI MenuBarExtra in CodeLooperApp.swift
301307

302308
// Initialize Sparkle with error handling to prevent dialogs
303309
sparkleUpdaterManager = SparkleUpdaterManager()
@@ -399,6 +405,7 @@ public final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObjec
399405
// Initialize WelcomeWindowCoordinator to ensure it's listening for notifications
400406
_ = WelcomeWindowCoordinator.shared
401407

408+
402409
logger.info("Application startup completed successfully")
403410
}
404411

@@ -418,7 +425,7 @@ public final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObjec
418425
@objc private func handleShowSettingsNotification() {
419426
Task { @MainActor in
420427
logger.info("Received request to show settings from another instance")
421-
SettingsService.openSettingsSubject.send()
428+
MainSettingsCoordinator.shared.showSettings()
422429
NSApp.activate(ignoringOtherApps: true)
423430
}
424431
}
@@ -514,6 +521,7 @@ public final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObjec
514521
}
515522
}
516523

524+
517525
// MARK: - Global Tint Setup
518526

519527
private func setupGlobalTint() {

App/CodeLooperApp.swift

Lines changed: 3 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import DesignSystem
44
import Diagnostics
55
import KeyboardShortcuts
66
import Logging
7-
import MenuBarExtraAccess
87
import os
98
import SwiftUI
109

@@ -54,25 +53,10 @@ struct CodeLooperApp: App {
5453
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
5554

5655
var body: some Scene {
57-
MenuBarExtra {
58-
menuBarContent
59-
} label: {
60-
MenuBarIconView() // Use the new struct for the label
61-
.environmentObject(cursorMonitor)
62-
.environmentObject(appIconStateController)
56+
// No window groups - settings are handled by MainSettingsCoordinator
57+
Settings {
58+
EmptyView()
6359
}
64-
.menuBarExtraStyle(.window)
65-
.menuBarExtraAccess(isPresented: $isMenuPresented) { _ in
66-
}
67-
68-
WindowGroup("CodeLooper", id: "settings") {
69-
SettingsSceneView()
70-
.environmentObject(sessionLogger)
71-
}
72-
.windowResizability(.contentSize)
73-
.defaultSize(width: 640, height: 800)
74-
.commandsRemoved()
75-
.handlesExternalEvents(matching: Set(["settings"]))
7660
.commands {
7761
CommandGroup(replacing: .appInfo) {
7862
Button("About CodeLooper") {
@@ -91,81 +75,11 @@ struct CodeLooperApp: App {
9175
@StateObject private var cursorMonitor = CursorMonitor.shared
9276
@StateObject private var sessionLogger = SessionLogger.shared
9377
@StateObject private var appIconStateController = AppIconStateController.shared // Renamed for clarity
94-
@State private var isMenuPresented: Bool = false // For MenuBarExtraAccess
9578

9679
@Default(.isGlobalMonitoringEnabled) private var isGlobalMonitoringEnabled
9780
@Default(.showDebugMenu) private var showDebugMenu // For the debug menu items
9881
@Default(.startAtLogin) private var startAtLogin // For the menu item state
9982

10083
// Logger for CodeLooperApp
10184
private let logger = Logger(category: .app)
102-
103-
@ViewBuilder
104-
private var menuBarContent: some View {
105-
MainPopoverView()
106-
.environmentObject(sessionLogger)
107-
.environmentObject(cursorMonitor)
108-
}
109-
}
110-
111-
struct MenuBarIconView: View {
112-
@StateObject private var menuBarIconManager = MenuBarIconManager.shared
113-
@EnvironmentObject var appIconStateController: AppIconStateController // Keep for status display
114-
@Default(.isGlobalMonitoringEnabled) private var isGlobalMonitoringEnabled
115-
@Default(.useDynamicMenuBarIcon) private var useDynamicMenuBarIcon
116-
117-
var body: some View {
118-
HStack(spacing: 4) {
119-
// Icon based on user preference
120-
if useDynamicMenuBarIcon {
121-
// Animated SwiftUI icon
122-
AnimatedLoopIcon(size: 16)
123-
} else {
124-
// Static PNG icon
125-
Image("MenuBarTemplateIcon")
126-
.renderingMode(.template)
127-
.frame(width: 16, height: 16)
128-
}
129-
130-
// Display the status text from the manager
131-
Text(menuBarIconManager.currentIconAttributedString)
132-
.font(.system(size: 12, weight: .medium))
133-
}
134-
.contentShape(Rectangle())
135-
}
136-
}
137-
138-
struct SettingsSceneView: View {
139-
// MARK: Internal
140-
141-
var body: some View {
142-
SettingsContainerView()
143-
.environmentObject(mainSettingsViewModel)
144-
.onReceive(SettingsService.openSettingsSubject) { _ in
145-
// Handle settings opening if needed
146-
}
147-
.onAppear {
148-
ensureSingleSettingsWindow()
149-
}
150-
}
151-
152-
// MARK: Private
153-
154-
@StateObject private var mainSettingsViewModel = MainSettingsViewModel(
155-
loginItemManager: LoginItemManager.shared,
156-
updaterViewModel: UpdaterViewModel(sparkleUpdaterManager: nil)
157-
)
158-
159-
private func ensureSingleSettingsWindow() {
160-
// Close any duplicate settings windows
161-
let settingsWindows = NSApp.windows.filter { window in
162-
window.title.contains("Settings") || window.identifier?.rawValue == "settings"
163-
}
164-
165-
// Keep only the first one, close the rest
166-
for window in settingsWindows.dropFirst() {
167-
print("Closing duplicate settings window: \(window.title)")
168-
window.close()
169-
}
170-
}
17185
}

0 commit comments

Comments
 (0)