ux: float close button outside bubble and reduce hover flicker

This commit is contained in:
Peter Steinberger
2025-12-08 21:59:05 +01:00
parent 11400e43dc
commit 12e048a7fb

View File

@@ -89,7 +89,7 @@ final class VoiceWakeOverlayController: ObservableObject {
self.updateWindowFrame(animate: true) self.updateWindowFrame(animate: true)
} }
func sendNow() { func sendNow(sendChime: VoiceWakeChime = .none) {
self.autoSendTask?.cancel() self.autoSendTask?.cancel()
self.model.isEditing = false self.model.isEditing = false
guard let forwardConfig, forwardConfig.enabled else { guard let forwardConfig, forwardConfig.enabled else {
@@ -102,6 +102,10 @@ final class VoiceWakeOverlayController: ObservableObject {
return return
} }
if sendChime != .none {
VoiceWakeChimePlayer.play(sendChime)
}
self.model.isSending = true self.model.isSending = true
let payload = VoiceWakeForwarder.prefixedTranscript(text) let payload = VoiceWakeForwarder.prefixedTranscript(text)
Task.detached { Task.detached {
@@ -256,13 +260,17 @@ final class VoiceWakeOverlayController: ObservableObject {
private func scheduleAutoSend(after delay: TimeInterval, sendChime: VoiceWakeChime) { private func scheduleAutoSend(after delay: TimeInterval, sendChime: VoiceWakeChime) {
guard let forwardConfig, forwardConfig.enabled else { return } guard let forwardConfig, forwardConfig.enabled else { return }
self.autoSendTask = Task { [weak self] in self.autoSendTask?.cancel()
let nanos = UInt64(delay * 1_000_000_000) self.autoSendTask = Task { [weak self, sendChime] in
try? await Task.sleep(nanoseconds: nanos) do {
if sendChime != .none { let nanos = UInt64(delay * 1_000_000_000)
VoiceWakeChimePlayer.play(sendChime) try await Task.sleep(nanoseconds: nanos)
try Task.checkCancellation()
guard let self else { return }
await self.sendNow(sendChime: sendChime)
} catch is CancellationError {
return
} }
self?.sendNow()
} }
} }
@@ -285,9 +293,9 @@ private struct CloseHoverButton: View {
.font(.system(size: 12, weight: .bold)) .font(.system(size: 12, weight: .bold))
.foregroundColor(Color.white.opacity(0.85)) .foregroundColor(Color.white.opacity(0.85))
.frame(width: 22, height: 22) .frame(width: 22, height: 22)
.background(Color.black.opacity(0.35)) .background(Color.black.opacity(0.42))
.clipShape(Circle()) .clipShape(Circle())
.shadow(color: Color.black.opacity(0.35), radius: 6, y: 2) .shadow(color: Color.black.opacity(0.4), radius: 8, y: 2)
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.focusable(false) .focusable(false)
@@ -365,16 +373,17 @@ private struct VoiceWakeOverlayView: View {
.padding(.vertical, 8) .padding(.vertical, 8)
.padding(.horizontal, 10) .padding(.horizontal, 10)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.background(.regularMaterial) .background(
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous)) .regularMaterial,
in: RoundedRectangle(cornerRadius: 12, style: .continuous))
.onHover { self.isHovering = $0 } .onHover { self.isHovering = $0 }
if self.controller.model.isEditing || self.isHovering { if self.controller.model.isEditing || self.isHovering {
CloseHoverButton(onClose: { CloseHoverButton(onClose: {
self.controller.cancelEditingAndDismiss() self.controller.cancelEditingAndDismiss()
}) })
.offset(x: -10, y: -10) .offset(x: -8, y: -8)
.transition(AnyTransition.scale.combined(with: .opacity)) .transition(.opacity)
} }
} }
.onAppear { self.textFocused = false } .onAppear { self.textFocused = false }