Fix issues with button popovers

This commit is contained in:
Beingpax 2025-09-24 11:08:33 +05:45
parent 154748c1aa
commit 8abd8247af
6 changed files with 94 additions and 45 deletions

View File

@ -92,7 +92,7 @@ import Foundation
name: "parakeet-tdt-0.6b",
displayName: "Parakeet V3",
description: "NVIDIA's ASR model V3 for lightning-fast transcription with multi-lingual(English + European) support.",
size: "500 MB",
size: "630 MB",
speed: 0.99,
accuracy: 0.94,
ramUsage: 0.8,

View File

@ -77,12 +77,12 @@ struct PowerModeRow: View {
HStack(spacing: 8) {
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")
@ -90,9 +90,10 @@ struct PowerModeRow: View {
.font(.system(size: 10))
}
}
.contentShape(Rectangle())
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.vertical, 4)
.padding(.horizontal, 8)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.background(isSelected ? Color.white.opacity(0.1) : Color.clear)

View File

@ -73,12 +73,12 @@ struct EnhancementPromptRow: View {
Image(systemName: prompt.icon.rawValue)
.font(.system(size: 14))
.foregroundColor(isDisabled ? .white.opacity(0.4) : .white.opacity(0.7))
Text(prompt.title)
.foregroundColor(isDisabled ? .white.opacity(0.4) : .white.opacity(0.9))
.font(.system(size: 13))
.lineLimit(1)
if isSelected {
Spacer()
Image(systemName: "checkmark")
@ -86,9 +86,10 @@ struct EnhancementPromptRow: View {
.font(.system(size: 10))
}
}
.contentShape(Rectangle())
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.vertical, 4)
.padding(.horizontal, 8)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.background(isSelected ? Color.white.opacity(0.1) : Color.clear)

View File

@ -6,8 +6,7 @@ struct MiniRecorderView: View {
@EnvironmentObject var windowManager: MiniWindowManager
@EnvironmentObject private var enhancementService: AIEnhancementService
@State private var showPowerModePopover = false
@State private var showEnhancementPromptPopover = false
@State private var activePopover: ActivePopoverState = .none
private var backgroundView: some View {
ZStack {
@ -36,19 +35,19 @@ struct MiniRecorderView: View {
private var contentLayout: some View {
HStack(spacing: 0) {
// Left button zone - always visible
RecorderPromptButton(showPopover: $showEnhancementPromptPopover)
RecorderPromptButton(activePopover: $activePopover)
.padding(.leading, 7)
Spacer()
// Fixed visualizer zone
statusView
.frame(maxWidth: .infinity)
Spacer()
// Right button zone - always visible
RecorderPowerModeButton(showPopover: $showPowerModePopover)
RecorderPowerModeButton(activePopover: $activePopover)
.padding(.trailing, 7)
}
.padding(.vertical, 9)

View File

@ -5,8 +5,7 @@ struct NotchRecorderView: View {
@ObservedObject var recorder: Recorder
@EnvironmentObject var windowManager: NotchWindowManager
@State private var isHovering = false
@State private var showPowerModePopover = false
@State private var showEnhancementPromptPopover = false
@State private var activePopover: ActivePopoverState = .none
@ObservedObject private var powerModeManager = PowerModeManager.shared
@EnvironmentObject private var enhancementService: AIEnhancementService
@ -32,19 +31,19 @@ struct NotchRecorderView: View {
}
private var leftSection: some View {
HStack(spacing: 8) {
HStack(spacing: 12) {
RecorderPromptButton(
showPopover: $showEnhancementPromptPopover,
activePopover: $activePopover,
buttonSize: 22,
padding: EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
)
RecorderPowerModeButton(
showPopover: $showPowerModePopover,
activePopover: $activePopover,
buttonSize: 22,
padding: EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)
)
Spacer()
}
.frame(width: 84)

View File

@ -1,5 +1,12 @@
import SwiftUI
// MARK: - Shared Popover State
enum ActivePopoverState {
case none
case enhancement
case power
}
// MARK: - Hover Interaction Manager
class HoverInteraction: ObservableObject {
@Published var isHovered: Bool = false
@ -144,13 +151,15 @@ struct ProgressAnimation: View {
// MARK: - Prompt Button Component
struct RecorderPromptButton: View {
@EnvironmentObject private var enhancementService: AIEnhancementService
@Binding var showPopover: Bool
@Binding var activePopover: ActivePopoverState
let buttonSize: CGFloat
let padding: EdgeInsets
@StateObject private var hoverInteraction = HoverInteraction()
@State private var isHoveringEnhancement: Bool = false
@State private var isHoveringEnhancementPopover: Bool = false
@State private var enhancementDismissWorkItem: DispatchWorkItem?
init(showPopover: Binding<Bool>, buttonSize: CGFloat = 28, padding: EdgeInsets = EdgeInsets(top: 0, leading: 7, bottom: 0, trailing: 0)) {
self._showPopover = showPopover
init(activePopover: Binding<ActivePopoverState>, buttonSize: CGFloat = 28, padding: EdgeInsets = EdgeInsets(top: 0, leading: 7, bottom: 0, trailing: 0)) {
self._activePopover = activePopover
self.buttonSize = buttonSize
self.padding = padding
}
@ -163,23 +172,42 @@ struct RecorderPromptButton: View {
disabled: false
) {
if enhancementService.isEnhancementEnabled {
showPopover.toggle()
activePopover = activePopover == .enhancement ? .none : .enhancement
} else {
enhancementService.isEnhancementEnabled = true
}
}
.frame(width: buttonSize)
.padding(padding)
.onHover { hoverInteraction.setHover(on: $0) }
.popover(isPresented: $showPopover, arrowEdge: .bottom) {
.onHover {
isHoveringEnhancement = $0
syncEnhancementPopoverVisibility()
}
.popover(isPresented: .constant(activePopover == .enhancement), arrowEdge: .bottom) {
EnhancementPromptPopover()
.environmentObject(enhancementService)
.onHover { hoverInteraction.setHover(on: $0) }
.onHover {
isHoveringEnhancementPopover = $0
syncEnhancementPopoverVisibility()
}
}
.onChange(of: hoverInteraction.isHovered) { isHovered in
if isHovered != showPopover {
showPopover = isHovered
}
private func syncEnhancementPopoverVisibility() {
let shouldShow = isHoveringEnhancement || isHoveringEnhancementPopover
if shouldShow {
enhancementDismissWorkItem?.cancel()
enhancementDismissWorkItem = nil
activePopover = .enhancement
} else {
enhancementDismissWorkItem?.cancel()
let work = DispatchWorkItem { [activePopoverBinding = $activePopover] in
if activePopoverBinding.wrappedValue == .enhancement {
activePopoverBinding.wrappedValue = .none
}
}
enhancementDismissWorkItem = work
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25, execute: work)
}
}
}
@ -187,13 +215,15 @@ struct RecorderPromptButton: View {
// MARK: - Power Mode Button Component
struct RecorderPowerModeButton: View {
@ObservedObject private var powerModeManager = PowerModeManager.shared
@Binding var showPopover: Bool
@Binding var activePopover: ActivePopoverState
let buttonSize: CGFloat
let padding: EdgeInsets
@StateObject private var hoverInteraction = HoverInteraction()
@State private var isHoveringPower: Bool = false
@State private var isHoveringPowerPopover: Bool = false
@State private var powerDismissWorkItem: DispatchWorkItem?
init(showPopover: Binding<Bool>, buttonSize: CGFloat = 28, padding: EdgeInsets = EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 7)) {
self._showPopover = showPopover
init(activePopover: Binding<ActivePopoverState>, buttonSize: CGFloat = 28, padding: EdgeInsets = EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 7)) {
self._activePopover = activePopover
self.buttonSize = buttonSize
self.padding = padding
}
@ -205,19 +235,38 @@ struct RecorderPowerModeButton: View {
color: .orange,
disabled: powerModeManager.enabledConfigurations.isEmpty
) {
showPopover.toggle()
activePopover = activePopover == .power ? .none : .power
}
.frame(width: buttonSize)
.padding(padding)
.onHover { hoverInteraction.setHover(on: $0) }
.popover(isPresented: $showPopover, arrowEdge: .bottom) {
PowerModePopover()
.onHover { hoverInteraction.setHover(on: $0) }
.onHover {
isHoveringPower = $0
syncPowerPopoverVisibility()
}
.onChange(of: hoverInteraction.isHovered) { isHovered in
if isHovered != showPopover {
showPopover = isHovered
.popover(isPresented: .constant(activePopover == .power), arrowEdge: .bottom) {
PowerModePopover()
.onHover {
isHoveringPowerPopover = $0
syncPowerPopoverVisibility()
}
}
}
private func syncPowerPopoverVisibility() {
let shouldShow = isHoveringPower || isHoveringPowerPopover
if shouldShow {
powerDismissWorkItem?.cancel()
powerDismissWorkItem = nil
activePopover = .power
} else {
powerDismissWorkItem?.cancel()
let work = DispatchWorkItem { [activePopoverBinding = $activePopover] in
if activePopoverBinding.wrappedValue == .power {
activePopoverBinding.wrappedValue = .none
}
}
powerDismissWorkItem = work
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25, execute: work)
}
}
}