From fb7fa7d02b1fab91b0e7d7a7b2e5530b54849328 Mon Sep 17 00:00:00 2001 From: Hariharan Mudaliar Date: Sat, 15 Nov 2025 00:04:07 +0530 Subject: [PATCH] Remove unnecessary stats panel --- DynamicIsland/DynamicIslandApp.swift | 11 - DynamicIsland/Localizable.xcstrings | 23 +- .../Shortcuts/ShortcutConstants.swift | 1 - .../components/Settings/SettingsView.swift | 47 +- .../components/Stats/StatsPanel.swift | 130 --- .../components/Stats/StatsPanelView.swift | 798 ------------------ .../managers/StatsPanelManager.swift | 55 -- DynamicIsland/models/Constants.swift | 1 - 8 files changed, 15 insertions(+), 1051 deletions(-) delete mode 100644 DynamicIsland/components/Stats/StatsPanel.swift delete mode 100644 DynamicIsland/components/Stats/StatsPanelView.swift delete mode 100644 DynamicIsland/managers/StatsPanelManager.swift diff --git a/DynamicIsland/DynamicIslandApp.swift b/DynamicIsland/DynamicIslandApp.swift index a6f2e48..c259832 100644 --- a/DynamicIsland/DynamicIslandApp.swift +++ b/DynamicIsland/DynamicIslandApp.swift @@ -615,17 +615,6 @@ class AppDelegate: NSObject, NSApplicationDelegate { KeyboardShortcuts.isEnabled = Defaults[.enableShortcuts] - _ = KeyboardShortcuts.onKeyDown(for: .statsPanel) { - guard Defaults[.enableShortcuts] else { return } - guard Defaults[.enableStatsFeature] && Defaults[.showStatsPanel] else { return } - - if !StatsManager.shared.isMonitoring { - StatsManager.shared.startMonitoring() - } - - StatsPanelManager.shared.toggleStatsPanel() - } - if !Defaults[.showOnAllDisplays] { let viewModel = self.vm let window = createDynamicIslandWindow( diff --git a/DynamicIsland/Localizable.xcstrings b/DynamicIsland/Localizable.xcstrings index 8d14053..c7de981 100644 --- a/DynamicIsland/Localizable.xcstrings +++ b/DynamicIsland/Localizable.xcstrings @@ -1321,6 +1321,7 @@ } }, "• Updated %@" : { + "extractionState" : "stale", "localizations" : { "fr" : { "stringUnit" : { @@ -5014,6 +5015,7 @@ } }, "Clear" : { + "extractionState" : "stale", "localizations" : { "fr" : { "stringUnit" : { @@ -7860,9 +7862,6 @@ }, "Enable stats monitoring in Settings to view system performance data." : { - }, - "Enable system stats monitoring in Settings to view detailed performance data." : { - }, "Enable Thinking Mode" : { @@ -10533,6 +10532,7 @@ "isCommentAutoGenerated" : true }, "Live Monitoring" : { + "extractionState" : "stale", "localizations" : { "fr" : { "stringUnit" : { @@ -12602,6 +12602,7 @@ } }, "Monitoring Stopped" : { + "extractionState" : "stale", "localizations" : { "fr" : { "stringUnit" : { @@ -14521,9 +14522,6 @@ }, "Open Model Settings" : { - }, - "Open Settings" : { - }, "Open System Settings" : { @@ -14536,9 +14534,6 @@ }, "Opens the color picker panel for screen color capture. Default is Cmd+Shift+P. Only works when color picker feature is enabled." : { - }, - "Opens the detailed system performance monitor panel. Default is Cmd+Shift+S. Only works when stats feature is enabled." : { - }, "Output devices" : { "comment" : "Section title for the list of available audio output devices in the media output selector.", @@ -17745,6 +17740,7 @@ } }, "Start" : { + "extractionState" : "stale", "localizations" : { "fr" : { "stringUnit" : { @@ -17835,6 +17831,7 @@ } }, "Stats Feature Disabled" : { + "extractionState" : "stale", "localizations" : { "fr" : { "stringUnit" : { @@ -17845,6 +17842,7 @@ } }, "Stats feature is disabled" : { + "extractionState" : "stale", "localizations" : { "fr" : { "stringUnit" : { @@ -17853,11 +17851,9 @@ } } } - }, - "Stats monitoring displays performance graphs in the Dynamic Island. The detailed panel (Cmd+Shift+S) shows Activity Monitor-style detailed graphs and metrics." : { - }, "Stats panel is disabled" : { + "extractionState" : "stale", "localizations" : { "fr" : { "stringUnit" : { @@ -17868,6 +17864,7 @@ } }, "Stats Panel:" : { + "extractionState" : "stale", "localizations" : { "fr" : { "stringUnit" : { @@ -18268,6 +18265,7 @@ } }, "System Performance Monitor" : { + "extractionState" : "stale", "localizations" : { "fr" : { "stringUnit" : { @@ -18304,6 +18302,7 @@ "isCommentAutoGenerated" : true }, "Time Range" : { + "extractionState" : "stale", "localizations" : { "fr" : { "stringUnit" : { diff --git a/DynamicIsland/Shortcuts/ShortcutConstants.swift b/DynamicIsland/Shortcuts/ShortcutConstants.swift index 122100e..e6d276f 100644 --- a/DynamicIsland/Shortcuts/ShortcutConstants.swift +++ b/DynamicIsland/Shortcuts/ShortcutConstants.swift @@ -12,7 +12,6 @@ extension KeyboardShortcuts.Name { static let clipboardHistoryPanel = Self("clipboardHistoryPanel", default: .init(.c, modifiers: [.shift, .command])) static let colorPickerPanel = Self("colorPickerPanel", default: .init(.p, modifiers: [.shift, .command])) static let screenAssistantPanel = Self("screenAssistantPanel", default: .init(.a, modifiers: [.shift, .command])) - static let statsPanel = Self("statsPanel", default: .init(.s, modifiers: [.shift, .command])) static let toggleMicrophone = Self("toggleMicrophone", default: .init(.f5, modifiers: [.function])) static let decreaseBacklight = Self("decreaseBacklight", default: .init(.f1, modifiers: [.command])) static let increaseBacklight = Self("increaseBacklight", default: .init(.f2, modifiers: [.command])) diff --git a/DynamicIsland/components/Settings/SettingsView.swift b/DynamicIsland/components/Settings/SettingsView.swift index b5de9c3..bda6dff 100644 --- a/DynamicIsland/components/Settings/SettingsView.swift +++ b/DynamicIsland/components/Settings/SettingsView.swift @@ -1745,34 +1745,6 @@ struct Shortcuts: View { .font(.caption) } - Section { - HStack { - VStack(alignment: .leading) { - KeyboardShortcuts.Recorder("Stats Panel:", name: .statsPanel) - .disabled(!enableShortcuts || !enableStatsFeature || !Defaults[.showStatsPanel]) - if !enableStatsFeature { - Text("Stats feature is disabled") - .font(.caption) - .foregroundStyle(.secondary) - .padding(.top, 2) - } else if !Defaults[.showStatsPanel] { - Text("Stats panel is disabled") - .font(.caption) - .foregroundStyle(.secondary) - .padding(.top, 2) - } - } - Spacer() - } - } header: { - Text("Stats") - } footer: { - Text("Opens the detailed system performance monitor panel. Default is Cmd+Shift+S. Only works when stats feature is enabled.") - .multilineTextAlignment(.trailing) - .foregroundStyle(.secondary) - .font(.caption) - } - Section { HStack { VStack(alignment: .leading) { @@ -2267,7 +2239,6 @@ struct StatsSettings: View { @Default(.showGpuGraph) var showGpuGraph @Default(.showNetworkGraph) var showNetworkGraph @Default(.showDiskGraph) var showDiskGraph - @Default(.showStatsPanel) var showStatsPanel var enabledGraphsCount: Int { [showCpuGraph, showMemoryGraph, showGpuGraph, showNetworkGraph, showDiskGraph].filter { $0 }.count @@ -2299,23 +2270,13 @@ struct StatsSettings: View { // Note: Smart monitoring will handle starting when switching to stats tab } - if enableStatsFeature { - Defaults.Toggle("Enable detailed stats panel", key: .showStatsPanel) - } } header: { Text("General") } footer: { - if enableStatsFeature && showStatsPanel { - Text("Stats monitoring displays performance graphs in the Dynamic Island. The detailed panel (Cmd+Shift+S) shows Activity Monitor-style detailed graphs and metrics.") - .multilineTextAlignment(.trailing) - .foregroundStyle(.secondary) - .font(.caption) - } else { - Text("When enabled, the Stats tab will display real-time system performance graphs. This feature requires system permissions and may use additional battery.") - .multilineTextAlignment(.trailing) - .foregroundStyle(.secondary) - .font(.caption) - } + Text("When enabled, the Stats tab will display real-time system performance graphs. This feature requires system permissions and may use additional battery.") + .multilineTextAlignment(.trailing) + .foregroundStyle(.secondary) + .font(.caption) } if enableStatsFeature { diff --git a/DynamicIsland/components/Stats/StatsPanel.swift b/DynamicIsland/components/Stats/StatsPanel.swift deleted file mode 100644 index ee986c5..0000000 --- a/DynamicIsland/components/Stats/StatsPanel.swift +++ /dev/null @@ -1,130 +0,0 @@ -// -// StatsPanel.swift -// DynamicIsland -// -// Created by Ebullioscopic on 13/08/25. -// - -import AppKit -import SwiftUI -import Defaults - -class StatsPanel: NSPanel { - - init() { - super.init( - contentRect: .zero, - styleMask: [.borderless, .nonactivatingPanel], - backing: .buffered, - defer: true - ) - - setupWindow() - setupContentView() - } - - // Override to allow the panel to become key window (required for user interaction) - override var canBecomeKey: Bool { - return true - } - - // Override to allow the panel to become main window (required for text input and controls) - override var canBecomeMain: Bool { - return true - } - - private func setupWindow() { - backgroundColor = .clear - isOpaque = false - hasShadow = true - level = .floating - isMovableByWindowBackground = true // Enable dragging - titlebarAppearsTransparent = true - titleVisibility = .hidden - isFloatingPanel = true // Mark as floating panel for proper behavior - - // Allow dragging from any part of the window - styleMask.insert(.fullSizeContentView) - - collectionBehavior = [ - .canJoinAllSpaces, - .stationary, - .fullScreenAuxiliary // Float above full-screen apps - ] - - // Apply screen capture hiding setting - updateScreenCaptureVisibility() - setupScreenCaptureObserver() - - // Accept mouse moved events for proper hover behavior - acceptsMouseMovedEvents = true - } - - private func setupContentView() { - let contentView = StatsPanelView { - self.close() - } - - let hostingView = NSHostingView(rootView: contentView) - self.contentView = hostingView - - // Set initial size - let preferredSize = CGSize(width: 600, height: 400) - hostingView.setFrameSize(preferredSize) - setContentSize(preferredSize) - } - - func positionNearNotch() { - guard let screen = NSScreen.main else { return } - - let screenFrame = screen.visibleFrame - let panelFrame = frame - - // Position at top center of screen (near where the notch would be) - let xPosition = (screenFrame.width - panelFrame.width) / 2 + screenFrame.minX - let yPosition = screenFrame.maxY - panelFrame.height - 10 // 10px from top - - setFrameOrigin(NSPoint(x: xPosition, y: yPosition)) - } - - func positionNearMouse() { - let mouseLocation = NSEvent.mouseLocation - let panelFrame = frame - - // Position near mouse but ensure it stays on screen - guard let screen = NSScreen.main else { return } - let screenFrame = screen.visibleFrame - - var xPosition = mouseLocation.x - panelFrame.width / 2 - var yPosition = mouseLocation.y - panelFrame.height - 20 - - // Keep within screen bounds - xPosition = max(screenFrame.minX + 10, min(xPosition, screenFrame.maxX - panelFrame.width - 10)) - yPosition = max(screenFrame.minY + 10, min(yPosition, screenFrame.maxY - panelFrame.height - 10)) - - setFrameOrigin(NSPoint(x: xPosition, y: yPosition)) - } - - private func setupScreenCaptureObserver() { - // Observe changes to hidePanelsFromScreenCapture setting - Defaults.observe(.hidePanelsFromScreenCapture) { [weak self] change in - DispatchQueue.main.async { - self?.updateScreenCaptureVisibility() - } - } - } - - private func updateScreenCaptureVisibility() { - let shouldHide = Defaults[.hidePanelsFromScreenCapture] - - if shouldHide { - // Hide from screen capture and recording - self.sharingType = .none - print("🙈 StatsPanel: Hidden from screen capture and recordings") - } else { - // Allow normal screen capture - self.sharingType = .readOnly - print("👁️ StatsPanel: Visible in screen capture and recordings") - } - } -} diff --git a/DynamicIsland/components/Stats/StatsPanelView.swift b/DynamicIsland/components/Stats/StatsPanelView.swift deleted file mode 100644 index f0a04c2..0000000 --- a/DynamicIsland/components/Stats/StatsPanelView.swift +++ /dev/null @@ -1,798 +0,0 @@ -// -// StatsPanelView.swift -// DynamicIsland -// -// Created by Ebullioscopic on 13/08/25. -// - -import SwiftUI -import Defaults - -struct StatsPanelView: View { - let onClose: () -> Void - @ObservedObject var statsManager = StatsManager.shared - @Default(.enableStatsFeature) var enableStatsFeature - @State private var selectedTimeRange: TimeRange = .last30 - @State private var refreshTimer: Timer? - - enum TimeRange: String, CaseIterable { - case last30 = "Last 30 sec" - case last60 = "Last 1 min" - case last300 = "Last 5 min" - - var dataPoints: Int { - switch self { - case .last30: return 30 - case .last60: return 60 - case .last300: return 300 - } - } - } - - var body: some View { - VStack(spacing: 0) { - // Header with controls - StatsPanelHeader( - selectedTimeRange: $selectedTimeRange, - onClose: onClose - ) - - Divider() - - // Main content - if !enableStatsFeature { - // Disabled state - VStack(spacing: 16) { - Image(systemName: "chart.line.uptrend.xyaxis") - .font(.system(size: 48)) - .foregroundColor(.secondary) - - Text("Stats Feature Disabled") - .font(.title2) - .fontWeight(.semibold) - .foregroundColor(.primary) - - Text("Enable system stats monitoring in Settings to view detailed performance data.") - .font(.body) - .foregroundColor(.secondary) - .multilineTextAlignment(.center) - .padding(.horizontal, 40) - - Button("Open Settings") { - // TODO: Add settings navigation - onClose() - } - .buttonStyle(.borderedProminent) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - } else { - // Stats content - Compact two-row layout - CPUStatsDetailView() - } - } - .frame(width: 520, height: 620) - .background(VisualEffectView(material: .hudWindow, blendingMode: .behindWindow)) - .cornerRadius(12) - .shadow(color: .black.opacity(0.4), radius: 20, x: 0, y: 10) - .onAppear { - // Note: Smart monitoring will handle starting/stopping based on UI state - } - } -} - -// MARK: - Header Component -struct StatsPanelHeader: View { - @Binding var selectedTimeRange: StatsPanelView.TimeRange - let onClose: () -> Void - @ObservedObject var statsManager = StatsManager.shared - - var body: some View { - HStack { - // Title and status - HStack(spacing: 12) { - Image(systemName: "chart.line.uptrend.xyaxis") - .foregroundColor(.blue) - .font(.system(size: 18, weight: .medium)) - - VStack(alignment: .leading, spacing: 2) { - Text("System Performance Monitor") - .font(.headline) - .foregroundColor(.primary) - - HStack(spacing: 8) { - Circle() - .fill(statsManager.isMonitoring ? .green : .red) - .frame(width: 6, height: 6) - - Text(statsManager.isMonitoring ? "Live Monitoring" : "Monitoring Stopped") - .font(.caption) - .foregroundColor(.secondary) - - if statsManager.isMonitoring { - Text("• Updated \(formatLastUpdated(statsManager.lastUpdated))") - .font(.caption) - .foregroundColor(.secondary) - } - } - } - } - - Spacer() - - // Controls - HStack(spacing: 12) { - // Time range picker - Picker("Time Range", selection: $selectedTimeRange) { - ForEach(StatsPanelView.TimeRange.allCases, id: \.self) { range in - Text(range.rawValue).tag(range) - } - } - .pickerStyle(.menu) - .frame(width: 120) - - // Monitoring controls - if statsManager.isMonitoring { - Button("Stop") { - statsManager.stopMonitoring() - } - .buttonStyle(.bordered) - .foregroundColor(.red) - } else { - Button("Start") { - statsManager.startMonitoring() - } - .buttonStyle(.borderedProminent) - } - - Button("Clear") { - statsManager.clearHistory() - } - .buttonStyle(.bordered) - .disabled(statsManager.isMonitoring) - - // Close button - Button(action: onClose) { - Image(systemName: "xmark") - .foregroundColor(.secondary) - .font(.system(size: 12, weight: .medium)) - } - .buttonStyle(.bordered) - } - } - .padding(.horizontal, 20) - .padding(.vertical, 16) - } - - private func formatLastUpdated(_ date: Date) -> String { - let formatter = DateFormatter() - formatter.timeStyle = .medium - return formatter.string(from: date) - } -} - -// MARK: - Section Container -struct StatsPanelSection: View { - let title: String - @ViewBuilder let content: Content - - var body: some View { - VStack(alignment: .leading, spacing: 12) { - Text(title) - .font(.title3) - .fontWeight(.semibold) - .foregroundColor(.primary) - - content - .padding(16) - .background(Color.gray.opacity(0.1)) - .cornerRadius(12) - } - } -} - -// MARK: - System Overview -struct SystemOverviewGrid: View { - @ObservedObject var statsManager = StatsManager.shared - - var body: some View { - LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 3), spacing: 16) { - OverviewCard( - title: "CPU", - value: statsManager.cpuUsageString, - color: .blue, - icon: "cpu" - ) - - OverviewCard( - title: "Memory", - value: statsManager.memoryUsageString, - color: .green, - icon: "memorychip" - ) - - OverviewCard( - title: "GPU", - value: statsManager.gpuUsageString, - color: .purple, - icon: "display" - ) - } - } -} - -struct OverviewCard: View { - let title: String - let value: String - let color: Color - let icon: String - - var body: some View { - VStack(spacing: 8) { - HStack { - Image(systemName: icon) - .foregroundColor(color) - .font(.title3) - - Spacer() - } - - VStack(alignment: .leading, spacing: 4) { - Text(title) - .font(.caption) - .foregroundColor(.secondary) - - Text(value) - .font(.title2) - .fontWeight(.bold) - .foregroundColor(.primary) - } - - Spacer() - } - .padding(12) - .background(Color.gray.opacity(0.05)) - .cornerRadius(8) - .frame(height: 80) - } -} - -// MARK: - Detailed Views (Placeholder implementations) -struct DetailedCPUView: View { - let timeRange: StatsPanelView.TimeRange - @ObservedObject var statsManager = StatsManager.shared - - var body: some View { - VStack(alignment: .leading, spacing: 12) { - // Large timeline graph - DetailedTimelineGraph( - data: statsManager.cpuHistory, - color: .blue, - title: "CPU Usage Over Time", - unit: "%", - timeRange: timeRange - ) - - // CPU details - LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 3), spacing: 12) { - DetailMetricCard(title: "Current", value: statsManager.cpuUsageString, color: .blue) - DetailMetricCard(title: "Average", value: String(format: "%.1f%%", statsManager.avgCpuUsage), color: .blue) - DetailMetricCard(title: "Peak", value: String(format: "%.1f%%", statsManager.maxCpuUsage), color: .blue) - } - } - } -} - -struct DetailedMemoryView: View { - let timeRange: StatsPanelView.TimeRange - @ObservedObject var statsManager = StatsManager.shared - - var body: some View { - VStack(alignment: .leading, spacing: 12) { - DetailedTimelineGraph( - data: statsManager.memoryHistory, - color: .green, - title: "Memory Usage Over Time", - unit: "%", - timeRange: timeRange - ) - - LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 3), spacing: 12) { - DetailMetricCard(title: "Current", value: statsManager.memoryUsageString, color: .green) - DetailMetricCard(title: "Average", value: String(format: "%.1f%%", statsManager.avgMemoryUsage), color: .green) - DetailMetricCard(title: "Peak", value: String(format: "%.1f%%", statsManager.maxMemoryUsage), color: .green) - } - } - } -} - -struct DetailedNetworkView: View { - let timeRange: StatsPanelView.TimeRange - @ObservedObject var statsManager = StatsManager.shared - - var body: some View { - VStack(alignment: .leading, spacing: 12) { - DualTimelineGraph( - positiveData: statsManager.networkDownloadHistory, - negativeData: statsManager.networkUploadHistory, - positiveColor: .orange, - negativeColor: .red, - title: "Network Activity Over Time", - positiveLabel: "Download", - negativeLabel: "Upload", - unit: "MB/s", - timeRange: timeRange - ) - - LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 2), spacing: 12) { - DetailMetricCard(title: "Download", value: statsManager.networkDownloadString, color: .orange) - DetailMetricCard(title: "Upload", value: statsManager.networkUploadString, color: .red) - } - } - } -} - -struct DetailedDiskView: View { - let timeRange: StatsPanelView.TimeRange - @ObservedObject var statsManager = StatsManager.shared - - var body: some View { - VStack(alignment: .leading, spacing: 12) { - DualTimelineGraph( - positiveData: statsManager.diskReadHistory, - negativeData: statsManager.diskWriteHistory, - positiveColor: .cyan, - negativeColor: .yellow, - title: "Disk Activity Over Time", - positiveLabel: "Read", - negativeLabel: "Write", - unit: "MB/s", - timeRange: timeRange - ) - - LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 2), spacing: 12) { - DetailMetricCard(title: "Read", value: statsManager.diskReadString, color: .cyan) - DetailMetricCard(title: "Write", value: statsManager.diskWriteString, color: .yellow) - } - } - } -} - -struct DetailMetricCard: View { - let title: String - let value: String - let color: Color - - var body: some View { - VStack(alignment: .leading, spacing: 4) { - Text(title) - .font(.caption) - .foregroundColor(.secondary) - - Text(value) - .font(.title3) - .fontWeight(.semibold) - .foregroundColor(color) - } - .padding(8) - .background(Color.gray.opacity(0.05)) - .cornerRadius(6) - } -} - -// MARK: - Enhanced Graph Components -struct DetailedTimelineGraph: View { - let data: [Double] - let color: Color - let title: String - let unit: String - let timeRange: StatsPanelView.TimeRange - - var body: some View { - VStack(alignment: .leading, spacing: 8) { - Text(title) - .font(.caption) - .foregroundColor(.secondary) - - GeometryReader { geometry in - let maxValue = data.max() ?? 1.0 - let normalizedData = maxValue > 0 ? data.map { $0 / maxValue } : data - - ZStack { - // Grid lines - Path { path in - let horizontalLines = 5 - for i in 0...horizontalLines { - let y = geometry.size.height * CGFloat(i) / CGFloat(horizontalLines) - path.move(to: CGPoint(x: 0, y: y)) - path.addLine(to: CGPoint(x: geometry.size.width, y: y)) - } - } - .stroke(Color.gray.opacity(0.2), lineWidth: 0.5) - - // Data line - Path { path in - guard !normalizedData.isEmpty else { return } - - let stepX = geometry.size.width / CGFloat(max(1, normalizedData.count - 1)) - - for (index, value) in normalizedData.enumerated() { - let x = CGFloat(index) * stepX - let y = geometry.size.height * (1 - CGFloat(value)) - - if index == 0 { - path.move(to: CGPoint(x: x, y: y)) - } else { - path.addLine(to: CGPoint(x: x, y: y)) - } - } - } - .stroke(color, lineWidth: 2) - - // Fill area - Path { path in - guard !normalizedData.isEmpty else { return } - - let stepX = geometry.size.width / CGFloat(max(1, normalizedData.count - 1)) - - path.move(to: CGPoint(x: 0, y: geometry.size.height)) - - for (index, value) in normalizedData.enumerated() { - let x = CGFloat(index) * stepX - let y = geometry.size.height * (1 - CGFloat(value)) - path.addLine(to: CGPoint(x: x, y: y)) - } - - path.addLine(to: CGPoint(x: geometry.size.width, y: geometry.size.height)) - path.closeSubpath() - } - .fill( - LinearGradient( - gradient: Gradient(colors: [color.opacity(0.3), color.opacity(0.1)]), - startPoint: .top, - endPoint: .bottom - ) - ) - } - } - .frame(height: 120) - } - } -} - -struct DualTimelineGraph: View { - let positiveData: [Double] - let negativeData: [Double] - let positiveColor: Color - let negativeColor: Color - let title: String - let positiveLabel: String - let negativeLabel: String - let unit: String - let timeRange: StatsPanelView.TimeRange - - var body: some View { - VStack(alignment: .leading, spacing: 8) { - HStack { - Text(title) - .font(.caption) - .foregroundColor(.secondary) - - Spacer() - - HStack(spacing: 12) { - HStack(spacing: 4) { - Circle() - .fill(positiveColor) - .frame(width: 8, height: 8) - Text(positiveLabel) - .font(.caption2) - .foregroundColor(.secondary) - } - - HStack(spacing: 4) { - Circle() - .fill(negativeColor) - .frame(width: 8, height: 8) - Text(negativeLabel) - .font(.caption2) - .foregroundColor(.secondary) - } - } - } - - GeometryReader { geometry in - let maxPositive = positiveData.max() ?? 1.0 - let maxNegative = negativeData.max() ?? 1.0 - let maxValue = max(maxPositive, maxNegative) - - let normalizedPositive = maxValue > 0 ? positiveData.map { $0 / maxValue } : positiveData - let normalizedNegative = maxValue > 0 ? negativeData.map { $0 / maxValue } : negativeData - - let centerY = geometry.size.height / 2 - - ZStack { - // Center line - Path { path in - path.move(to: CGPoint(x: 0, y: centerY)) - path.addLine(to: CGPoint(x: geometry.size.width, y: centerY)) - } - .stroke(Color.gray.opacity(0.3), lineWidth: 1) - - // Positive line and fill - Path { path in - guard !normalizedPositive.isEmpty else { return } - - let stepX = geometry.size.width / CGFloat(max(1, normalizedPositive.count - 1)) - - for (index, value) in normalizedPositive.enumerated() { - let x = CGFloat(index) * stepX - let y = centerY - (centerY * CGFloat(value)) - - if index == 0 { - path.move(to: CGPoint(x: x, y: y)) - } else { - path.addLine(to: CGPoint(x: x, y: y)) - } - } - } - .stroke(positiveColor, lineWidth: 2) - - // Negative line and fill - Path { path in - guard !normalizedNegative.isEmpty else { return } - - let stepX = geometry.size.width / CGFloat(max(1, normalizedNegative.count - 1)) - - for (index, value) in normalizedNegative.enumerated() { - let x = CGFloat(index) * stepX - let y = centerY + (centerY * CGFloat(value)) - - if index == 0 { - path.move(to: CGPoint(x: x, y: y)) - } else { - path.addLine(to: CGPoint(x: x, y: y)) - } - } - } - .stroke(negativeColor, lineWidth: 2) - } - } - .frame(height: 120) - } - } -} - -// MARK: - Compact Components for Two-Row Layout - -struct CompactOverviewCard: View { - let title: String - let value: String - let color: Color - let icon: String - let data: [Double] - - var body: some View { - VStack(spacing: 8) { - // Header - HStack { - Image(systemName: icon) - .foregroundColor(color) - .font(.title3) - - Text(title) - .font(.caption) - .foregroundColor(.secondary) - - Spacer() - - Text(value) - .font(.system(size: 14, weight: .semibold, design: .monospaced)) - .foregroundColor(color) - } - - // Mini graph - CompactGraph(data: data, color: color) - .frame(height: 40) - } - .padding(12) - .background(Color.gray.opacity(0.05)) - .cornerRadius(8) - .frame(height: 80) - } -} - -struct CompactGraph: View { - let data: [Double] - let color: Color - - var body: some View { - Canvas { context, size in - guard data.count > 1 else { return } - - let maxValue = data.max() ?? 1.0 - let path = Path { path in - let dataToUse = Array(data.suffix(30)) // Last 30 points - let stepX = size.width / CGFloat(max(1, dataToUse.count - 1)) - - for (index, value) in dataToUse.enumerated() { - let x = CGFloat(index) * stepX - let normalizedValue = maxValue > 0 ? min(value / maxValue, 1.0) : 0 - let y = size.height - (CGFloat(normalizedValue) * size.height) - - if index == 0 { - path.move(to: CGPoint(x: x, y: y)) - } else { - path.addLine(to: CGPoint(x: x, y: y)) - } - } - } - - // Fill area - let fillPath = Path { fillPath in - let dataToUse = Array(data.suffix(30)) - let stepX = size.width / CGFloat(max(1, dataToUse.count - 1)) - - fillPath.move(to: CGPoint(x: 0, y: size.height)) - - for (index, value) in dataToUse.enumerated() { - let x = CGFloat(index) * stepX - let normalizedValue = maxValue > 0 ? min(value / maxValue, 1.0) : 0 - let y = size.height - (CGFloat(normalizedValue) * size.height) - - if index == 0 { - fillPath.addLine(to: CGPoint(x: x, y: y)) - } else { - fillPath.addLine(to: CGPoint(x: x, y: y)) - } - } - - if !dataToUse.isEmpty { - fillPath.addLine(to: CGPoint(x: size.width, y: size.height)) - } - fillPath.closeSubpath() - } - - context.fill( - fillPath, - with: .linearGradient( - Gradient(colors: [color.opacity(0.3), color.opacity(0.1), Color.clear]), - startPoint: CGPoint(x: 0, y: 0), - endPoint: CGPoint(x: 0, y: size.height) - ) - ) - - context.stroke(path, with: .color(color), lineWidth: 1.5) - } - } -} - -struct CompactDualGraph: View { - let title: String - let primaryLabel: String - let primaryValue: String - let primaryData: [Double] - let primaryColor: Color - let secondaryLabel: String - let secondaryValue: String - let secondaryData: [Double] - let secondaryColor: Color - - var body: some View { - VStack(spacing: 8) { - // Header - HStack { - Text(title) - .font(.caption) - .foregroundColor(.secondary) - - Spacer() - } - - // Legend and values - HStack { - HStack(spacing: 4) { - Circle() - .fill(primaryColor) - .frame(width: 6, height: 6) - Text(primaryLabel) - .font(.caption2) - .foregroundColor(.secondary) - Text(primaryValue) - .font(.system(size: 10, weight: .medium, design: .monospaced)) - .foregroundColor(primaryColor) - } - - Spacer() - - HStack(spacing: 4) { - Circle() - .fill(secondaryColor) - .frame(width: 6, height: 6) - Text(secondaryLabel) - .font(.caption2) - .foregroundColor(.secondary) - Text(secondaryValue) - .font(.system(size: 10, weight: .medium, design: .monospaced)) - .foregroundColor(secondaryColor) - } - } - - // Dual graph - CompactDualLineGraph( - primaryData: primaryData, - primaryColor: primaryColor, - secondaryData: secondaryData, - secondaryColor: secondaryColor - ) - .frame(height: 50) - } - .padding(12) - .background(Color.gray.opacity(0.05)) - .cornerRadius(8) - .frame(height: 100) - } -} - -struct CompactDualLineGraph: View { - let primaryData: [Double] - let primaryColor: Color - let secondaryData: [Double] - let secondaryColor: Color - - var body: some View { - Canvas { context, size in - let maxPrimary = primaryData.max() ?? 1.0 - let maxSecondary = secondaryData.max() ?? 1.0 - let maxValue = max(maxPrimary, maxSecondary) - - // Primary line - if primaryData.count > 1 { - let path = Path { path in - let dataToUse = Array(primaryData.suffix(30)) - let stepX = size.width / CGFloat(max(1, dataToUse.count - 1)) - - for (index, value) in dataToUse.enumerated() { - let x = CGFloat(index) * stepX - let normalizedValue = maxValue > 0 ? min(value / maxValue, 1.0) : 0 - let y = size.height - (CGFloat(normalizedValue) * size.height) - - if index == 0 { - path.move(to: CGPoint(x: x, y: y)) - } else { - path.addLine(to: CGPoint(x: x, y: y)) - } - } - } - context.stroke(path, with: .color(primaryColor), lineWidth: 1.5) - } - - // Secondary line - if secondaryData.count > 1 { - let path = Path { path in - let dataToUse = Array(secondaryData.suffix(30)) - let stepX = size.width / CGFloat(max(1, dataToUse.count - 1)) - - for (index, value) in dataToUse.enumerated() { - let x = CGFloat(index) * stepX - let normalizedValue = maxValue > 0 ? min(value / maxValue, 1.0) : 0 - let y = size.height - (CGFloat(normalizedValue) * size.height) - - if index == 0 { - path.move(to: CGPoint(x: x, y: y)) - } else { - path.addLine(to: CGPoint(x: x, y: y)) - } - } - } - context.stroke(path, with: .color(secondaryColor), lineWidth: 1.5) - } - } - } -} - -#Preview { - StatsPanelView { - print("Close panel") - } - .frame(width: 800, height: 600) -} diff --git a/DynamicIsland/managers/StatsPanelManager.swift b/DynamicIsland/managers/StatsPanelManager.swift deleted file mode 100644 index 8950f14..0000000 --- a/DynamicIsland/managers/StatsPanelManager.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// StatsPanelManager.swift -// DynamicIsland -// -// Created by DynamicIsland App on 2024. -// - -import AppKit -import SwiftUI - -class StatsPanelManager: ObservableObject { - static let shared = StatsPanelManager() - - private var statsPanel: StatsPanel? - - private init() {} - - func showStatsPanel() { - hideStatsPanel() // Close any existing panel - - let panel = StatsPanel() - panel.positionNearNotch() - - self.statsPanel = panel - - // Make the panel key and order front to ensure it can receive focus - panel.makeKeyAndOrderFront(nil) - panel.orderFrontRegardless() - - // Activate the app to ensure proper focus handling - NSApp.activate(ignoringOtherApps: true) - - // Ensure the panel becomes the key window for input - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - panel.makeKey() - } - } - - func hideStatsPanel() { - statsPanel?.close() - statsPanel = nil - } - - func toggleStatsPanel() { - if let panel = statsPanel, panel.isVisible { - hideStatsPanel() - } else { - showStatsPanel() - } - } - - var isPanelVisible: Bool { - return statsPanel?.isVisible ?? false - } -} diff --git a/DynamicIsland/models/Constants.swift b/DynamicIsland/models/Constants.swift index d348173..895bafd 100644 --- a/DynamicIsland/models/Constants.swift +++ b/DynamicIsland/models/Constants.swift @@ -453,7 +453,6 @@ extension Defaults.Keys { static let showGpuGraph = Key("showGpuGraph", default: true) static let showNetworkGraph = Key("showNetworkGraph", default: false) static let showDiskGraph = Key("showDiskGraph", default: false) - static let showStatsPanel = Key("showStatsPanel", default: true) // MARK: Timer Feature static let enableTimerFeature = Key("enableTimerFeature", default: true)