From 7e2c5b4b1685622e772754d01dd10c76571f3c3e Mon Sep 17 00:00:00 2001 From: Beingpax Date: Wed, 28 May 2025 12:39:30 +0545 Subject: [PATCH] Refactor the custom prompt view code. --- VoiceInk/Models/CustomPrompt.swift | 245 +++++++++++++++++++ VoiceInk/PowerMode/PowerModeConfigView.swift | 28 +-- VoiceInk/Views/EnhancementSettingsView.swift | 187 +------------- VoiceInk/Views/PromptEditorView.swift | 8 +- 4 files changed, 270 insertions(+), 198 deletions(-) diff --git a/VoiceInk/Models/CustomPrompt.swift b/VoiceInk/Models/CustomPrompt.swift index dbc8122..7ce6d03 100644 --- a/VoiceInk/Models/CustomPrompt.swift +++ b/VoiceInk/Models/CustomPrompt.swift @@ -1,4 +1,5 @@ import Foundation +import SwiftUI enum PromptIcon: String, Codable, CaseIterable { // Document & Text @@ -103,4 +104,248 @@ struct CustomPrompt: Identifiable, Codable, Equatable { self.isPredefined = isPredefined self.triggerWord = triggerWord } +} + +// MARK: - UI Extensions +extension CustomPrompt { + func promptIcon(isSelected: Bool, onTap: @escaping () -> Void, onEdit: ((CustomPrompt) -> Void)? = nil, onDelete: ((CustomPrompt) -> Void)? = nil, assistantTriggerWord: String? = nil) -> some View { + VStack(spacing: 8) { + ZStack { + // Dynamic background with blur effect + RoundedRectangle(cornerRadius: 14) + .fill( + LinearGradient( + gradient: isSelected ? + Gradient(colors: [ + Color.accentColor.opacity(0.9), + Color.accentColor.opacity(0.7) + ]) : + Gradient(colors: [ + Color(NSColor.controlBackgroundColor).opacity(0.95), + Color(NSColor.controlBackgroundColor).opacity(0.85) + ]), + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + ) + .overlay( + RoundedRectangle(cornerRadius: 14) + .stroke( + LinearGradient( + gradient: Gradient(colors: [ + isSelected ? + Color.white.opacity(0.3) : Color.white.opacity(0.15), + isSelected ? + Color.white.opacity(0.1) : Color.white.opacity(0.05) + ]), + startPoint: .topLeading, + endPoint: .bottomTrailing + ), + lineWidth: 1 + ) + ) + .shadow( + color: isSelected ? + Color.accentColor.opacity(0.4) : Color.black.opacity(0.1), + radius: isSelected ? 10 : 6, + x: 0, + y: 3 + ) + + // Decorative background elements + Circle() + .fill( + RadialGradient( + gradient: Gradient(colors: [ + isSelected ? + Color.white.opacity(0.15) : Color.white.opacity(0.08), + Color.clear + ]), + center: .center, + startRadius: 1, + endRadius: 25 + ) + ) + .frame(width: 50, height: 50) + .offset(x: -15, y: -15) + .blur(radius: 2) + + // Icon with enhanced effects + Image(systemName: icon.rawValue) + .font(.system(size: 20, weight: .medium)) + .foregroundStyle( + LinearGradient( + colors: isSelected ? + [Color.white, Color.white.opacity(0.9)] : + [Color.primary.opacity(0.9), Color.primary.opacity(0.7)], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + ) + .shadow( + color: isSelected ? + Color.white.opacity(0.5) : Color.clear, + radius: 4 + ) + .shadow( + color: isSelected ? + Color.accentColor.opacity(0.5) : Color.clear, + radius: 3 + ) + } + .frame(width: 48, height: 48) + + // Enhanced title styling + VStack(spacing: 2) { + Text(title) + .font(.system(size: 11, weight: .medium)) + .foregroundColor(isSelected ? + .primary : .secondary) + .lineLimit(1) + .frame(maxWidth: 70) + + // Trigger word section with consistent height + ZStack(alignment: .center) { + if id == PredefinedPrompts.assistantPromptId, let assistantTriggerWord = assistantTriggerWord, !assistantTriggerWord.isEmpty { + // Show the global assistant trigger word for the Assistant Mode + HStack(spacing: 2) { + Image(systemName: "mic.fill") + .font(.system(size: 7)) + .foregroundColor(isSelected ? .accentColor.opacity(0.9) : .secondary.opacity(0.7)) + + Text("\"\(assistantTriggerWord)...\"") + .font(.system(size: 8, weight: .regular)) + .foregroundColor(isSelected ? .primary.opacity(0.8) : .secondary.opacity(0.7)) + .lineLimit(1) + } + .frame(maxWidth: 70) + } else if let triggerWord = triggerWord, !triggerWord.isEmpty { + // Show custom trigger words for Enhancement Modes + HStack(spacing: 2) { + Image(systemName: "mic.fill") + .font(.system(size: 7)) + .foregroundColor(isSelected ? .accentColor.opacity(0.9) : .secondary.opacity(0.7)) + + Text("\"\(triggerWord)...\"") + .font(.system(size: 8, weight: .regular)) + .foregroundColor(isSelected ? .primary.opacity(0.8) : .secondary.opacity(0.7)) + .lineLimit(1) + } + .frame(maxWidth: 70) + } + } + .frame(height: 16) // Fixed height for all modes, with or without trigger words + } + } + .padding(.horizontal, 4) + .padding(.vertical, 6) + .contentShape(Rectangle()) + .scaleEffect(isSelected ? 1.05 : 1.0) + .onTapGesture(perform: onTap) + .contextMenu { + if !isPredefined && (onEdit != nil || onDelete != nil) { + if let onEdit = onEdit { + Button { + onEdit(self) + } label: { + Label("Edit", systemImage: "pencil") + } + } + + if let onDelete = onDelete { + Button(role: .destructive) { + onDelete(self) + } label: { + Label("Delete", systemImage: "trash") + } + } + } + } + } + + // Static method to create an "Add New" button with the same styling as the prompt icons + static func addNewButton(action: @escaping () -> Void) -> some View { + VStack(spacing: 8) { + ZStack { + // Dynamic background with blur effect - same styling as promptIcon + RoundedRectangle(cornerRadius: 14) + .fill( + LinearGradient( + gradient: Gradient(colors: [ + Color(NSColor.controlBackgroundColor).opacity(0.95), + Color(NSColor.controlBackgroundColor).opacity(0.85) + ]), + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + ) + .overlay( + RoundedRectangle(cornerRadius: 14) + .stroke( + LinearGradient( + gradient: Gradient(colors: [ + Color.white.opacity(0.15), + Color.white.opacity(0.05) + ]), + startPoint: .topLeading, + endPoint: .bottomTrailing + ), + lineWidth: 1 + ) + ) + .shadow( + color: Color.black.opacity(0.1), + radius: 6, + x: 0, + y: 3 + ) + + // Decorative background elements (same as in promptIcon) + Circle() + .fill( + RadialGradient( + gradient: Gradient(colors: [ + Color.white.opacity(0.08), + Color.clear + ]), + center: .center, + startRadius: 1, + endRadius: 25 + ) + ) + .frame(width: 50, height: 50) + .offset(x: -15, y: -15) + .blur(radius: 2) + + // Plus icon with same styling as the normal icons + Image(systemName: "plus.circle.fill") + .font(.system(size: 20, weight: .medium)) + .foregroundStyle( + LinearGradient( + colors: [Color.accentColor.opacity(0.9), Color.accentColor.opacity(0.7)], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + ) + } + .frame(width: 48, height: 48) + + // Text label with matching styling + VStack(spacing: 2) { + Text("Add New") + .font(.system(size: 11, weight: .medium)) + .foregroundColor(.secondary) + .lineLimit(1) + .frame(maxWidth: 70) + + // Empty space matching the trigger word area height + Spacer() + .frame(height: 16) + } + } + .padding(.horizontal, 4) + .padding(.vertical, 6) + .contentShape(Rectangle()) + .onTapGesture(perform: action) + } } \ No newline at end of file diff --git a/VoiceInk/PowerMode/PowerModeConfigView.swift b/VoiceInk/PowerMode/PowerModeConfigView.swift index 968b641..aa59a05 100644 --- a/VoiceInk/PowerMode/PowerModeConfigView.swift +++ b/VoiceInk/PowerMode/PowerModeConfigView.swift @@ -557,26 +557,14 @@ struct ConfigurationView: View { } - // Enhancement Modes Section (reused from EnhancementSettingsView) + // Enhancement Prompts Section (reused from EnhancementSettingsView) VStack(alignment: .leading, spacing: 12) { - HStack { - Text("Enhancement Modes") - .font(.subheadline) - .foregroundColor(.primary) - Spacer() - Button(action: { isEditingPrompt = true }) { - Image(systemName: "plus.circle.fill") - .symbolRenderingMode(.hierarchical) - .font(.system(size: 26, weight: .medium)) - .foregroundStyle(Color.accentColor) - } - .buttonStyle(.plain) - .contentShape(Circle()) - .help("Add new mode") - } + Text("Enhancement Prompts") + .font(.subheadline) + .foregroundColor(.primary) if enhancementService.allPrompts.isEmpty { - Text("No modes available") + Text("No prompts available") .foregroundColor(.secondary) .font(.caption) } else { @@ -593,6 +581,12 @@ struct ConfigurationView: View { onDelete: { enhancementService.deletePrompt($0) } ) } + + // Plus icon using the same styling as prompt icons + CustomPrompt.addNewButton { + isEditingPrompt = true + } + .help("Add new prompt") } .padding(.vertical, 12) .padding(.horizontal, 16) diff --git a/VoiceInk/Views/EnhancementSettingsView.swift b/VoiceInk/Views/EnhancementSettingsView.swift index 9d049c1..acfa97c 100644 --- a/VoiceInk/Views/EnhancementSettingsView.swift +++ b/VoiceInk/Views/EnhancementSettingsView.swift @@ -1,162 +1,5 @@ import SwiftUI -extension CustomPrompt { - func promptIcon(isSelected: Bool, onTap: @escaping () -> Void, onEdit: ((CustomPrompt) -> Void)? = nil, onDelete: ((CustomPrompt) -> Void)? = nil, assistantTriggerWord: String? = nil) -> some View { - VStack(spacing: 8) { - ZStack { - // Dynamic background with blur effect - RoundedRectangle(cornerRadius: 14) - .fill( - LinearGradient( - gradient: isSelected ? - Gradient(colors: [ - Color.accentColor.opacity(0.9), - Color.accentColor.opacity(0.7) - ]) : - Gradient(colors: [ - Color(NSColor.controlBackgroundColor).opacity(0.95), - Color(NSColor.controlBackgroundColor).opacity(0.85) - ]), - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - .overlay( - RoundedRectangle(cornerRadius: 14) - .stroke( - LinearGradient( - gradient: Gradient(colors: [ - isSelected ? - Color.white.opacity(0.3) : Color.white.opacity(0.15), - isSelected ? - Color.white.opacity(0.1) : Color.white.opacity(0.05) - ]), - startPoint: .topLeading, - endPoint: .bottomTrailing - ), - lineWidth: 1 - ) - ) - .shadow( - color: isSelected ? - Color.accentColor.opacity(0.4) : Color.black.opacity(0.1), - radius: isSelected ? 10 : 6, - x: 0, - y: 3 - ) - - // Decorative background elements - Circle() - .fill( - RadialGradient( - gradient: Gradient(colors: [ - isSelected ? - Color.white.opacity(0.15) : Color.white.opacity(0.08), - Color.clear - ]), - center: .center, - startRadius: 1, - endRadius: 25 - ) - ) - .frame(width: 50, height: 50) - .offset(x: -15, y: -15) - .blur(radius: 2) - - // Icon with enhanced effects - Image(systemName: icon.rawValue) - .font(.system(size: 20, weight: .medium)) - .foregroundStyle( - LinearGradient( - colors: isSelected ? - [Color.white, Color.white.opacity(0.9)] : - [Color.primary.opacity(0.9), Color.primary.opacity(0.7)], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - .shadow( - color: isSelected ? - Color.white.opacity(0.5) : Color.clear, - radius: 4 - ) - .shadow( - color: isSelected ? - Color.accentColor.opacity(0.5) : Color.clear, - radius: 3 - ) - } - .frame(width: 48, height: 48) - - // Enhanced title styling - VStack(spacing: 2) { - Text(title) - .font(.system(size: 11, weight: .medium)) - .foregroundColor(isSelected ? - .primary : .secondary) - .lineLimit(1) - .frame(maxWidth: 70) - - // Trigger word section with consistent height - ZStack(alignment: .center) { - if id == PredefinedPrompts.assistantPromptId, let assistantTriggerWord = assistantTriggerWord, !assistantTriggerWord.isEmpty { - // Show the global assistant trigger word for the Assistant Mode - HStack(spacing: 2) { - Image(systemName: "mic.fill") - .font(.system(size: 7)) - .foregroundColor(isSelected ? .accentColor.opacity(0.9) : .secondary.opacity(0.7)) - - Text("\"\(assistantTriggerWord)...\"") - .font(.system(size: 8, weight: .regular)) - .foregroundColor(isSelected ? .primary.opacity(0.8) : .secondary.opacity(0.7)) - .lineLimit(1) - } - .frame(maxWidth: 70) - } else if let triggerWord = triggerWord, !triggerWord.isEmpty { - // Show custom trigger words for Enhancement Modes - HStack(spacing: 2) { - Image(systemName: "mic.fill") - .font(.system(size: 7)) - .foregroundColor(isSelected ? .accentColor.opacity(0.9) : .secondary.opacity(0.7)) - - Text("\"\(triggerWord)...\"") - .font(.system(size: 8, weight: .regular)) - .foregroundColor(isSelected ? .primary.opacity(0.8) : .secondary.opacity(0.7)) - .lineLimit(1) - } - .frame(maxWidth: 70) - } - } - .frame(height: 16) // Fixed height for all modes, with or without trigger words - } - } - .padding(.horizontal, 4) - .padding(.vertical, 6) - .contentShape(Rectangle()) - .scaleEffect(isSelected ? 1.05 : 1.0) - .onTapGesture(perform: onTap) - .contextMenu { - if !isPredefined && (onEdit != nil || onDelete != nil) { - if let onEdit = onEdit { - Button { - onEdit(self) - } label: { - Label("Edit", systemImage: "pencil") - } - } - - if let onDelete = onDelete { - Button(role: .destructive) { - onDelete(self) - } label: { - Label("Delete", systemImage: "trash") - } - } - } - } - } -} - struct EnhancementSettingsView: View { @EnvironmentObject private var enhancementService: AIEnhancementService @State private var isEditingPrompt = false @@ -243,29 +86,13 @@ struct EnhancementSettingsView: View { // 3. Enhancement Modes & Assistant Section VStack(alignment: .leading, spacing: 16) { - Text("Enhancement Modes & Assistant") + Text("Enhancement Prompt") .font(.headline) - // Modes Section + // Prompts Section VStack(alignment: .leading, spacing: 12) { - HStack { - Text("Enhancement Modes") - .font(.subheadline) - .foregroundColor(.primary) - Spacer() - Button(action: { isEditingPrompt = true }) { - Image(systemName: "plus.circle.fill") - .symbolRenderingMode(.hierarchical) - .font(.system(size: 26, weight: .medium)) - .foregroundStyle(Color.accentColor) - } - .buttonStyle(.plain) - .contentShape(Circle()) - .help("Add new mode") - } - if enhancementService.allPrompts.isEmpty { - Text("No modes available") + Text("No prompts available") .foregroundColor(.secondary) .font(.caption) } else { @@ -285,6 +112,12 @@ struct EnhancementSettingsView: View { assistantTriggerWord: enhancementService.assistantTriggerWord ) } + + // Plus icon using the same styling as prompt icons + CustomPrompt.addNewButton { + isEditingPrompt = true + } + .help("Add new prompt") } .padding(.vertical, 12) .padding(.horizontal, 16) @@ -296,7 +129,7 @@ struct EnhancementSettingsView: View { // Assistant Mode Section VStack(alignment: .leading, spacing: 12) { HStack { - Text("Assistant Mode") + Text("Assistant Prompt") .font(.subheadline) Image(systemName: "sparkles") .foregroundColor(.accentColor) diff --git a/VoiceInk/Views/PromptEditorView.swift b/VoiceInk/Views/PromptEditorView.swift index 8ebf3be..ffe12a1 100644 --- a/VoiceInk/Views/PromptEditorView.swift +++ b/VoiceInk/Views/PromptEditorView.swift @@ -49,7 +49,7 @@ struct PromptEditorView: View { VStack(spacing: 0) { // Header with modern styling HStack { - Text(mode == .add ? "New Mode" : "Edit Mode") + Text(mode == .add ? "New Prompt" : "Edit Prompt") .font(.title2) .fontWeight(.bold) Spacer() @@ -133,7 +133,7 @@ struct PromptEditorView: View { .font(.headline) .foregroundColor(.secondary) - Text("Add a brief description of what this mode does") + Text("Add a brief description of what this prompt does") .font(.subheadline) .foregroundColor(.secondary) @@ -149,7 +149,7 @@ struct PromptEditorView: View { .font(.headline) .foregroundColor(.secondary) - Text("Add a custom word to activate this mode by voice (optional)") + Text("Add a custom word to activate this prompt by voice (optional)") .font(.subheadline) .foregroundColor(.secondary) @@ -161,7 +161,7 @@ struct PromptEditorView: View { // Prompt Text Section with improved styling VStack(alignment: .leading, spacing: 8) { - Text("Mode Instructions") + Text("Prompt Instructions") .font(.headline) .foregroundColor(.secondary)