banyaro-ios/BanYaroGo/API/DTOs.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

110 lines
2.5 KiB
Swift

import Foundation
// MARK: - Auth
struct LoginRequest: Encodable {
let email: String
let password: String
}
struct LoginResponse: Decodable {
let token: String
let name: String
let isPremium: Bool
}
struct UserProfile: Decodable {
let id: Int
let name: String
let email: String
let realName: String?
let rolle: String?
let isPremium: Bool?
// Backend bool-konvertiert nur is_premium; is_founder/is_partner kommen
// als SQLite-Int 0/1 zurück deshalb hier Int? statt Bool?.
let isFounder: Int?
let isPartner: Int?
let founderNumber: Int?
let subscriptionTier: String?
let avatarUrl: String?
let wohnort: String?
let bio: String?
var isFounderFlag: Bool { isFounder == 1 }
var isPartnerFlag: Bool { isPartner == 1 }
}
// MARK: - Dogs
struct Dog: Decodable, Identifiable {
let id: Int
let name: String
let rasse: String?
let fotoUrl: String?
let geburtstag: String?
}
// MARK: - Routes
struct GPSPoint: Codable, Hashable {
let lat: Double
let lon: Double
let alt: Double?
}
struct RouteListItem: Decodable, Identifiable {
let id: Int
let userId: Int
let name: String
let beschreibung: String?
let distanzKm: Double?
let dauerMin: Int?
let createdAt: String?
let previewTrack: [GPSPoint]
let fotoUrls: [String]?
let userName: String?
let isPublic: Bool?
}
struct RouteDetail: Decodable, Identifiable {
let id: Int
let userId: Int
let name: String
let beschreibung: String?
let distanzKm: Double?
let dauerMin: Int?
let gpsTrack: [GPSPoint]
let fotoUrls: [String]?
let createdAt: String?
let userName: String?
let dogIds: [Int]?
}
struct RouteCreateBody: Encodable {
let name: String
let gpsTrack: [GPSPoint]
let distanzKm: Double
let dauerMin: Int
let dogIds: [Int]
let isPublic: Bool
}
/// Patch body for PATCH /api/routes/{id}. Only non-nil fields are encoded.
struct RouteUpdateBody: Encodable {
var name: String?
var beschreibung: String?
var isPublic: Bool?
enum CodingKeys: String, CodingKey {
case name
case beschreibung
case isPublic
}
func encode(to encoder: Encoder) throws {
var c = encoder.container(keyedBy: CodingKeys.self)
try c.encodeIfPresent(name, forKey: .name)
try c.encodeIfPresent(beschreibung, forKey: .beschreibung)
try c.encodeIfPresent(isPublic, forKey: .isPublic)
}
}