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.
This commit is contained in:
rene 2026-05-30 11:19:53 +02:00
parent 30e0fbe7ec
commit c01e3d6be7
26 changed files with 978 additions and 28 deletions

View file

@ -8,7 +8,7 @@ struct TrackingView: View {
@State private var tracker = LocationTracker()
@State private var now: Date = .now
@State private var pendingPhotos: [Data] = []
@State private var pendingPhotos: [CapturedPhoto] = []
@State private var showFinishSheet = false
@State private var showCamera = false
@ -31,7 +31,10 @@ struct TrackingView: View {
.navigationBarTitleDisplayMode(.inline)
}
.onReceive(clockTicker) { now = $0 }
.onReceive(persistTicker) { _ in persistActive() }
.onReceive(persistTicker) { _ in
tracker.checkAutoPause()
persistActive()
}
.onAppear { offerResumeIfNeeded() }
.sheet(isPresented: $showFinishSheet) {
FinishWalkSheet(
@ -45,7 +48,8 @@ struct TrackingView: View {
}
.fullScreenCover(isPresented: $showCamera) {
CameraPicker { data in
pendingPhotos.append(data)
let location = tracker.points.last
pendingPhotos.append(CapturedPhoto(data: data, location: location))
}
.ignoresSafeArea()
}
@ -109,18 +113,20 @@ struct TrackingView: View {
.background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16))
.overlay(alignment: .topLeading) {
if tracker.isPaused {
pausedBadge.padding(8)
badge("Pause", icon: "pause.circle.fill", color: .orange).padding(8)
} else if tracker.isAutoPaused {
badge("Auto-Pause", icon: "pause.circle", color: .gray).padding(8)
}
}
}
private var pausedBadge: some View {
Label("Pause", systemImage: "pause.circle.fill")
private func badge(_ text: String, icon: String, color: Color) -> some View {
Label(text, systemImage: icon)
.font(.caption.bold())
.foregroundStyle(.white)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(.orange, in: Capsule())
.background(color, in: Capsule())
}
private var divider: some View {