D.10 401-Handling: APIError.unauthorized, NotificationCenter-Bridge, AuthSession.logout() bei 401 → User landet wieder im Login D.12 PWA-Deep-Links: Settings-Section mit Forum/Hunde/Walks/Settings öffnet Safari per https://banyaro.app/#fragment B.4 Auto-Pause: 2-min-Inaktivität → isAutoPaused, automatischer Resume bei nächstem GPS-Update. Settings-Toggle, im UI eigenes Badge "Auto-Pause" (grau vs. Pause orange). C.7 Edit/Delete: RouteUpdateBody + APIClient.patch + APIClient.delete, EditRouteSheet (Name/Beschreibung/Public), Menu in Toolbar (nur eigene Touren), Alert für Delete. C.9 Statistik-Tab: neuer Tab "Statistik" zwischen Hunde und Mehr. Filtert /api/routes auf meine Touren, rechnet Woche/Monat/Allzeit (Distanz, Dauer, Touren), Längste Tour, aktuelle Streak (Tage in Folge). B.5 Walk-Review: Map-Header an die Spitze des FinishWalkSheet-Forms. B.6 Geo-Fotos: CapturedPhoto (Data + GPSPoint?), PhotoLocation @Model in SwiftData. Kamera während Walk taggt mit tracker.points.last. Nach Upload: foto_url aus Response → PhotoLocation persistiert. MiniRouteMap rendert Annotations mit Tap-Callback, PhotoViewerSheet zeigt Foto fullscreen. C.8 Share PNG+GPX: RouteShareImage (MKMapSnapshotter + Polyline overlay + SwiftUI ShareCard via ImageRenderer), GPXExporter (Tempfile mit XML), ShareSheet (UIActivityViewController-Wrapper), Menu in Route-Toolbar. D.11 Icon-Varianten: AppIcon-Dark (0.45 Brightness), AppIcon-Tinted (Grayscale + Kontrastverstärkung), Contents.json mit appearance entries. A.2 HealthKit: BanYaroGo.entitlements (com.apple.developer.healthkit), NSHealthShare/UpdateUsageDescription. WalkHealthSync.shared mit HKWorkoutBuilder (.walking) + HKWorkoutRouteBuilder, Timestamps gleichmäßig über Walk-Dauer verteilt. Settings-Toggle mit Permission-Request.
84 lines
2.8 KiB
Swift
84 lines
2.8 KiB
Swift
import SwiftUI
|
|
|
|
struct EditRouteSheet: View {
|
|
let routeId: Int
|
|
|
|
@Environment(\.dismiss) private var dismiss
|
|
|
|
@State private var name: String
|
|
@State private var beschreibung: String
|
|
@State private var isPublic: Bool
|
|
@State private var isSaving = false
|
|
@State private var errorMessage: String?
|
|
|
|
let onSaved: (RouteDetail) -> Void
|
|
|
|
init(detail: RouteDetail, onSaved: @escaping (RouteDetail) -> Void) {
|
|
self.routeId = detail.id
|
|
self.onSaved = onSaved
|
|
_name = State(initialValue: detail.name)
|
|
_beschreibung = State(initialValue: detail.beschreibung ?? "")
|
|
_isPublic = State(initialValue: false) // backend field; default to private
|
|
}
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
Form {
|
|
Section("Name") {
|
|
TextField("Name", text: $name)
|
|
}
|
|
Section("Beschreibung") {
|
|
TextField("Beschreibung", text: $beschreibung, axis: .vertical)
|
|
.lineLimit(3...8)
|
|
}
|
|
Section {
|
|
Toggle("Öffentlich sichtbar", isOn: $isPublic)
|
|
}
|
|
if let errorMessage {
|
|
Section {
|
|
Text(errorMessage).font(.footnote).foregroundStyle(.red)
|
|
}
|
|
}
|
|
}
|
|
.navigationTitle("Tour bearbeiten")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
ToolbarItem(placement: .cancellationAction) {
|
|
Button("Abbrechen") { dismiss() }
|
|
.disabled(isSaving)
|
|
}
|
|
ToolbarItem(placement: .confirmationAction) {
|
|
if isSaving {
|
|
ProgressView()
|
|
} else {
|
|
Button("Sichern") { Task { await save() } }
|
|
.disabled(name.trimmingCharacters(in: .whitespaces).isEmpty)
|
|
}
|
|
}
|
|
}
|
|
.interactiveDismissDisabled(isSaving)
|
|
}
|
|
}
|
|
|
|
private func save() async {
|
|
isSaving = true
|
|
errorMessage = nil
|
|
defer { isSaving = false }
|
|
let body = RouteUpdateBody(
|
|
name: name.trimmingCharacters(in: .whitespaces),
|
|
beschreibung: beschreibung.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
? nil : beschreibung.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
isPublic: isPublic
|
|
)
|
|
do {
|
|
let updated: RouteDetail = try await APIClient.shared.patch(
|
|
"/api/routes/\(routeId)",
|
|
body: body
|
|
)
|
|
onSaved(updated)
|
|
dismiss()
|
|
} catch {
|
|
errorMessage = error.localizedDescription
|
|
}
|
|
}
|
|
}
|