feat: Implement NotificationsView component for Lendair iOS
- 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
This commit is contained in:
103
Lendair/Views/NotificationsView.swift
Normal file
103
Lendair/Views/NotificationsView.swift
Normal file
@@ -0,0 +1,103 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user