Improved notification system

This commit is contained in:
Beingpax 2025-06-22 12:12:16 +05:45
parent e0ff033581
commit 8537a59b2a
4 changed files with 66 additions and 56 deletions

View File

@ -2,7 +2,6 @@ import SwiftUI
struct AppNotificationView: View {
let title: String
let message: String
let type: NotificationType
let duration: TimeInterval
let onClose: () -> Void
@ -39,57 +38,70 @@ struct AppNotificationView: View {
var body: some View {
ZStack {
HStack(alignment: .center, spacing: 12) {
if let appIcon = NSApp.applicationIconImage {
Image(nsImage: appIcon)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 50, height: 50)
.cornerRadius(10)
}
// Type icon
Image(systemName: type.iconName)
.font(.system(size: 16, weight: .medium))
.foregroundColor(type.iconColor)
.frame(width: 20, height: 20)
VStack(alignment: .leading, spacing: 3) {
Text(title)
.fontWeight(.bold)
.font(.system(size: 13))
.lineLimit(1)
Text(message)
.font(.system(size: 11))
.fontWeight(.semibold)
.opacity(0.9)
.lineLimit(2)
.multilineTextAlignment(.leading)
}
// Single message text
Text(title)
.font(.system(size: 12))
.fontWeight(.medium)
.foregroundColor(.white)
.lineLimit(2)
.multilineTextAlignment(.leading)
Spacer()
}
.padding(12)
.frame(height: 60)
VStack {
HStack {
Spacer()
Button(action: onClose) {
Image(systemName: "xmark")
.font(.system(size: 11, weight: .medium))
.foregroundColor(.secondary)
}
.buttonStyle(PlainButtonStyle())
.frame(width: 16, height: 16)
Button(action: onClose) {
Image(systemName: "xmark")
.font(.system(size: 10, weight: .medium))
.foregroundColor(.white.opacity(0.6))
}
Spacer()
.buttonStyle(PlainButtonStyle())
.frame(width: 16, height: 16)
}
.padding(8)
.padding(.horizontal, 16)
.padding(.vertical, 12)
}
.frame(minWidth: 320, maxWidth: 420)
.frame(minWidth: 280, maxWidth: 380, minHeight: 44)
.background(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.fill(Color(nsColor: .controlBackgroundColor).opacity(0.95))
.fill(.clear)
.background(
ZStack {
// Base dark background
Color.black.opacity(0.9)
// Subtle gradient overlay
LinearGradient(
colors: [
Color.black.opacity(0.95),
Color(red: 0.15, green: 0.15, blue: 0.15).opacity(0.9)
],
startPoint: .top,
endPoint: .bottom
)
// Very subtle visual effect for depth
VisualEffectView(material: .hudWindow, blendingMode: .withinWindow)
.opacity(0.05)
}
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
)
)
.overlay(
// Subtle inner border
RoundedRectangle(cornerRadius: 12, style: .continuous)
.strokeBorder(Color.white.opacity(0.1), lineWidth: 0.5)
)
.overlay(
VStack {
Spacer()
GeometryReader { geometry in
Rectangle()
.fill(Color.accentColor.opacity(0.6))
.fill(type.iconColor.opacity(0.8))
.frame(width: geometry.size.width * max(0, progress), height: 2)
.animation(.linear(duration: 0.1), value: progress)
}
@ -126,3 +138,4 @@ struct AppNotificationView: View {
}
}
}

View File

@ -12,9 +12,8 @@ class NotificationManager {
@MainActor
func showNotification(
title: String,
message: String,
type: AppNotificationView.NotificationType,
duration: TimeInterval = 8.0,
duration: TimeInterval = 5.0,
onTap: (() -> Void)? = nil
) {
dismissTimer?.invalidate()
@ -31,8 +30,7 @@ class NotificationManager {
}
let notificationView = AppNotificationView(
title: title,
message: message,
title: title,
type: type,
duration: duration,
onClose: { [weak self] in
@ -84,12 +82,18 @@ class NotificationManager {
private func positionWindow(_ window: NSWindow) {
let activeScreen = NSApp.keyWindow?.screen ?? NSScreen.main ?? NSScreen.screens[0]
let screenRect = activeScreen.visibleFrame
let windowRect = window.frame
let notificationRect = window.frame
let x = screenRect.maxX - windowRect.width - 20
let y = screenRect.maxY - windowRect.height - 20
// Position notification centered horizontally on screen
let notificationX = screenRect.midX - (notificationRect.width / 2)
window.setFrameOrigin(NSPoint(x: x, y: y))
// Position notification near bottom of screen with appropriate spacing
let bottomPadding: CGFloat = 24
let componentHeight: CGFloat = 34
let notificationSpacing: CGFloat = 16
let notificationY = screenRect.minY + bottomPadding + componentHeight + notificationSpacing
window.setFrameOrigin(NSPoint(x: notificationX, y: notificationY))
}
@MainActor

View File

@ -71,8 +71,7 @@ class Recorder: ObservableObject {
if let deviceName = deviceManager.availableDevices.first(where: { $0.id == currentDeviceID })?.name {
await MainActor.run {
NotificationManager.shared.showNotification(
title: "Audio Input Source",
message: "Using: \(deviceName)",
title: "Using: \(deviceName)",
type: .info
)
}
@ -126,7 +125,7 @@ class Recorder: ObservableObject {
}
audioLevelCheckTask = Task {
try? await Task.sleep(nanoseconds: 5_000_000_000)
try? await Task.sleep(nanoseconds: 2_000_000_000)
if Task.isCancelled { return }
@ -134,7 +133,6 @@ class Recorder: ObservableObject {
await MainActor.run {
NotificationManager.shared.showNotification(
title: "No Audio Detected",
message: "Is your microphone muted? Please check your audio input settings.",
type: .warning
)
}

View File

@ -154,7 +154,6 @@ class WhisperState: NSObject, ObservableObject, AVAudioRecorderDelegate {
await MainActor.run {
NotificationManager.shared.showNotification(
title: "No AI Model Selected",
message: "Please select a default AI model before recording.",
type: .error
)
}
@ -409,8 +408,7 @@ class WhisperState: NSObject, ObservableObject, AVAudioRecorderDelegate {
await MainActor.run {
if permanentURL != nil {
NotificationManager.shared.showNotification(
title: "Transcription Failed",
message: "🔄 Tap to retry transcription",
title: "Transcription Failed. Tap to retry.",
type: .error,
onTap: { [weak self] in
Task {
@ -421,7 +419,6 @@ class WhisperState: NSObject, ObservableObject, AVAudioRecorderDelegate {
} else {
NotificationManager.shared.showNotification(
title: "Recording Failed",
message: "Could not save audio file. Please try recording again.",
type: .error
)
}
@ -463,7 +460,6 @@ class WhisperState: NSObject, ObservableObject, AVAudioRecorderDelegate {
await MainActor.run {
NotificationManager.shared.showNotification(
title: "Transcription Successful",
message: "✅ Retry completed successfully",
type: .success
)
@ -479,7 +475,6 @@ class WhisperState: NSObject, ObservableObject, AVAudioRecorderDelegate {
await MainActor.run {
NotificationManager.shared.showNotification(
title: "Retry Failed",
message: "Transcription failed again. Check your model and settings.",
type: .error
)
}