Update Sidebar

This commit is contained in:
Beingpax 2025-11-07 23:58:44 +05:45
parent f559d19390
commit 692bd5f9d4

View File

@ -3,7 +3,7 @@ import SwiftData
import KeyboardShortcuts import KeyboardShortcuts
// ViewType enum with all cases // ViewType enum with all cases
enum ViewType: String, CaseIterable { enum ViewType: String, CaseIterable, Identifiable {
case metrics = "Dashboard" case metrics = "Dashboard"
case transcribeAudio = "Transcribe Audio" case transcribeAudio = "Transcribe Audio"
case history = "History" case history = "History"
@ -16,6 +16,8 @@ enum ViewType: String, CaseIterable {
case settings = "Settings" case settings = "Settings"
case license = "VoiceInk Pro" case license = "VoiceInk Pro"
var id: String { rawValue }
var icon: String { var icon: String {
switch self { switch self {
case .metrics: return "gauge.medium" case .metrics: return "gauge.medium"
@ -51,13 +53,15 @@ struct VisualEffectView: NSViewRepresentable {
} }
} }
struct DynamicSidebar: View { struct ContentView: View {
@Binding var selectedView: ViewType @Environment(\.modelContext) private var modelContext
@Binding var hoveredView: ViewType?
@Environment(\.colorScheme) private var colorScheme @Environment(\.colorScheme) private var colorScheme
@EnvironmentObject private var whisperState: WhisperState
@EnvironmentObject private var hotkeyManager: HotkeyManager
@AppStorage("powerModeUIFlag") private var powerModeUIFlag = false @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() @StateObject private var licenseViewModel = LicenseViewModel()
@Namespace private var buttonAnimation
private var visibleViewTypes: [ViewType] { private var visibleViewTypes: [ViewType] {
ViewType.allCases.filter { viewType in ViewType.allCases.filter { viewType in
@ -69,129 +73,73 @@ struct DynamicSidebar: View {
} }
var body: some View { var body: some View {
VStack(spacing: 15) { NavigationSplitView {
// App Header List(selection: $selectedView) {
HStack(spacing: 6) { Section {
if let appIcon = NSImage(named: "AppIcon") { // App Header
Image(nsImage: appIcon) HStack(spacing: 6) {
.resizable() if let appIcon = NSImage(named: "AppIcon") {
.aspectRatio(contentMode: .fit) Image(nsImage: appIcon)
.frame(width: 28, height: 28) .resizable()
.cornerRadius(8) .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(.vertical, 4)
} }
Text("VoiceInk") ForEach(visibleViewTypes) { viewType in
.font(.system(size: 14, weight: .semibold)) Section {
NavigationLink(value: viewType) {
HStack(spacing: 12) {
Image(systemName: viewType.icon)
.font(.system(size: 18, weight: .medium))
.frame(width: 24, height: 24)
if case .licensed = licenseViewModel.licenseState { Text(viewType.rawValue)
Text("PRO") .font(.system(size: 14, weight: .medium))
.font(.system(size: 9, weight: .heavy))
.foregroundStyle(.white)
.padding(.horizontal, 4)
.padding(.vertical, 2)
.background(Color.blue)
.cornerRadius(4)
}
Spacer() Spacer()
} }
.padding(.horizontal, 16) .padding(.vertical, 8)
.padding(.vertical, 12) .padding(.horizontal, 2)
}
// Navigation Items .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
ForEach(visibleViewTypes, id: \.self) { viewType in .listRowSeparator(.hidden)
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)
}
}
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
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))
} }
} }
) }
.padding(.horizontal, 8) .listStyle(.sidebar)
} .navigationTitle("VoiceInk")
.buttonStyle(PlainButtonStyle()) .navigationSplitViewColumnWidth(210)
}
}
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)
} detail: { } detail: {
detailView if let selectedView = selectedView {
.frame(maxWidth: .infinity, maxHeight: .infinity) detailView(for: selectedView)
.toolbar(.hidden, for: .automatic) .frame(maxWidth: .infinity, maxHeight: .infinity)
.navigationTitle("") .navigationTitle(selectedView.rawValue)
} else {
Text("Select a view")
.foregroundColor(.secondary)
}
} }
.navigationSplitViewStyle(.balanced) .navigationSplitViewStyle(.balanced)
.frame(minWidth: 940, minHeight: 730) .frame(minWidth: 940, minHeight: 730)
// inside ContentView body:
.onReceive(NotificationCenter.default.publisher(for: .navigateToDestination)) { notification in .onReceive(NotificationCenter.default.publisher(for: .navigateToDestination)) { notification in
if let destination = notification.userInfo?["destination"] as? String { if let destination = notification.userInfo?["destination"] as? String {
switch destination { switch destination {
@ -219,8 +167,8 @@ struct ContentView: View {
} }
@ViewBuilder @ViewBuilder
private var detailView: some View { private func detailView(for viewType: ViewType) -> some View {
switch selectedView { switch viewType {
case .metrics: case .metrics:
MetricsView() MetricsView()
case .models: case .models: