05/15 Update: show issue's images, push notifications
This commit is contained in:
@@ -875,6 +875,32 @@ struct IssueDetailView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !issue.photoServerPaths.isEmpty {
|
||||
Section("Photos (\(issue.photoServerPaths.count))") {
|
||||
ForEach(issue.photoServerPaths, id: \.self) { relativePath in
|
||||
AsyncImage(url: URL(string: Constants.baseURL + "/" + relativePath)) { phase in
|
||||
switch phase {
|
||||
case .success(let image):
|
||||
image
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
case .failure:
|
||||
Label("Photo unavailable", systemImage: "photo.slash")
|
||||
.foregroundStyle(.secondary)
|
||||
case .empty:
|
||||
HStack(spacing: 8) {
|
||||
ProgressView()
|
||||
Text("Loading…").font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
@unknown default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Issue Detail")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
|
||||
@@ -28,6 +28,7 @@ struct FlagIssueView: View {
|
||||
|
||||
@State private var showCamera = false
|
||||
@State private var showLibrary = false
|
||||
@State private var showBanner = false // success confirmation banner
|
||||
|
||||
private let maxPhotos = 5
|
||||
|
||||
@@ -199,6 +200,33 @@ struct FlagIssueView: View {
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
}
|
||||
// ── Submission confirmation banner ─────────────────────────────
|
||||
.overlay(alignment: .top) {
|
||||
if showBanner {
|
||||
HStack(spacing: 12) {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.font(.title2)
|
||||
.foregroundStyle(.green)
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("Issue Logged")
|
||||
.font(.headline)
|
||||
Text(sync.isOnline ? "Submitted to server." : "Saved — will sync when online.")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.padding(16)
|
||||
.background(Color(.secondarySystemGroupedBackground))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||
.shadow(color: .black.opacity(0.1), radius: 8, y: 4)
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.top, 8)
|
||||
.transition(.move(edge: .top).combined(with: .opacity))
|
||||
.zIndex(10)
|
||||
}
|
||||
}
|
||||
.animation(.spring(duration: 0.35), value: showBanner)
|
||||
.fullScreenCover(isPresented: $showCamera) {
|
||||
CameraPickerView(image: .constant(nil), onSelected: appendPhoto)
|
||||
.ignoresSafeArea()
|
||||
@@ -272,6 +300,11 @@ struct FlagIssueView: View {
|
||||
Task { await sync.triggerSync() }
|
||||
}
|
||||
|
||||
dismiss()
|
||||
// Show confirmation banner for 2 seconds then dismiss.
|
||||
withAnimation { showBanner = true }
|
||||
Task {
|
||||
try? await Task.sleep(for: .seconds(2))
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user