fix: enhance app scanning to include applications in subfolders using FileManager.enumerator for recursive directory traversal
This commit is contained in:
parent
726da26a12
commit
41a33906b3
@ -468,7 +468,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 96;
|
||||
CURRENT_PROJECT_VERSION = 99;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"VoiceInk/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = V6J6A3VWY2;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
@ -483,7 +483,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 0.97;
|
||||
MARKETING_VERSION = 0.99;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.prakashjoshipax.VoiceInk;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
@ -501,7 +501,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 96;
|
||||
CURRENT_PROJECT_VERSION = 99;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"VoiceInk/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = V6J6A3VWY2;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
@ -516,7 +516,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||
MARKETING_VERSION = 0.97;
|
||||
MARKETING_VERSION = 0.99;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.prakashjoshipax.VoiceInk;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
|
||||
@ -260,12 +260,18 @@ struct ConfigurationSheet: View {
|
||||
let systemAppURLs = FileManager.default.urls(for: .applicationDirectory, in: .systemDomainMask)
|
||||
let allAppURLs = userAppURLs + systemAppURLs
|
||||
|
||||
let apps = allAppURLs.flatMap { url -> [URL] in
|
||||
return (try? FileManager.default.contentsOfDirectory(
|
||||
at: url,
|
||||
let apps = allAppURLs.flatMap { baseURL -> [URL] in
|
||||
let enumerator = FileManager.default.enumerator(
|
||||
at: baseURL,
|
||||
includingPropertiesForKeys: [.isApplicationKey],
|
||||
options: [.skipsHiddenFiles]
|
||||
)) ?? []
|
||||
options: [.skipsHiddenFiles, .skipsPackageDescendants]
|
||||
)
|
||||
|
||||
return enumerator?.compactMap { item -> URL? in
|
||||
guard let url = item as? URL,
|
||||
url.pathExtension == "app" else { return nil }
|
||||
return url
|
||||
} ?? []
|
||||
}
|
||||
|
||||
installedApps = apps.compactMap { url in
|
||||
@ -449,313 +455,3 @@ struct PowerModeView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
310
VoiceInk/Views/PowerModeViewComponents.swift
Normal file
310
VoiceInk/Views/PowerModeViewComponents.swift
Normal file
@ -0,0 +1,310 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user