import SwiftUI import MapKit import CoreLocation struct DiaryDetailView: View { let dogId: Int @State var entry: DiaryEntry let onChange: () async -> Void @Environment(\.dismiss) private var dismiss @State private var photoIndex = 0 @State private var showDeleteConfirm = false @State private var isDeleting = false @State private var errorMessage: String? var body: some View { ScrollView { VStack(alignment: .leading, spacing: 16) { header if let media = entry.mediaItems, !media.isEmpty { gallery(media) } if let text = entry.text, !text.isEmpty { Text(text) .font(.body) .padding(.horizontal, 14) } if let tags = entry.tags, !tags.isEmpty { tagsRow(tags) } if let lat = entry.gpsLat, let lon = entry.gpsLon { locationCard(lat: lat, lon: lon) } deleteButton if let errorMessage { Text(errorMessage).font(.footnote).foregroundStyle(.red).padding(.horizontal, 14) } } .padding(.vertical) } .navigationTitle(entry.titel?.isEmpty == false ? entry.titel! : "Eintrag") .navigationBarTitleDisplayMode(.inline) .alert("Eintrag löschen?", isPresented: $showDeleteConfirm) { Button("Abbrechen", role: .cancel) {} Button("Löschen", role: .destructive) { Task { await delete() } } } message: { Text("Dieser Tagebucheintrag und alle zugehörigen Fotos werden endgültig entfernt.") } } // MARK: - Header private var header: some View { VStack(alignment: .leading, spacing: 6) { HStack(spacing: 8) { if entry.isMilestoneFlag || entry.typ == "meilenstein" { Label("Meilenstein", systemImage: "star.fill") .font(.caption.bold()) .padding(.horizontal, 8) .padding(.vertical, 4) .background(Color.orange.opacity(0.2), in: Capsule()) .foregroundStyle(.orange) } if let typ = entry.typ, !typ.isEmpty, typ != "meilenstein" { Text(typ.capitalized) .font(.caption.bold()) .padding(.horizontal, 8) .padding(.vertical, 4) .background(Color.accentColor.opacity(0.15), in: Capsule()) .foregroundStyle(Color.accentColor) } Spacer() if let datum = entry.datum { Text(DiaryUtil.format(datum)) .font(.subheadline.monospacedDigit()) .foregroundStyle(.secondary) } } if let loc = entry.locationName, !loc.isEmpty { Label(loc, systemImage: "mappin.and.ellipse") .font(.caption) .foregroundStyle(.secondary) } } .padding(.horizontal, 14) } // MARK: - Photo gallery private func gallery(_ media: [DiaryMedia]) -> some View { VStack(spacing: 8) { TabView(selection: $photoIndex) { ForEach(Array(media.enumerated()), id: \.element.id) { idx, m in AsyncImage(url: URL(string: "https://banyaro.app\(m.url)")) { phase in switch phase { case .success(let img): img.resizable().scaledToFit() case .failure: Image(systemName: "photo") .font(.largeTitle) .foregroundStyle(.secondary) default: ProgressView() } } .frame(maxWidth: .infinity) .tag(idx) } } .tabViewStyle(.page(indexDisplayMode: media.count > 1 ? .always : .never)) .frame(height: 320) .background(Color.black.opacity(0.04)) .clipShape(RoundedRectangle(cornerRadius: 16)) .padding(.horizontal, 14) if media.count > 1 { Text("\(photoIndex + 1) / \(media.count)") .font(.caption.monospacedDigit()) .foregroundStyle(.secondary) } } } // MARK: - Tags private func tagsRow(_ tags: [String]) -> some View { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 6) { ForEach(tags, id: \.self) { tag in Text("#" + tag) .font(.caption.bold()) .padding(.horizontal, 10) .padding(.vertical, 5) .background(Color.secondary.opacity(0.15), in: Capsule()) } } .padding(.horizontal, 14) } } // MARK: - Location private func locationCard(lat: Double, lon: Double) -> some View { VStack(alignment: .leading, spacing: 8) { Label("Ort", systemImage: "location.fill") .font(.caption.bold()) .foregroundStyle(.secondary) .padding(.horizontal, 14) Map(initialPosition: .region(MKCoordinateRegion( center: CLLocationCoordinate2D(latitude: lat, longitude: lon), span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01) ))) { Annotation(entry.titel ?? "Eintrag", coordinate: CLLocationCoordinate2D(latitude: lat, longitude: lon)) { Image(systemName: "pawprint.circle.fill") .font(.title2) .foregroundStyle(.white, Color.accentColor) .background(.white, in: Circle()) } } .frame(height: 180) .clipShape(RoundedRectangle(cornerRadius: 12)) .padding(.horizontal, 14) .onTapGesture { openInMaps(lat: lat, lon: lon) } } } private func openInMaps(lat: Double, lon: Double) { let item = MKMapItem(placemark: MKPlacemark( coordinate: CLLocationCoordinate2D(latitude: lat, longitude: lon))) item.name = entry.titel ?? "Tagebuch-Eintrag" item.openInMaps(launchOptions: nil) } // MARK: - Delete private var deleteButton: some View { Button(role: .destructive) { showDeleteConfirm = true } label: { HStack { if isDeleting { ProgressView() } Image(systemName: "trash") Text("Eintrag löschen").bold() } .frame(maxWidth: .infinity, minHeight: 44) } .disabled(isDeleting) .padding(.horizontal, 14) .padding(.top, 10) } private func delete() async { isDeleting = true errorMessage = nil defer { isDeleting = false } do { try await APIClient.shared.delete("/api/dogs/\(dogId)/diary/\(entry.id)") await onChange() dismiss() } catch { errorMessage = error.localizedDescription } } }