05/15 Update: inspector issue view
This commit is contained in:
@@ -33,8 +33,8 @@ class SyncManager: ObservableObject {
|
||||
// poll only retrieves newer records. Nil on first launch → server returns
|
||||
// last 50 unread. Reset to nil on logout.
|
||||
private var lastNotificationFetch: Date?
|
||||
private var pollTimer: Timer?
|
||||
private let pollInterval: TimeInterval = 60 // seconds
|
||||
private var pollTask: Task<Void, Never>? // replaces Timer — Task.sleep works correctly
|
||||
private let pollInterval: UInt64 = 60_000_000_000 // 60 seconds in nanoseconds
|
||||
|
||||
static let shared = SyncManager()
|
||||
private init() {}
|
||||
@@ -51,36 +51,45 @@ class SyncManager: ObservableObject {
|
||||
if wasOffline {
|
||||
await self.triggerSync()
|
||||
}
|
||||
self.startPollTimer()
|
||||
self.startPollTask()
|
||||
} else {
|
||||
self.stopPollTimer()
|
||||
self.stopPollTask()
|
||||
}
|
||||
}
|
||||
}
|
||||
monitor.start(queue: monitorQueue)
|
||||
}
|
||||
|
||||
// ── Notification poll timer ───────────────────────────────────────────
|
||||
// ── Notification poll task ────────────────────────────────────────────
|
||||
// Timer.scheduledTimer requires RunLoop.main to be ticking. When called
|
||||
// from inside a Swift Concurrency Task { @MainActor } the current RunLoop
|
||||
// is NOT RunLoop.main — the timer is added to a runloop that never runs,
|
||||
// so it silently fires never. Task + Task.sleep has no such dependency.
|
||||
|
||||
private func startPollTimer() {
|
||||
guard pollTimer == nil else { return } // already running
|
||||
pollTimer = Timer.scheduledTimer(withTimeInterval: pollInterval, repeats: true) { [weak self] _ in
|
||||
Task { @MainActor [weak self] in
|
||||
guard let self, self.isOnline, AuthManager.shared.isAuthenticated else { return }
|
||||
await self.pollNotifications()
|
||||
private func startPollTask() {
|
||||
guard pollTask == nil else { return } // already running
|
||||
pollTask = Task { [weak self] in
|
||||
while !Task.isCancelled {
|
||||
try? await Task.sleep(nanoseconds: 60_000_000_000)
|
||||
guard !Task.isCancelled else { break }
|
||||
await MainActor.run { [weak self] in
|
||||
guard let self, self.isOnline,
|
||||
AuthManager.shared.isAuthenticated else { return }
|
||||
Task { await self.pollNotifications() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func stopPollTimer() {
|
||||
pollTimer?.invalidate()
|
||||
pollTimer = nil
|
||||
private func stopPollTask() {
|
||||
pollTask?.cancel()
|
||||
pollTask = nil
|
||||
}
|
||||
|
||||
/// Called on logout so the next login starts a clean fetch.
|
||||
func resetNotificationPoller() {
|
||||
lastNotificationFetch = nil
|
||||
stopPollTimer()
|
||||
stopPollTask()
|
||||
}
|
||||
|
||||
// ── Notification polling ──────────────────────────────────────────────
|
||||
@@ -405,7 +414,8 @@ class SyncManager: ObservableObject {
|
||||
guard isOnline, AuthManager.shared.isAuthenticated else { return }
|
||||
do {
|
||||
let apiIssues = try await APIClient.shared.fetchAssignedIssues()
|
||||
guard !apiIssues.isEmpty else { return }
|
||||
// Do not return early on empty — deletion still needs to run
|
||||
// to remove issues that were unassigned from this inspector.
|
||||
|
||||
// Build a map of existing LocalIssues by serverId for upsert
|
||||
let allLocal = (try? context.fetch(FetchDescriptor<LocalIssue>())) ?? []
|
||||
@@ -455,6 +465,24 @@ class SyncManager: ObservableObject {
|
||||
context.insert(local)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove server-pulled records that are no longer in the response.
|
||||
// This happens when an issue is reassigned to a different inspector —
|
||||
// the server stops returning it for this user, so the local copy must
|
||||
// be deleted. Only remove records that were pulled from the server
|
||||
// (syncStatus == "synced" AND serverId != nil AND inspectionLocalId == "").
|
||||
// Device-created issues (inspectionLocalId != "") are never touched.
|
||||
let returnedServerIds = Set(apiIssues.map { $0.id })
|
||||
for local in allLocal {
|
||||
guard let sid = local.serverId,
|
||||
local.syncStatus == "synced",
|
||||
local.inspectionLocalId == ""
|
||||
else { continue }
|
||||
if !returnedServerIds.contains(sid) {
|
||||
context.delete(local)
|
||||
}
|
||||
}
|
||||
|
||||
try? context.save()
|
||||
|
||||
} catch APIError.notAuthenticated {
|
||||
|
||||
Reference in New Issue
Block a user