From 692bd5f9d49f7beb4674b9fc1fb4e7da569df1ce Mon Sep 17 00:00:00 2001 From: Beingpax Date: Fri, 7 Nov 2025 23:58:44 +0545 Subject: [PATCH] Update Sidebar --- VoiceInk/Views/ContentView.swift | 200 ++++++++++++------------------- 1 file changed, 74 insertions(+), 126 deletions(-) diff --git a/VoiceInk/Views/ContentView.swift b/VoiceInk/Views/ContentView.swift index 499842f..34265ee 100644 --- a/VoiceInk/Views/ContentView.swift +++ b/VoiceInk/Views/ContentView.swift @@ -3,7 +3,7 @@ import SwiftData import KeyboardShortcuts // ViewType enum with all cases -enum ViewType: String, CaseIterable { +enum ViewType: String, CaseIterable, Identifiable { case metrics = "Dashboard" case transcribeAudio = "Transcribe Audio" case history = "History" @@ -15,7 +15,9 @@ enum ViewType: String, CaseIterable { case dictionary = "Dictionary" case settings = "Settings" case license = "VoiceInk Pro" - + + var id: String { rawValue } + var icon: String { switch self { case .metrics: return "gauge.medium" @@ -36,7 +38,7 @@ enum ViewType: String, CaseIterable { struct VisualEffectView: NSViewRepresentable { let material: NSVisualEffectView.Material let blendingMode: NSVisualEffectView.BlendingMode - + func makeNSView(context: Context) -> NSVisualEffectView { let visualEffectView = NSVisualEffectView() visualEffectView.material = material @@ -44,21 +46,23 @@ struct VisualEffectView: NSViewRepresentable { visualEffectView.state = .active return visualEffectView } - + func updateNSView(_ visualEffectView: NSVisualEffectView, context: Context) { visualEffectView.material = material visualEffectView.blendingMode = blendingMode } } -struct DynamicSidebar: View { - @Binding var selectedView: ViewType - @Binding var hoveredView: ViewType? +struct ContentView: View { + @Environment(\.modelContext) private var modelContext @Environment(\.colorScheme) private var colorScheme + @EnvironmentObject private var whisperState: WhisperState + @EnvironmentObject private var hotkeyManager: HotkeyManager @AppStorage("powerModeUIFlag") private var powerModeUIFlag = false + @State private var selectedView: ViewType? = .metrics + let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0" @StateObject private var licenseViewModel = LicenseViewModel() - @Namespace private var buttonAnimation - + private var visibleViewTypes: [ViewType] { ViewType.allCases.filter { viewType in if viewType == .powerMode { @@ -69,129 +73,73 @@ struct DynamicSidebar: View { } var body: some View { - VStack(spacing: 15) { - // App Header - HStack(spacing: 6) { - if let appIcon = NSImage(named: "AppIcon") { - Image(nsImage: appIcon) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 28, height: 28) - .cornerRadius(8) - } - - Text("VoiceInk") - .font(.system(size: 14, weight: .semibold)) - - if case .licensed = licenseViewModel.licenseState { - Text("PRO") - .font(.system(size: 9, weight: .heavy)) - .foregroundStyle(.white) - .padding(.horizontal, 4) - .padding(.vertical, 2) - .background(Color.blue) - .cornerRadius(4) - } - - Spacer() - } - .padding(.horizontal, 16) - .padding(.vertical, 12) - - // Navigation Items - ForEach(visibleViewTypes, id: \.self) { viewType in - DynamicSidebarButton( - title: viewType.rawValue, - systemImage: viewType.icon, - isSelected: selectedView == viewType, - isHovered: hoveredView == viewType, - namespace: buttonAnimation - ) { - selectedView = viewType - } - .onHover { isHovered in - hoveredView = isHovered ? viewType : nil - } - } - - Spacer() - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - } -} + NavigationSplitView { + List(selection: $selectedView) { + Section { + // App Header + HStack(spacing: 6) { + if let appIcon = NSImage(named: "AppIcon") { + Image(nsImage: appIcon) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 28, height: 28) + .cornerRadius(8) + } -struct DynamicSidebarButton: View { - let title: String - let systemImage: String - let isSelected: Bool - let isHovered: Bool - let namespace: Namespace.ID - let action: () -> Void - - @Environment(\.colorScheme) private var colorScheme + Text("VoiceInk") + .font(.system(size: 14, weight: .semibold)) - var body: some View { - Button(action: action) { - HStack(spacing: 12) { - Image(systemName: systemImage) - .font(.system(size: 18, weight: .medium)) - .frame(width: 24, height: 24) - - Text(title) - .font(.system(size: 14, weight: .medium)) - .lineLimit(1) - Spacer() - } - .foregroundColor(isSelected ? .white : (isHovered ? .accentColor : .primary)) - .frame(height: 40) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.leading, 16) - .background( - ZStack { - if isSelected { - RoundedRectangle(cornerRadius: 12) - .fill(Color.accentColor) - .shadow(color: Color.accentColor.opacity(0.5), radius: 5, x: 0, y: 2) - } else if isHovered { - RoundedRectangle(cornerRadius: 12) - .fill(colorScheme == .dark ? Color.white.opacity(0.1) : Color.black.opacity(0.05)) + if case .licensed = licenseViewModel.licenseState { + Text("PRO") + .font(.system(size: 9, weight: .heavy)) + .foregroundStyle(.white) + .padding(.horizontal, 4) + .padding(.vertical, 2) + .background(Color.blue) + .cornerRadius(4) + } + + Spacer() + } + .padding(.vertical, 4) + } + + ForEach(visibleViewTypes) { viewType in + Section { + NavigationLink(value: viewType) { + HStack(spacing: 12) { + Image(systemName: viewType.icon) + .font(.system(size: 18, weight: .medium)) + .frame(width: 24, height: 24) + + Text(viewType.rawValue) + .font(.system(size: 14, weight: .medium)) + + Spacer() + } + .padding(.vertical, 8) + .padding(.horizontal, 2) + } + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + .listRowSeparator(.hidden) } } - ) - .padding(.horizontal, 8) - } - .buttonStyle(PlainButtonStyle()) - } -} - -struct ContentView: View { - @Environment(\.modelContext) private var modelContext - @Environment(\.colorScheme) private var colorScheme - @EnvironmentObject private var whisperState: WhisperState - @EnvironmentObject private var hotkeyManager: HotkeyManager - @AppStorage("powerModeUIFlag") private var powerModeUIFlag = false - @State private var selectedView: ViewType = .metrics - @State private var hoveredView: ViewType? - let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0" - @StateObject private var licenseViewModel = LicenseViewModel() - - var body: some View { - NavigationSplitView { - DynamicSidebar( - selectedView: $selectedView, - hoveredView: $hoveredView - ) - .frame(width: 200) - .navigationSplitViewColumnWidth(200) + } + .listStyle(.sidebar) + .navigationTitle("VoiceInk") + .navigationSplitViewColumnWidth(210) } detail: { - detailView - .frame(maxWidth: .infinity, maxHeight: .infinity) - .toolbar(.hidden, for: .automatic) - .navigationTitle("") + if let selectedView = selectedView { + detailView(for: selectedView) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .navigationTitle(selectedView.rawValue) + } else { + Text("Select a view") + .foregroundColor(.secondary) + } } .navigationSplitViewStyle(.balanced) .frame(minWidth: 940, minHeight: 730) - // inside ContentView body: .onReceive(NotificationCenter.default.publisher(for: .navigateToDestination)) { notification in if let destination = notification.userInfo?["destination"] as? String { switch destination { @@ -219,8 +167,8 @@ struct ContentView: View { } @ViewBuilder - private var detailView: some View { - switch selectedView { + private func detailView(for viewType: ViewType) -> some View { + switch viewType { case .metrics: MetricsView() case .models: