Enhance recorder with PowerMode popover

This commit is contained in:
Beingpax 2025-05-24 20:35:26 +05:45
parent 91b43171d6
commit 9704a1ba79
5 changed files with 164 additions and 73 deletions

View File

@ -7,11 +7,11 @@
objects = {
/* Begin PBXBuildFile section */
E10340512DE1F666008BCBE5 /* whisper.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = E136D0102DA3EE57000E1E8A /* whisper.xcframework */; };
E10340522DE1F666008BCBE5 /* whisper.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E136D0102DA3EE57000E1E8A /* whisper.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E1A261122CC143AC00B233D1 /* KeyboardShortcuts in Frameworks */ = {isa = PBXBuildFile; productRef = E1A261112CC143AC00B233D1 /* KeyboardShortcuts */; };
E1ADD45A2CC5352A00303ECB /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = E1ADD4592CC5352A00303ECB /* LaunchAtLogin */; };
E1ADD45F2CC544F100303ECB /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = E1ADD45E2CC544F100303ECB /* Sparkle */; };
E1C7A8112DE06FC60034EDA0 /* whisper.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = E11CB51D2DB1F8AF00F9F3ED /* whisper.xcframework */; };
E1C7A8122DE06FC60034EDA0 /* whisper.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E11CB51D2DB1F8AF00F9F3ED /* whisper.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E1F5FA7A2DA6CBF900B1FD8A /* Zip in Frameworks */ = {isa = PBXBuildFile; productRef = E1F5FA792DA6CBF900B1FD8A /* Zip */; };
/* End PBXBuildFile section */
@ -39,7 +39,7 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
E1C7A8122DE06FC60034EDA0 /* whisper.xcframework in Embed Frameworks */,
E10340522DE1F666008BCBE5 /* whisper.xcframework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@ -80,7 +80,7 @@
E1ADD45A2CC5352A00303ECB /* LaunchAtLogin in Frameworks */,
E1ADD45F2CC544F100303ECB /* Sparkle in Frameworks */,
E1A261122CC143AC00B233D1 /* KeyboardShortcuts in Frameworks */,
E1C7A8112DE06FC60034EDA0 /* whisper.xcframework in Frameworks */,
E10340512DE1F666008BCBE5 /* whisper.xcframework in Frameworks */,
E1F5FA7A2DA6CBF900B1FD8A /* Zip in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@ -76,10 +76,12 @@ class PowerModeManager: ObservableObject {
@Published var configurations: [PowerModeConfig] = []
@Published var defaultConfig: PowerModeConfig
@Published var isPowerModeEnabled: Bool
@Published var activeConfiguration: PowerModeConfig?
private let configKey = "powerModeConfigurationsV2"
private let defaultConfigKey = "defaultPowerModeConfigV2"
private let powerModeEnabledKey = "isPowerModeEnabled"
private let activeConfigIdKey = "activeConfigurationId"
private init() {
// Load power mode enabled state or default to false if not set
@ -111,6 +113,20 @@ class PowerModeManager: ObservableObject {
saveDefaultConfig()
}
loadConfigurations()
// Set the active configuration, either from saved ID or default to the default config
if let activeConfigIdString = UserDefaults.standard.string(forKey: activeConfigIdKey),
let activeConfigId = UUID(uuidString: activeConfigIdString) {
if let savedConfig = configurations.first(where: { $0.id == activeConfigId }) {
activeConfiguration = savedConfig
} else if activeConfigId == defaultConfig.id {
activeConfiguration = defaultConfig
} else {
activeConfiguration = defaultConfig
}
} else {
activeConfiguration = defaultConfig
}
}
private func loadConfigurations() {
@ -237,4 +253,16 @@ class PowerModeManager: ObservableObject {
func savePowerModeEnabled() {
UserDefaults.standard.set(isPowerModeEnabled, forKey: powerModeEnabledKey)
}
// Set active configuration
func setActiveConfiguration(_ config: PowerModeConfig) {
activeConfiguration = config
UserDefaults.standard.set(config.id.uuidString, forKey: activeConfigIdKey)
self.objectWillChange.send()
}
// Get current active configuration
var currentActiveConfiguration: PowerModeConfig {
return activeConfiguration ?? defaultConfig
}
}

View File

@ -0,0 +1,91 @@
import SwiftUI
// Power Mode Popover for recorder views
struct PowerModePopover: View {
@ObservedObject var powerModeManager = PowerModeManager.shared
@State private var selectedConfig: PowerModeConfig?
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text("Select Power Mode")
.font(.headline)
.foregroundColor(.white.opacity(0.9))
.padding(.horizontal)
.padding(.top, 8)
Divider()
.background(Color.white.opacity(0.1))
ScrollView {
VStack(alignment: .leading, spacing: 4) {
// Default Configuration
PowerModeRow(
config: powerModeManager.defaultConfig,
isSelected: selectedConfig?.id == powerModeManager.defaultConfig.id,
action: {
powerModeManager.setActiveConfiguration(powerModeManager.defaultConfig)
selectedConfig = powerModeManager.defaultConfig
}
)
// Custom Configurations
ForEach(powerModeManager.configurations) { config in
PowerModeRow(
config: config,
isSelected: selectedConfig?.id == config.id,
action: {
powerModeManager.setActiveConfiguration(config)
selectedConfig = config
}
)
}
}
.padding(.horizontal)
}
}
.frame(width: 180)
.frame(maxHeight: 300)
.padding(.vertical, 8)
.background(Color.black)
.environment(\.colorScheme, .dark)
.onAppear {
// Set the initially selected configuration
selectedConfig = powerModeManager.activeConfiguration
}
}
}
// Row view for each power mode in the popover
struct PowerModeRow: View {
let config: PowerModeConfig
let isSelected: Bool
let action: () -> Void
var body: some View {
Button(action: action) {
HStack(spacing: 8) {
// Always use the emoji from the configuration
Text(config.emoji)
.font(.system(size: 14))
Text(config.name)
.foregroundColor(.white.opacity(0.9))
.font(.system(size: 13))
.lineLimit(1)
if isSelected {
Spacer()
Image(systemName: "checkmark")
.foregroundColor(.green)
.font(.system(size: 10))
}
}
.contentShape(Rectangle())
.padding(.vertical, 4)
.padding(.horizontal, 8)
}
.buttonStyle(.plain)
.background(isSelected ? Color.white.opacity(0.1) : Color.clear)
.cornerRadius(4)
}
}

View File

@ -4,7 +4,8 @@ struct MiniRecorderView: View {
@ObservedObject var whisperState: WhisperState
@ObservedObject var recorder: Recorder
@EnvironmentObject var windowManager: MiniWindowManager
@State private var showPromptPopover = false
@State private var showPowerModePopover = false
@ObservedObject private var powerModeManager = PowerModeManager.shared
var body: some View {
Group {
@ -49,36 +50,7 @@ struct MiniRecorderView: View {
.frame(width: 18)
.padding(.leading, -4)
// AI Enhancement Toggle
if let enhancementService = whisperState.getEnhancementService() {
NotchToggleButton(
isEnabled: enhancementService.isEnhancementEnabled,
icon: "sparkles",
color: .blue
) {
enhancementService.isEnhancementEnabled.toggle()
}
.frame(width: 18)
.disabled(!enhancementService.isConfigured)
}
// Custom Prompt Toggle and Selector
if let enhancementService = whisperState.getEnhancementService() {
NotchToggleButton(
isEnabled: enhancementService.isEnhancementEnabled,
icon: enhancementService.activePrompt?.icon.rawValue ?? "text.badge.checkmark",
color: .green
) {
showPromptPopover.toggle()
}
.frame(width: 18)
.disabled(!enhancementService.isEnhancementEnabled)
.popover(isPresented: $showPromptPopover, arrowEdge: .bottom) {
NotchPromptPopover(enhancementService: enhancementService)
}
}
// Visualizer
// Visualizer - moved to middle position
Group {
if whisperState.isProcessing {
NotchStaticVisualizer(color: .white)
@ -91,7 +63,24 @@ struct MiniRecorderView: View {
}
}
.frame(width: 18)
// Empty space for future use
Spacer()
.frame(width: 18)
// Power Mode Button - moved to last position
NotchToggleButton(
isEnabled: powerModeManager.isPowerModeEnabled,
icon: powerModeManager.currentActiveConfiguration.emoji,
color: .orange
) {
showPowerModePopover.toggle()
}
.frame(width: 18)
.padding(.trailing, -4)
.popover(isPresented: $showPowerModePopover, arrowEdge: .bottom) {
PowerModePopover()
}
}
.padding(.horizontal, 8)
.padding(.vertical, 8)

View File

@ -5,7 +5,8 @@ struct NotchRecorderView: View {
@ObservedObject var recorder: Recorder
@EnvironmentObject var windowManager: NotchWindowManager
@State private var isHovering = false
@State private var showPromptPopover = false
@State private var showPowerModePopover = false
@ObservedObject private var powerModeManager = PowerModeManager.shared
private var menuBarHeight: CGFloat {
if let screen = NSScreen.main {
@ -46,18 +47,9 @@ struct NotchRecorderView: View {
}
.frame(width: 22)
// AI Enhancement Toggle
if let enhancementService = whisperState.getEnhancementService() {
NotchToggleButton(
isEnabled: enhancementService.isEnhancementEnabled,
icon: "sparkles",
color: .blue
) {
enhancementService.isEnhancementEnabled.toggle()
}
// Empty space for future use
Spacer()
.frame(width: 22)
.disabled(!enhancementService.isConfigured)
}
}
.frame(width: 44) // Fixed width for controls
.padding(.leading, 16)
@ -70,23 +62,7 @@ struct NotchRecorderView: View {
// Right side group with fixed width
HStack(spacing: 8) {
// Custom Prompt Toggle and Selector
if let enhancementService = whisperState.getEnhancementService() {
NotchToggleButton(
isEnabled: enhancementService.isEnhancementEnabled,
icon: enhancementService.activePrompt?.icon.rawValue ?? "text.badge.checkmark",
color: .green
) {
showPromptPopover.toggle()
}
.frame(width: 22)
.disabled(!enhancementService.isEnhancementEnabled)
.popover(isPresented: $showPromptPopover, arrowEdge: .bottom) {
NotchPromptPopover(enhancementService: enhancementService)
}
}
// Visualizer
// Visualizer - moved to first position
Group {
if whisperState.isProcessing {
NotchStaticVisualizer(color: .white)
@ -99,6 +75,19 @@ struct NotchRecorderView: View {
}
}
.frame(width: 22)
// Power Mode Button - moved to second position
NotchToggleButton(
isEnabled: powerModeManager.isPowerModeEnabled,
icon: powerModeManager.currentActiveConfiguration.emoji,
color: .orange
) {
showPowerModePopover.toggle()
}
.frame(width: 22)
.popover(isPresented: $showPowerModePopover, arrowEdge: .bottom) {
PowerModePopover()
}
}
.frame(width: 44) // Fixed width for controls
.padding(.trailing, 16)
@ -195,15 +184,9 @@ struct NotchToggleButton: View {
var body: some View {
Button(action: action) {
ZStack {
Circle()
.fill(isEnabled ? color.opacity(0.2) : Color(red: 0.4, green: 0.4, blue: 0.45).opacity(0.2))
.frame(width: 20, height: 20)
Image(systemName: icon)
.font(.system(size: 10, weight: .medium))
.foregroundColor(isEnabled ? color : .white.opacity(0.6))
}
Text(icon)
.font(.system(size: 12))
.foregroundColor(isEnabled ? .white : .white.opacity(0.6))
}
.buttonStyle(PlainButtonStyle())
}