107 lines
3.2 KiB
Swift
107 lines
3.2 KiB
Swift
import SwiftUI
|
|
import AppKit
|
|
|
|
class NotificationManager {
|
|
static let shared = NotificationManager()
|
|
|
|
private var notificationWindow: NSPanel?
|
|
private var dismissTimer: Timer?
|
|
|
|
private init() {}
|
|
|
|
@MainActor
|
|
func showNotification(
|
|
title: String,
|
|
message: String,
|
|
type: AppNotificationView.NotificationType,
|
|
duration: TimeInterval = 8.0
|
|
) {
|
|
// If a notification is already showing, dismiss it before showing the new one.
|
|
if notificationWindow != nil {
|
|
dismissNotification()
|
|
}
|
|
|
|
let notificationView = AppNotificationView(
|
|
title: title,
|
|
message: message,
|
|
type: type,
|
|
duration: duration,
|
|
onClose: { [weak self] in
|
|
Task { @MainActor in
|
|
self?.dismissNotification()
|
|
}
|
|
}
|
|
)
|
|
let hostingController = NSHostingController(rootView: notificationView)
|
|
let size = hostingController.view.fittingSize
|
|
|
|
let panel = NSPanel(
|
|
contentRect: NSRect(origin: .zero, size: size),
|
|
styleMask: [.borderless, .nonactivatingPanel],
|
|
backing: .buffered,
|
|
defer: false
|
|
)
|
|
|
|
panel.contentView = hostingController.view
|
|
panel.isFloatingPanel = true
|
|
panel.level = .mainMenu
|
|
panel.backgroundColor = .clear
|
|
panel.hasShadow = false
|
|
panel.isMovableByWindowBackground = false
|
|
|
|
// Position at final location and start with fade animation
|
|
positionWindow(panel)
|
|
panel.alphaValue = 0
|
|
panel.makeKeyAndOrderFront(nil)
|
|
|
|
self.notificationWindow = panel
|
|
|
|
// Simple fade-in animation
|
|
NSAnimationContext.runAnimationGroup({ context in
|
|
context.duration = 0.3
|
|
context.timingFunction = CAMediaTimingFunction(name: .easeOut)
|
|
panel.animator().alphaValue = 1
|
|
})
|
|
|
|
dismissTimer?.invalidate()
|
|
dismissTimer = Timer.scheduledTimer(
|
|
withTimeInterval: duration,
|
|
repeats: false
|
|
) { [weak self] _ in
|
|
self?.dismissNotification()
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
private func positionWindow(_ window: NSWindow) {
|
|
guard let screen = NSScreen.main else { return }
|
|
let screenRect = screen.visibleFrame
|
|
let windowRect = window.frame
|
|
|
|
let x = screenRect.maxX - windowRect.width - 20 // 20px padding from the right
|
|
let y = screenRect.maxY - windowRect.height - 20 // 20px padding from the top
|
|
|
|
window.setFrameOrigin(NSPoint(x: x, y: y))
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@MainActor
|
|
func dismissNotification() {
|
|
guard let window = notificationWindow else { return }
|
|
|
|
dismissTimer?.invalidate()
|
|
dismissTimer = nil
|
|
|
|
NSAnimationContext.runAnimationGroup({ context in
|
|
context.duration = 0.2
|
|
context.timingFunction = CAMediaTimingFunction(name: .easeIn)
|
|
window.animator().alphaValue = 0
|
|
}, completionHandler: {
|
|
window.close()
|
|
self.notificationWindow = nil
|
|
})
|
|
}
|
|
} |