vOOice/VoiceInk/Views/PowerModeViewComponents.swift

311 lines
12 KiB
Swift

import SwiftUI
// Supporting Views
struct PowerModeEmptyStateView: View {
@Binding var showAddModal: Bool
@Binding var configMode: ConfigurationMode?
var body: some View {
VStack(spacing: 16) {
Image(systemName: "bolt.circle.fill")
.font(.system(size: 48))
.foregroundColor(.secondary)
Text("No Applications Configured")
.font(.title2)
.fontWeight(.semibold)
Text("Add applications to customize their AI enhancement settings.")
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
Button(action: {
print("🔍 Empty state Add Application button clicked")
configMode = .add
print("🔍 Configuration mode set to: \(String(describing: configMode))")
showAddModal = true
print("🔍 Empty state showAddModal set to: \(showAddModal)")
}) {
Label("Add Application", systemImage: "plus.circle.fill")
.font(.headline)
}
.buttonStyle(.borderedProminent)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct ConfiguredAppsGrid: View {
@ObservedObject var powerModeManager: PowerModeManager
@EnvironmentObject var enhancementService: AIEnhancementService
@State private var editingConfig: PowerModeConfig?
@State private var showingConfigSheet = false
var body: some View {
ScrollView {
VStack(spacing: 8) {
ForEach(powerModeManager.configurations.sorted(by: { $0.appName.localizedCaseInsensitiveCompare($1.appName) == .orderedAscending })) { config in
ConfiguredAppRow(
config: config,
isEditing: editingConfig?.id == config.id,
action: {
editingConfig = config
showingConfigSheet = true
}
)
.contextMenu {
Button(action: {
editingConfig = config
showingConfigSheet = true
}) {
Label("Edit", systemImage: "pencil")
}
Button(role: .destructive, action: {
powerModeManager.removeConfiguration(for: config.bundleIdentifier)
}) {
Label("Remove", systemImage: "trash")
}
}
}
}
.padding()
}
.sheet(isPresented: $showingConfigSheet, onDismiss: { editingConfig = nil }) {
if let config = editingConfig {
ConfigurationSheet(
mode: .edit(config),
isPresented: $showingConfigSheet,
powerModeManager: powerModeManager
)
.environmentObject(enhancementService)
}
}
}
}
struct ConfiguredAppRow: View {
let config: PowerModeConfig
let isEditing: Bool
let action: () -> Void
@EnvironmentObject var enhancementService: AIEnhancementService
private var selectedPrompt: CustomPrompt? {
guard let promptId = config.selectedPrompt,
let uuid = UUID(uuidString: promptId) else { return nil }
return enhancementService.allPrompts.first { $0.id == uuid }
}
private var isWebsiteConfig: Bool {
return config.urlConfigs != nil && !config.urlConfigs!.isEmpty
}
var body: some View {
Button(action: action) {
HStack(spacing: 12) {
// Icon
if isWebsiteConfig {
Image(systemName: "globe")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 32, height: 32)
.foregroundColor(.accentColor)
} else if let appURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: config.bundleIdentifier) {
Image(nsImage: NSWorkspace.shared.icon(forFile: appURL.path))
.resizable()
.frame(width: 32, height: 32)
}
// Info
VStack(alignment: .leading, spacing: 2) {
Text(config.appName)
.font(.headline)
if isWebsiteConfig {
if let urlConfig = config.urlConfigs?.first {
Text(urlConfig.url)
.font(.caption)
.foregroundColor(.secondary)
}
} else {
Text(config.bundleIdentifier)
.font(.caption)
.foregroundColor(.secondary)
}
}
Spacer()
HStack(spacing: 8) {
HStack(spacing: 4) {
Image(systemName: config.isAIEnhancementEnabled ? "checkmark.circle.fill" : "circle")
.foregroundColor(config.isAIEnhancementEnabled ? .accentColor : .secondary)
.font(.system(size: 14))
Text("AI Enhancement")
.font(.system(size: 12, weight: .medium))
.foregroundColor(config.isAIEnhancementEnabled ? .accentColor : .secondary)
}
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(RoundedRectangle(cornerRadius: 6)
.fill(config.isAIEnhancementEnabled ? Color.accentColor.opacity(0.1) : Color.secondary.opacity(0.1)))
if config.isAIEnhancementEnabled {
if let prompt = selectedPrompt {
HStack(spacing: 4) {
Image(systemName: prompt.icon.rawValue)
.foregroundColor(.accentColor)
.font(.system(size: 14))
Text(prompt.title)
.font(.system(size: 12))
.foregroundColor(.secondary)
}
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(RoundedRectangle(cornerRadius: 6)
.fill(Color.accentColor.opacity(0.1)))
} else {
Text("No Prompt")
.font(.system(size: 12))
.foregroundColor(.secondary)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(RoundedRectangle(cornerRadius: 6)
.fill(Color.secondary.opacity(0.1)))
}
}
}
}
.contentShape(Rectangle())
.padding(12)
.background(RoundedRectangle(cornerRadius: 8)
.fill(isEditing ? Color.accentColor.opacity(0.1) : Color(.windowBackgroundColor).opacity(0.4)))
.overlay(RoundedRectangle(cornerRadius: 8)
.stroke(isEditing ? Color.accentColor : Color.clear, lineWidth: 1))
}
.buttonStyle(.plain)
}
}
struct AppConfigurationFormView: View {
let appName: String?
let appIcon: NSImage?
let isDefaultConfig: Bool
@Binding var isAIEnhancementEnabled: Bool
@Binding var selectedPromptId: UUID?
@EnvironmentObject var enhancementService: AIEnhancementService
var body: some View {
VStack(spacing: 20) {
VStack(spacing: 16) {
if !isDefaultConfig {
if let appIcon = appIcon {
HStack {
Image(nsImage: appIcon)
.resizable()
.frame(width: 32, height: 32)
Text(appName ?? "")
.font(.headline)
Spacer()
}
}
} else {
HStack {
Image(systemName: "gearshape.fill")
.font(.system(size: 24))
.foregroundColor(.accentColor)
Text("Default Settings")
.font(.headline)
Spacer()
}
Text("These settings will be applied to all applications that don't have specific configurations.")
.font(.subheadline)
.foregroundColor(.secondary)
.padding(.bottom, 8)
}
Toggle("AI Enhancement", isOn: $isAIEnhancementEnabled)
}
.padding(.horizontal)
if isAIEnhancementEnabled {
Divider()
VStack(alignment: .leading) {
Text("Select Prompt")
.font(.headline)
.padding(.horizontal)
let columns = [
GridItem(.adaptive(minimum: 80, maximum: 100), spacing: 36)
]
LazyVGrid(columns: columns, spacing: 24) {
ForEach(enhancementService.allPrompts) { prompt in
prompt.promptIcon(
isSelected: selectedPromptId == prompt.id,
onTap: { selectedPromptId = prompt.id }
)
}
}
.padding(.vertical, 12)
.padding(.horizontal, 16)
}
}
}
.padding(.vertical)
}
}
struct AppGridItem: View {
let app: (url: URL, name: String, bundleId: String, icon: NSImage)
let isSelected: Bool
let action: () -> Void
var body: some View {
Button(action: action) {
VStack(spacing: 8) {
Image(nsImage: app.icon)
.resizable()
.frame(width: 48, height: 48)
Text(app.name)
.font(.system(size: 12))
.lineLimit(2)
.multilineTextAlignment(.center)
.frame(height: 32)
}
.frame(width: 100)
.padding(8)
.background(RoundedRectangle(cornerRadius: 8)
.fill(isSelected ? Color.accentColor.opacity(0.1) : Color.clear))
.overlay(RoundedRectangle(cornerRadius: 8)
.stroke(isSelected ? Color.accentColor : Color.clear, lineWidth: 1))
}
.buttonStyle(.plain)
}
}
// New component for feature highlights
struct FeatureHighlight: View {
let icon: String
let title: String
let description: String
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 6) {
Image(systemName: icon)
.font(.system(size: 14, weight: .semibold))
.foregroundStyle(.blue)
Text(title)
.font(.system(size: 13, weight: .semibold))
}
Text(description)
.font(.system(size: 12))
.foregroundColor(.secondary)
.lineLimit(2)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}