banyaro-ios/BanYaroGo/Views/MiniRouteMap.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

59 lines
2.2 KiB
Swift

import SwiftUI
import MapKit
/// Non-interactive map showing a polyline for a GPS track. Optional photo
/// annotations can be tapped to fire a callback.
struct MiniRouteMap: View {
let track: [GPSPoint]
var lineWidth: CGFloat = 3
var photoLocations: [PhotoLocation] = []
var onPhotoTap: ((PhotoLocation) -> Void)? = nil
var body: some View {
Map(initialPosition: .region(region)) {
MapPolyline(coordinates: coordinates)
.stroke(Color.accentColor, style: StrokeStyle(lineWidth: lineWidth, lineJoin: .round))
ForEach(photoLocations) { loc in
Annotation("", coordinate: CLLocationCoordinate2D(latitude: loc.lat, longitude: loc.lon)) {
Button {
onPhotoTap?(loc)
} label: {
Image(systemName: "camera.circle.fill")
.font(.title2)
.foregroundStyle(.white, Color.accentColor)
.background(.white, in: Circle())
.shadow(radius: 2)
}
.buttonStyle(.plain)
.disabled(onPhotoTap == nil)
}
}
}
.mapStyle(.standard(elevation: .flat, pointsOfInterest: .excludingAll))
.allowsHitTesting(onPhotoTap != nil)
}
private var coordinates: [CLLocationCoordinate2D] {
track.map { CLLocationCoordinate2D(latitude: $0.lat, longitude: $0.lon) }
}
private var region: MKCoordinateRegion {
let lats = track.map(\.lat)
let lons = track.map(\.lon)
let minLat = lats.min() ?? 0
let maxLat = lats.max() ?? 0
let minLon = lons.min() ?? 0
let maxLon = lons.max() ?? 0
let center = CLLocationCoordinate2D(
latitude: (minLat + maxLat) / 2,
longitude: (minLon + maxLon) / 2
)
let latDelta = max((maxLat - minLat) * 1.4, 0.002)
let lonDelta = max((maxLon - minLon) * 1.4, 0.002)
return MKCoordinateRegion(
center: center,
span: MKCoordinateSpan(latitudeDelta: latDelta, longitudeDelta: lonDelta)
)
}
}