Files
mayday/Mayday/ViewModels/NotificationsViewModel.swift
T
copilot-swe-agent[bot] d642bffcaa fix: address code review feedback
- SettingsView: replace broken Toggle+constant with a Button that directly
  opens system notification settings (Toggle was bound to .constant(true)
  and onChange never fired)
- HTTPClient: append query parameters to URL for GET requests instead of
  putting them in the request body (body is ignored for GET)
- PushNotificationService: document why info-severity alerts skip Live Activities
- NotificationsViewModel: document polling page-reset behavior

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-13 23:05:53 +00:00

96 lines
3.0 KiB
Swift

import Foundation
import SwiftUI
@MainActor
class NotificationsViewModel: ObservableObject {
@Published var notifications: [AppNotification] = []
@Published var isLoading = false
@Published var isLoadingMore = false
@Published var error: String?
@Published var hasMore = true
private let service = NotificationsAPIService.shared
private var currentPage = 1
private let perPage = 20
private var pollingTask: Task<Void, Never>?
func load() async {
isLoading = true
error = nil
currentPage = 1
defer { isLoading = false }
do {
let page = try await service.getNotifications(page: 1, perPage: perPage)
notifications = page.items
hasMore = page.items.count == perPage
updateBadge()
} catch {
self.error = error.localizedDescription
}
}
func loadMore() async {
guard !isLoadingMore && hasMore else { return }
isLoadingMore = true
defer { isLoadingMore = false }
do {
let nextPage = currentPage + 1
let page = try await service.getNotifications(page: nextPage, perPage: perPage)
notifications.append(contentsOf: page.items)
currentPage = nextPage
hasMore = page.items.count == perPage
} catch {
self.error = error.localizedDescription
}
}
func markAsRead(_ notification: AppNotification) async {
guard !notification.isRead else { return }
do {
try await service.markAsRead(id: notification.id)
if let index = notifications.firstIndex(where: { $0.id == notification.id }) {
let updated = AppNotification(
id: notification.id,
topic: notification.topic,
subject: notification.subject,
body: notification.body,
metadata: notification.metadata,
status: .read,
channel: notification.channel,
readAt: Date(),
createdAt: notification.createdAt,
updatedAt: Date()
)
notifications[index] = updated
}
updateBadge()
} catch {
self.error = error.localizedDescription
}
}
func startPolling() {
// Polling always reloads page 1 to pick up new notifications.
// Users who have scrolled to older pages will have the list reset on each interval.
pollingTask = Task {
while !Task.isCancelled {
try? await Task.sleep(for: .seconds(30))
guard !Task.isCancelled else { break }
await load()
}
}
}
func stopPolling() {
pollingTask?.cancel()
pollingTask = nil
}
private func updateBadge() {
let unreadCount = notifications.filter { !$0.isRead }.count
Task {
await service.updateAppBadge(unreadCount)
}
}
}