banyaro-ios/BanYaroGo/Views/EditRouteSheet.swift
rene c01e3d6be7 Phase 3.6: B+C+D komplett + HealthKit Sync
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.
2026-05-30 11:19:53 +02:00

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
}
}
}