89 lines
2.7 KiB
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))
|
|
}
|
|
}
|
|
|
|
|