Jul 21 - Update to support photos' timestamp/location

This commit is contained in:
2026-07-21 16:22:13 -04:00
parent 2a0b264a5c
commit 1921f95faf
18 changed files with 261 additions and 5172 deletions
@@ -643,11 +643,17 @@ struct ExecuteInspectionView: View {
private func handlePhotoSelected(localPath: String, field: [String: Any]) {
let fid = fieldId(field)
formValues[fid] = "local://\(localPath)"
// Stamp capture time + the current fix now; the upload may be hours
// later on a slow sync and must not use its own clock.
let fix = locationManager.lastLocation ?? PhotoLocationProvider.shared.lastLocation
let photo = PendingPhoto(
localFilePath: localPath,
entityType: "inspection",
entityLocalId: inspection.localId,
fieldId: fid
localFilePath: localPath,
entityType: "inspection",
entityLocalId: inspection.localId,
fieldId: fid,
capturedAt: Date(),
captureLatitude: fix?.coordinate.latitude,
captureLongitude: fix?.coordinate.longitude
)
inspection.pendingPhotos.append(photo)
context.insert(photo)
@@ -24,7 +24,9 @@ struct FlagIssueView: View {
@State private var description = ""
// Each entry: (UIImage for display, local file path for storage)
@State private var photos: [(image: UIImage, path: String)] = []
// CapturedPhoto records the capture moment + GPS fix at shutter time; the
// `image` / `path` members match the tuple this replaced.
@State private var photos: [CapturedPhoto] = []
@State private var showCamera = false
@State private var showLibrary = false
@@ -190,6 +192,9 @@ struct FlagIssueView: View {
}
.navigationTitle("Flag Issue")
.navigationBarTitleDisplayMode(.inline)
// Warm up GPS so a fix exists the instant a photo is taken; the
// coordinates are burned into the photo server-side.
.onAppear { PhotoLocationProvider.shared.start() }
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") { dismiss() }
@@ -242,14 +247,14 @@ struct FlagIssueView: View {
private func appendPhoto(_ img: UIImage) {
guard photos.count < maxPhotos else { return }
guard let path = savePhotoToDisk(img) else { return }
photos.append((image: img, path: path))
photos.append(CapturedPhoto(image: img, path: path))
}
private func appendPhotos(_ images: [UIImage]) {
for img in images {
guard photos.count < maxPhotos else { break }
guard let path = savePhotoToDisk(img) else { continue }
photos.append((image: img, path: path))
photos.append(CapturedPhoto(image: img, path: path))
}
}
@@ -290,9 +295,12 @@ struct FlagIssueView: View {
// Create one PendingPhoto per photo so they all upload independently
for photo in photos {
let pending = PendingPhoto(
localFilePath: photo.path,
entityType: "issue",
entityLocalId: issue.localId
localFilePath: photo.path,
entityType: "issue",
entityLocalId: issue.localId,
capturedAt: photo.capturedAt,
captureLatitude: photo.latitude,
captureLongitude: photo.longitude
)
context.insert(pending)
}
+26 -10
View File
@@ -240,7 +240,8 @@ struct IssueDetailView: View {
@State private var statusError: String?
@State private var showStatusPicker = false
// Resolution Photos
@State private var resultPhotos: [(image: UIImage, path: String)] = []
// Resolution photos are evidence too record capture time + GPS at shutter.
@State private var resultPhotos: [CapturedPhoto] = []
@State private var showResultCamera = false
@State private var showResultLibrary = false
@State private var isUploadingResultPhotos = false
@@ -641,6 +642,8 @@ struct IssueDetailView: View {
}
.navigationTitle("Issue Detail")
.navigationBarTitleDisplayMode(.inline)
// Warm up GPS so resolution photos taken here carry coordinates.
.onAppear { PhotoLocationProvider.shared.start() }
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button {
@@ -981,14 +984,14 @@ struct IssueDetailView: View {
private func appendResultPhoto(_ img: UIImage) {
guard resultPhotos.count < maxResultPhotos,
let path = saveResultPhotoToDisk(img) else { return }
resultPhotos.append((image: img, path: path))
resultPhotos.append(CapturedPhoto(image: img, path: path))
}
private func appendResultPhotos(_ images: [UIImage]) {
for img in images {
guard resultPhotos.count < maxResultPhotos,
let path = saveResultPhotoToDisk(img) else { break }
resultPhotos.append((image: img, path: path))
resultPhotos.append(CapturedPhoto(image: img, path: path))
}
}
@@ -1024,7 +1027,12 @@ struct IssueDetailView: View {
do {
var serverPaths: [String] = []
for photo in resultPhotos {
let path = try await APIClient.shared.uploadResultPhoto(localPath: photo.path)
let path = try await APIClient.shared.uploadResultPhoto(
localPath: photo.path,
capturedAt: photo.capturedAt,
latitude: photo.latitude,
longitude: photo.longitude
)
serverPaths.append(path)
}
@@ -1066,7 +1074,9 @@ struct StandaloneIssueView: View {
@State private var severity = "medium"
@State private var description = ""
@State private var photos: [(image: UIImage, path: String)] = []
// CapturedPhoto records the capture moment + GPS fix at shutter time; the
// `image` / `path` members match the tuple this replaced.
@State private var photos: [CapturedPhoto] = []
@State private var showCamera = false
@State private var showLibrary = false
@State private var showBanner = false
@@ -1238,6 +1248,9 @@ struct StandaloneIssueView: View {
}
.navigationTitle("New Issue")
.navigationBarTitleDisplayMode(.inline)
// Warm up GPS so a fix exists the instant a photo is taken; the
// coordinates are burned into the photo server-side.
.onAppear { PhotoLocationProvider.shared.start() }
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") { dismiss() }
@@ -1284,13 +1297,13 @@ struct StandaloneIssueView: View {
private func appendPhoto(_ img: UIImage) {
guard photos.count < maxPhotos, let path = savePhotoToDisk(img) else { return }
photos.append((image: img, path: path))
photos.append(CapturedPhoto(image: img, path: path))
}
private func appendPhotos(_ images: [UIImage]) {
for img in images {
guard photos.count < maxPhotos, let path = savePhotoToDisk(img) else { break }
photos.append((image: img, path: path))
photos.append(CapturedPhoto(image: img, path: path))
}
}
@@ -1325,9 +1338,12 @@ struct StandaloneIssueView: View {
for photo in photos {
let pending = PendingPhoto(
localFilePath: photo.path,
entityType: "issue",
entityLocalId: issue.localId
localFilePath: photo.path,
entityType: "issue",
entityLocalId: issue.localId,
capturedAt: photo.capturedAt,
captureLatitude: photo.latitude,
captureLongitude: photo.longitude
)
context.insert(pending)
}