Update UI for Power Mode
This commit is contained in:
parent
a26ec91e1b
commit
7530d3e9e7
@ -159,14 +159,17 @@ struct ConfigurationView: View {
|
||||
.foregroundColor(.primary)
|
||||
.tint(.accentColor)
|
||||
.focused($isNameFieldFocused)
|
||||
.onAppear {
|
||||
isNameFieldFocused = true
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 16)
|
||||
.background(CardBackground(isSelected: false))
|
||||
.padding(.horizontal)
|
||||
.onAppear {
|
||||
// Add a small delay to ensure the view is fully loaded
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
isNameFieldFocused = true
|
||||
}
|
||||
}
|
||||
|
||||
// Enhanced Emoji Picker with Custom Emoji Support
|
||||
// if isShowingEmojiPicker { // <<< This conditional block will be removed
|
||||
|
||||
@ -73,103 +73,122 @@ struct PowerModeView: View {
|
||||
|
||||
var body: some View {
|
||||
NavigationStack(path: $navigationPath) {
|
||||
ScrollView {
|
||||
VStack(spacing: 24) {
|
||||
// Header Section
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack(alignment: .center) {
|
||||
Text("Power Mode")
|
||||
.font(.system(size: 22, weight: .bold))
|
||||
VStack(spacing: 0) {
|
||||
// Header Section with proper macOS styling
|
||||
VStack(spacing: 12) {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack(spacing: 8) {
|
||||
Text("Power Modes")
|
||||
.font(.system(size: 28, weight: .bold, design: .default))
|
||||
.foregroundColor(.primary)
|
||||
|
||||
// InfoTip for Power Mode
|
||||
InfoTip(
|
||||
title: "What is Power Mode?",
|
||||
message: "Automatically apply custom configurations based on the app/website you are using",
|
||||
learnMoreURL: "https://www.youtube.com/watch?v=-xFLvgNs_Iw"
|
||||
)
|
||||
}
|
||||
|
||||
InfoTip(
|
||||
title: "Power Mode",
|
||||
message: "Create custom modes that automatically apply when using specific apps/websites.",
|
||||
learnMoreURL: "https://www.youtube.com/watch?v=cEepexxgf6Y&t=10s"
|
||||
)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
Text("Automate your workflows with context-aware configurations.")
|
||||
.font(.system(size: 14))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
Text("Automatically apply custom configurations based on the app/website you are using")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
Spacer()
|
||||
|
||||
// Add button in header for better macOS UX
|
||||
Button(action: {
|
||||
configurationMode = .add
|
||||
navigationPath.append(configurationMode!)
|
||||
}) {
|
||||
HStack(spacing: 6) {
|
||||
Image(systemName: "plus")
|
||||
.font(.system(size: 12, weight: .medium))
|
||||
Text("Add Power Mode")
|
||||
.font(.system(size: 13, weight: .medium))
|
||||
}
|
||||
.foregroundColor(.white)
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 6)
|
||||
.background(Color.accentColor)
|
||||
.cornerRadius(6)
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.top, 8)
|
||||
|
||||
// Configurations Container
|
||||
VStack(spacing: 0) {
|
||||
// Custom Configurations Section
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("Custom Power Modes")
|
||||
.font(.headline)
|
||||
.foregroundColor(.primary)
|
||||
.padding(.horizontal)
|
||||
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.top, 20)
|
||||
.padding(.bottom, 16)
|
||||
|
||||
// Separator
|
||||
Rectangle()
|
||||
.fill(Color(NSColor.separatorColor))
|
||||
.frame(height: 1)
|
||||
.padding(.horizontal, 24)
|
||||
|
||||
// Main Content Area
|
||||
GeometryReader { geometry in
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
if powerModeManager.configurations.isEmpty {
|
||||
VStack(spacing: 20) {
|
||||
Image(systemName: "square.grid.2x2")
|
||||
.font(.system(size: 36))
|
||||
.foregroundColor(.secondary)
|
||||
// Empty State - Centered and symmetric
|
||||
VStack(spacing: 24) {
|
||||
Spacer()
|
||||
.frame(height: geometry.size.height * 0.2)
|
||||
|
||||
Text("No power modes configured")
|
||||
.font(.title3)
|
||||
.fontWeight(.medium)
|
||||
VStack(spacing: 16) {
|
||||
Image(systemName: "square.grid.2x2.fill")
|
||||
.font(.system(size: 48, weight: .regular))
|
||||
.foregroundColor(.secondary.opacity(0.6))
|
||||
|
||||
VStack(spacing: 8) {
|
||||
Text("No Power Modes Yet")
|
||||
.font(.system(size: 20, weight: .medium))
|
||||
.foregroundColor(.primary)
|
||||
|
||||
Text("Create your first power mode to enhance your productivity\nwith context-aware AI assistance")
|
||||
.font(.system(size: 14))
|
||||
.foregroundColor(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.lineSpacing(2)
|
||||
}
|
||||
}
|
||||
|
||||
Text("Create a new power mode to get started.")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 30)
|
||||
.frame(minHeight: geometry.size.height)
|
||||
} else {
|
||||
PowerModeConfigurationsGrid(
|
||||
powerModeManager: powerModeManager,
|
||||
onEditConfig: { config in
|
||||
configurationMode = .edit(config)
|
||||
navigationPath.append(configurationMode!)
|
||||
}
|
||||
)
|
||||
// Configurations Grid with symmetric padding
|
||||
VStack(spacing: 0) {
|
||||
PowerModeConfigurationsGrid(
|
||||
powerModeManager: powerModeManager,
|
||||
onEditConfig: { config in
|
||||
configurationMode = .edit(config)
|
||||
navigationPath.append(configurationMode!)
|
||||
}
|
||||
)
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.vertical, 20)
|
||||
|
||||
// Bottom spacing for visual balance
|
||||
Spacer()
|
||||
.frame(height: 40)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(minLength: 24)
|
||||
|
||||
// Add Configuration button at the bottom (centered)
|
||||
HStack {
|
||||
VoiceInkButton(
|
||||
title: "Add New Power Mode",
|
||||
action: {
|
||||
configurationMode = .add
|
||||
navigationPath.append(configurationMode!)
|
||||
}
|
||||
)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom, 16)
|
||||
}
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 16)
|
||||
.fill(Color(NSColor.controlBackgroundColor))
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 16)
|
||||
.stroke(Color(NSColor.separatorColor), lineWidth: 1)
|
||||
)
|
||||
.shadow(color: Color(NSColor.shadowColor).opacity(0.05), radius: 5, y: 2)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.padding(.bottom, 24)
|
||||
}
|
||||
.background(Color(NSColor.controlBackgroundColor))
|
||||
.navigationTitle("")
|
||||
.navigationDestination(for: ConfigurationMode.self) { mode in
|
||||
ConfigurationView(mode: mode, powerModeManager: powerModeManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -58,15 +58,12 @@ struct PowerModeConfigurationsGrid: View {
|
||||
|
||||
var body: some View {
|
||||
LazyVStack(spacing: 12) {
|
||||
ForEach(powerModeManager.configurations) { config in
|
||||
ForEach($powerModeManager.configurations) { $config in
|
||||
ConfigurationRow(
|
||||
config: config,
|
||||
config: $config,
|
||||
isEditing: false,
|
||||
isDefault: false,
|
||||
powerModeManager: powerModeManager,
|
||||
action: {
|
||||
onEditConfig(config)
|
||||
}
|
||||
onEditConfig: onEditConfig
|
||||
)
|
||||
.contextMenu {
|
||||
Button(action: {
|
||||
@ -87,11 +84,10 @@ struct PowerModeConfigurationsGrid: View {
|
||||
}
|
||||
|
||||
struct ConfigurationRow: View {
|
||||
@State var config: PowerModeConfig
|
||||
@Binding var config: PowerModeConfig
|
||||
let isEditing: Bool
|
||||
let isDefault: Bool
|
||||
let powerModeManager: PowerModeManager
|
||||
let action: () -> Void
|
||||
let onEditConfig: (PowerModeConfig) -> Void
|
||||
@EnvironmentObject var enhancementService: AIEnhancementService
|
||||
@EnvironmentObject var whisperState: WhisperState
|
||||
|
||||
@ -150,203 +146,174 @@ struct ConfigurationRow: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Button(action: action) {
|
||||
VStack(spacing: 0) {
|
||||
// Top row: Emoji, Name, and App/Website counts
|
||||
HStack(spacing: 12) {
|
||||
// Left: Emoji/Icon
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(isDefault ? Color.accentColor.opacity(0.15) : Color(.controlBackgroundColor))
|
||||
.frame(width: 40, height: 40)
|
||||
|
||||
if isDefault {
|
||||
Image(systemName: "gearshape.fill")
|
||||
.font(.system(size: 18))
|
||||
.foregroundColor(.accentColor)
|
||||
} else {
|
||||
Text(config.emoji)
|
||||
.font(.system(size: 20))
|
||||
}
|
||||
}
|
||||
VStack(spacing: 0) {
|
||||
// Top row: Emoji, Name, and App/Website counts
|
||||
HStack(spacing: 12) {
|
||||
// Left: Emoji/Icon
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(Color(NSColor.controlBackgroundColor))
|
||||
.frame(width: 40, height: 40)
|
||||
|
||||
// Middle: Name and badge
|
||||
VStack(alignment: .leading, spacing: 3) {
|
||||
HStack(spacing: 6) {
|
||||
Text(config.name)
|
||||
.font(.system(size: 15, weight: .semibold))
|
||||
|
||||
if isDefault {
|
||||
Text("Default")
|
||||
.font(.system(size: 10, weight: .medium))
|
||||
.padding(.horizontal, 5)
|
||||
.padding(.vertical, 2)
|
||||
.background(Capsule().fill(Color.accentColor.opacity(0.15)))
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
}
|
||||
|
||||
if isDefault {
|
||||
Text("Fallback power mode")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
// Right: App Icons and Website Count
|
||||
if !isDefault {
|
||||
HStack(alignment: .center, spacing: 6) {
|
||||
// App Count
|
||||
if appCount > 0 {
|
||||
HStack(spacing: 3) {
|
||||
Text(appText)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Image(systemName: "app.fill")
|
||||
.font(.system(size: 9))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
// Website Count
|
||||
if websiteCount > 0 {
|
||||
HStack(spacing: 3) {
|
||||
Text(websiteText)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Image(systemName: "globe")
|
||||
.font(.system(size: 9))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Text(config.emoji)
|
||||
.font(.system(size: 20))
|
||||
}
|
||||
.padding(.vertical, 12)
|
||||
.padding(.horizontal, 14)
|
||||
|
||||
// Only add divider and settings row if we have settings
|
||||
if selectedModel != nil || selectedLanguage != nil || config.isAIEnhancementEnabled {
|
||||
Divider()
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
// Middle: Name and badge
|
||||
VStack(alignment: .leading, spacing: 3) {
|
||||
HStack(spacing: 6) {
|
||||
Text(config.name)
|
||||
.font(.system(size: 15, weight: .semibold))
|
||||
}
|
||||
|
||||
// Settings badges in specified order
|
||||
HStack(spacing: 8) {
|
||||
// 1. Voice Model badge
|
||||
if let model = selectedModel, model != "Default" {
|
||||
// Display app and website counts
|
||||
HStack(spacing: 12) {
|
||||
if appCount > 0 {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "waveform")
|
||||
Image(systemName: "app.fill")
|
||||
.font(.system(size: 10))
|
||||
Text(model)
|
||||
.font(.caption)
|
||||
Text(appText)
|
||||
.font(.caption2)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 4)
|
||||
.background(Capsule()
|
||||
.fill(Color(.controlBackgroundColor)))
|
||||
.overlay(
|
||||
Capsule()
|
||||
.stroke(Color(.separatorColor), lineWidth: 0.5)
|
||||
)
|
||||
}
|
||||
|
||||
// 2. Language badge
|
||||
if let language = selectedLanguage, language != "Default" {
|
||||
if websiteCount > 0 {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "globe")
|
||||
.font(.system(size: 10))
|
||||
Text(language)
|
||||
.font(.caption)
|
||||
Text(websiteText)
|
||||
.font(.caption2)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 4)
|
||||
.background(Capsule()
|
||||
.fill(Color(.controlBackgroundColor)))
|
||||
.overlay(
|
||||
Capsule()
|
||||
.stroke(Color(.separatorColor), lineWidth: 0.5)
|
||||
)
|
||||
}
|
||||
|
||||
// 3. AI Model badge if specified (moved before AI Enhancement)
|
||||
if config.isAIEnhancementEnabled, let modelName = config.selectedAIModel, !modelName.isEmpty {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "cpu")
|
||||
.font(.system(size: 10))
|
||||
// Display a shortened version of the model name if it's too long (increased limit)
|
||||
Text(modelName.count > 20 ? String(modelName.prefix(18)) + "..." : modelName)
|
||||
.font(.caption)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 4)
|
||||
.background(Capsule()
|
||||
.fill(Color(.controlBackgroundColor)))
|
||||
.overlay(
|
||||
Capsule()
|
||||
.stroke(Color(.separatorColor), lineWidth: 0.5)
|
||||
)
|
||||
}
|
||||
|
||||
// 4. AI Enhancement badge
|
||||
if config.isAIEnhancementEnabled {
|
||||
// Context Awareness badge (moved before AI Enhancement)
|
||||
if config.useScreenCapture {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "camera.viewfinder")
|
||||
.font(.system(size: 10))
|
||||
Text("Context Awareness")
|
||||
.font(.caption)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 4)
|
||||
.background(Capsule()
|
||||
.fill(Color(.controlBackgroundColor)))
|
||||
.overlay(
|
||||
Capsule()
|
||||
.stroke(Color(.separatorColor), lineWidth: 0.5)
|
||||
)
|
||||
}
|
||||
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "sparkles")
|
||||
.font(.system(size: 10))
|
||||
Text(selectedPrompt?.title ?? "AI")
|
||||
.font(.caption)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 4)
|
||||
.background(Capsule()
|
||||
.fill(Color.accentColor.opacity(0.1)))
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 16)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
.background(CardBackground(isSelected: isEditing))
|
||||
|
||||
Spacer()
|
||||
|
||||
// Right: Toggle Switch
|
||||
Toggle("", isOn: $config.isEnabled)
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.labelsHidden()
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.padding(.vertical, 12)
|
||||
.padding(.horizontal, 14)
|
||||
|
||||
Toggle("", isOn: $config.isEnabled)
|
||||
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||
.labelsHidden()
|
||||
.onChange(of: config.isEnabled) { oldValue, newValue in
|
||||
if newValue {
|
||||
powerModeManager.enableConfiguration(with: config.id)
|
||||
} else {
|
||||
powerModeManager.disableConfiguration(with: config.id)
|
||||
// Only add divider and settings row if we have settings
|
||||
if selectedModel != nil || selectedLanguage != nil || config.isAIEnhancementEnabled {
|
||||
Divider()
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
// Settings badges in specified order
|
||||
HStack(spacing: 8) {
|
||||
// 1. Voice Model badge
|
||||
if let model = selectedModel, model != "Default" {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "waveform")
|
||||
.font(.system(size: 10))
|
||||
Text(model)
|
||||
.font(.caption)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 4)
|
||||
.background(Capsule()
|
||||
.fill(Color(NSColor.controlBackgroundColor)))
|
||||
.overlay(
|
||||
Capsule()
|
||||
.stroke(Color(NSColor.separatorColor), lineWidth: 0.5)
|
||||
)
|
||||
}
|
||||
|
||||
// 2. Language badge
|
||||
if let language = selectedLanguage, language != "Default" {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "globe")
|
||||
.font(.system(size: 10))
|
||||
Text(language)
|
||||
.font(.caption)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 4)
|
||||
.background(Capsule()
|
||||
.fill(Color(NSColor.controlBackgroundColor)))
|
||||
.overlay(
|
||||
Capsule()
|
||||
.stroke(Color(NSColor.separatorColor), lineWidth: 0.5)
|
||||
)
|
||||
}
|
||||
|
||||
// 3. AI Model badge if specified (moved before AI Enhancement)
|
||||
if config.isAIEnhancementEnabled, let modelName = config.selectedAIModel, !modelName.isEmpty {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "cpu")
|
||||
.font(.system(size: 10))
|
||||
// Display a shortened version of the model name if it's too long (increased limit)
|
||||
Text(modelName.count > 20 ? String(modelName.prefix(18)) + "..." : modelName)
|
||||
.font(.caption)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 4)
|
||||
.background(Capsule()
|
||||
.fill(Color(NSColor.controlBackgroundColor)))
|
||||
.overlay(
|
||||
Capsule()
|
||||
.stroke(Color(NSColor.separatorColor), lineWidth: 0.5)
|
||||
)
|
||||
}
|
||||
|
||||
// 4. AI Enhancement badge
|
||||
if config.isAIEnhancementEnabled {
|
||||
// Context Awareness badge (moved before AI Enhancement)
|
||||
if config.useScreenCapture {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "camera.viewfinder")
|
||||
.font(.system(size: 10))
|
||||
Text("Context Awareness")
|
||||
.font(.caption)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 4)
|
||||
.background(Capsule()
|
||||
.fill(Color(NSColor.controlBackgroundColor)))
|
||||
.overlay(
|
||||
Capsule()
|
||||
.stroke(Color(NSColor.separatorColor), lineWidth: 0.5)
|
||||
)
|
||||
}
|
||||
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "sparkles")
|
||||
.font(.system(size: 10))
|
||||
Text(selectedPrompt?.title ?? "AI")
|
||||
.font(.caption)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 4)
|
||||
.background(Capsule()
|
||||
.fill(Color.accentColor.opacity(0.1)))
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
}
|
||||
.background(CardBackground(isSelected: isEditing))
|
||||
.opacity(config.isEnabled ? 1.0 : 0.5)
|
||||
.contextMenu {
|
||||
Button(action: {
|
||||
onEditConfig(config)
|
||||
}) {
|
||||
Label("Edit", systemImage: "pencil")
|
||||
}
|
||||
.opacity(config.isEnabled ? 1.0 : 0.5)
|
||||
Button(role: .destructive, action: {
|
||||
powerModeManager.removeConfiguration(with: config.id)
|
||||
}) {
|
||||
Label("Remove", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var isSelected: Bool {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user