142 lines
4.6 KiB
Swift
142 lines
4.6 KiB
Swift
import SwiftUI
|
|
|
|
struct AppNotificationView: View {
|
|
let title: String
|
|
let type: NotificationType
|
|
let duration: TimeInterval
|
|
let onClose: () -> Void
|
|
let onTap: (() -> Void)?
|
|
|
|
@State private var progress: Double = 1.0
|
|
@State private var timer: Timer?
|
|
|
|
enum NotificationType {
|
|
case error
|
|
case warning
|
|
case info
|
|
case success
|
|
|
|
var iconName: String {
|
|
switch self {
|
|
case .error: return "xmark.octagon.fill"
|
|
case .warning: return "exclamationmark.triangle.fill"
|
|
case .info: return "info.circle.fill"
|
|
case .success: return "checkmark.circle.fill"
|
|
}
|
|
}
|
|
|
|
var iconColor: Color {
|
|
switch self {
|
|
case .error: return .red
|
|
case .warning: return .yellow
|
|
case .info: return .blue
|
|
case .success: return .green
|
|
}
|
|
}
|
|
}
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
HStack(alignment: .center, spacing: 12) {
|
|
// Type icon
|
|
Image(systemName: type.iconName)
|
|
.font(.system(size: 16, weight: .medium))
|
|
.foregroundColor(type.iconColor)
|
|
.frame(width: 20, height: 20)
|
|
|
|
// Single message text
|
|
Text(title)
|
|
.font(.system(size: 12))
|
|
.fontWeight(.medium)
|
|
.foregroundColor(.white)
|
|
.lineLimit(2)
|
|
.multilineTextAlignment(.leading)
|
|
|
|
Spacer()
|
|
|
|
Button(action: onClose) {
|
|
Image(systemName: "xmark")
|
|
.font(.system(size: 10, weight: .medium))
|
|
.foregroundColor(.white.opacity(0.6))
|
|
}
|
|
.buttonStyle(PlainButtonStyle())
|
|
.frame(width: 16, height: 16)
|
|
}
|
|
.padding(.horizontal, 16)
|
|
.padding(.vertical, 12)
|
|
}
|
|
.frame(minWidth: 280, maxWidth: 380, minHeight: 44)
|
|
.background(
|
|
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
|
.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(type.iconColor.opacity(0.8))
|
|
.frame(width: geometry.size.width * max(0, progress), height: 2)
|
|
.animation(.linear(duration: 0.1), value: progress)
|
|
}
|
|
.frame(height: 2)
|
|
}
|
|
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
|
|
)
|
|
.onAppear {
|
|
startProgressTimer()
|
|
}
|
|
.onDisappear {
|
|
timer?.invalidate()
|
|
}
|
|
.onTapGesture {
|
|
if let onTap = onTap {
|
|
onTap()
|
|
onClose()
|
|
}
|
|
}
|
|
}
|
|
|
|
private func startProgressTimer() {
|
|
let updateInterval: TimeInterval = 0.1
|
|
let totalSteps = duration / updateInterval
|
|
let stepDecrement = 1.0 / totalSteps
|
|
|
|
timer = Timer.scheduledTimer(withTimeInterval: updateInterval, repeats: true) { _ in
|
|
if progress > 0 {
|
|
progress = max(0, progress - stepDecrement)
|
|
} else {
|
|
timer?.invalidate()
|
|
timer = nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|