Aug 19 - Fixed photo-loss issue

This commit is contained in:
Nguyen Ngo
2026-08-19 10:31:20 -04:00
parent 7cbc514c39
commit e31cd7e1ff
13 changed files with 856 additions and 149 deletions
@@ -19,9 +19,20 @@ struct SyncStatusView: View {
sort: \LocalIssue.createdAt
) private var pendingIssues: [LocalIssue]
/// Unfiltered `uploadStatus` is matched in Swift rather than in a
/// #Predicate, per CLAUDE.md rules 3/48.
@Query private var allPendingPhotos: [PendingPhoto]
private var failedInspections: [LocalInspection] { pendingInspections.filter { $0.syncStatus == "failed" } }
private var failedIssues: [LocalIssue] { pendingIssues.filter { $0.syncStatus == "failed" } }
private var hasFailedItems: Bool { !failedInspections.isEmpty || !failedIssues.isEmpty }
/// Photos that exhausted `SyncManager.maxPhotoUploadAttempts`. These are the
/// reason an inspection can be submitted with a blank photo field, and
/// nothing else in the app ever moves one off "failed" so they belong in
/// the retry action too.
private var failedPhotos: [PendingPhoto] { allPendingPhotos.filter { $0.uploadStatus == "failed" } }
private var hasFailedItems: Bool {
!failedInspections.isEmpty || !failedIssues.isEmpty || !failedPhotos.isEmpty
}
var body: some View {
List {
@@ -60,7 +71,7 @@ struct SyncStatusView: View {
Button {
retryAllFailed()
} label: {
Label("Retry Failed Items (\(failedInspections.count + failedIssues.count))",
Label("Retry Failed Items (\(failedInspections.count + failedIssues.count + failedPhotos.count))",
systemImage: "exclamationmark.arrow.circlepath")
.foregroundStyle(.orange)
}
@@ -113,6 +124,14 @@ struct SyncStatusView: View {
issue.syncRetryCount = 0
issue.syncErrorMessage = nil
}
// Photos too. A PendingPhoto only reaches "failed" after every upload
// attempt was used, and that is exactly the state that lets an
// inspection be submitted with its photo field blank so without this
// the retry button could never actually recover a lost photo.
for photo in failedPhotos {
photo.uploadStatus = "pending"
photo.uploadRetryCount = 0
}
try? context.save()
Task { await sync.triggerSync() }
}