vOOice/VoiceInk/Notifications/AnnouncementManager.swift

89 lines
2.7 KiB
Swift

import SwiftUI
import AppKit
final class AnnouncementManager {
static let shared = AnnouncementManager()
private var panel: NSPanel?
private init() {}
@MainActor
func showAnnouncement(title: String, description: String?, learnMoreURL: URL?, onDismiss: @escaping () -> Void) {
dismiss()
let view = AnnouncementView(
title: title,
description: description ?? "",
onClose: { [weak self] in
onDismiss()
self?.dismiss()
},
onLearnMore: { [weak self] in
if let url = learnMoreURL {
NSWorkspace.shared.open(url)
}
onDismiss()
self?.dismiss()
}
)
let hosting = NSHostingController(rootView: view)
hosting.view.layoutSubtreeIfNeeded()
let size = hosting.view.fittingSize
let panel = NSPanel(
contentRect: NSRect(origin: .zero, size: size),
styleMask: [.borderless, .nonactivatingPanel],
backing: .buffered,
defer: false
)
panel.contentView = hosting.view
panel.isFloatingPanel = true
panel.level = .statusBar
panel.backgroundColor = .clear
panel.hasShadow = false
panel.isMovableByWindowBackground = false
panel.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
position(panel)
panel.alphaValue = 0
panel.makeKeyAndOrderFront(nil as Any?)
self.panel = panel
NSAnimationContext.runAnimationGroup { context in
context.duration = 0.25
context.timingFunction = CAMediaTimingFunction(name: .easeOut)
panel.animator().alphaValue = 1
}
}
@MainActor
func dismiss() {
guard let panel = panel else { return }
self.panel = nil
NSAnimationContext.runAnimationGroup({ context in
context.duration = 0.2
context.timingFunction = CAMediaTimingFunction(name: .easeIn)
panel.animator().alphaValue = 0
}, completionHandler: {
panel.close()
})
}
@MainActor
private func position(_ panel: NSPanel) {
let screen = NSApp.keyWindow?.screen ?? NSScreen.main ?? NSScreen.screens[0]
let visibleFrame = screen.visibleFrame
// Match MiniRecorder: bottom padding 24, centered horizontally
let bottomPadding: CGFloat = 24
let x = visibleFrame.midX - (panel.frame.width / 2)
// Ensure bottom padding, but if the panel is taller, anchor its bottom at padding
let y = max(visibleFrame.minY + bottomPadding, visibleFrame.minY + bottomPadding)
panel.setFrameOrigin(NSPoint(x: x, y: y))
}
}