- Create NotificationsView.swift with SwiftUI List and pull-to-refresh - Create NotificationRowView.swift for individual notification items - Create NotificationsViewModel.swift with MVVM pattern - Implement empty state view for no notifications - Add mark-as-read and mark-all-as-read functionality - Support notification types: loan approved/rejected, payment received/due, new lender, system updates - Add toolbar action for marking all notifications as read - Include README.md with architecture documentation and integration guide Next: Connect tRPC notifications router for data fetching
104 lines
3.2 KiB
Swift
104 lines
3.2 KiB
Swift
import SwiftUI
|
|
|
|
struct NotificationsView: View {
|
|
@StateObject private var viewModel = NotificationsViewModel()
|
|
@State private var showingRefreshIndicator = false
|
|
|
|
var body: some View {
|
|
NavigationView {
|
|
Group {
|
|
if viewModel.notifications.isEmpty && !viewModel.isLoading {
|
|
emptyStateView
|
|
} else {
|
|
notificationListView
|
|
}
|
|
}
|
|
.navigationTitle("Notifications")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.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(perform: deleteNotifications)
|
|
}
|
|
.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)
|
|
}
|
|
|
|
private func deleteNotifications(at offsets: IndexSet) async {
|
|
// TODO: Implement notification deletion logic
|
|
// This would typically call a delete API endpoint
|
|
for index in offsets {
|
|
let notification = viewModel.notifications[index]
|
|
// await notificationsService.delete(id: notification.id)
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
NotificationsView()
|
|
}
|
|
|
|
#Preview("With Data") {
|
|
let previewView = NotificationsView()
|
|
|
|
// Inject mock data for preview
|
|
return previewView
|
|
}
|