- P0: Add default param to protocol list(params:) for compile fix - P1: Fix onDelete async closure, implement deletion logic - P2: Remove redundant objectWillChange.send() (Published handles it) - P2: Make RelativeDateTimeFormatter static singleton (per-row perf) - P3: Replace deprecated NavigationView with NavigationStack
106 lines
3.3 KiB
Swift
106 lines
3.3 KiB
Swift
import SwiftUI
|
|
|
|
struct NotificationsView: View {
|
|
@StateObject private var viewModel: NotificationsViewModel
|
|
@State private var showingRefreshIndicator = false
|
|
|
|
init(viewModel: NotificationsViewModel = NotificationsViewModel()) {
|
|
self._viewModel = StateObject(wrappedValue: viewModel)
|
|
}
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
Group {
|
|
if viewModel.notifications.isEmpty && !viewModel.isLoading {
|
|
emptyStateView
|
|
} else {
|
|
notificationListView
|
|
}
|
|
}
|
|
.navigationTitle("Notifications")
|
|
.toolbar {
|
|
if !viewModel.notifications.isEmpty {
|
|
ToolbarItem(placement: .navigationBarTrailing) {
|
|
if viewModel.unreadCount > 0 {
|
|
Button {
|
|
Task {
|
|
await viewModel.markAllAsRead()
|
|
}
|
|
} label: {
|
|
Text("Mark All Read")
|
|
.font(.caption)
|
|
}
|
|
.foregroundColor(.blue)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.onAppear {
|
|
Task {
|
|
await viewModel.fetchNotifications()
|
|
}
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var notificationListView: some View {
|
|
List {
|
|
ForEach(viewModel.notifications) { notification in
|
|
NotificationRowView(notification: notification)
|
|
.onTapGesture {
|
|
Task {
|
|
if !notification.isRead {
|
|
await viewModel.markAsRead(id: notification.id)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.onDelete { offsets in
|
|
Task {
|
|
for index in offsets {
|
|
let notification = viewModel.notifications[index]
|
|
await viewModel.markAsRead(id: notification.id)
|
|
}
|
|
viewModel.notifications.remove(atOffsets: offsets)
|
|
}
|
|
}
|
|
}
|
|
.listStyle(.insetGrouped)
|
|
.refreshable {
|
|
await viewModel.refresh()
|
|
}
|
|
}
|
|
|
|
private var emptyStateView: some View {
|
|
VStack(spacing: 16) {
|
|
Image(systemName: "bell.slash")
|
|
.font(.system(size: 64))
|
|
.foregroundColor(.secondary)
|
|
|
|
Text("No Notifications")
|
|
.font(.title2)
|
|
.fontWeight(.semibold)
|
|
.foregroundColor(.primary)
|
|
|
|
Text("You're all caught up!\nWhen you have notifications, they'll appear here.")
|
|
.font(.subheadline)
|
|
.foregroundColor(.secondary)
|
|
.multilineTextAlignment(.center)
|
|
.padding(.horizontal, 32)
|
|
}
|
|
.padding(.vertical, 60)
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
NotificationsView()
|
|
}
|
|
|
|
#Preview("With Data") {
|
|
let previewView = NotificationsView()
|
|
|
|
// Inject mock data for preview
|
|
return previewView
|
|
}
|