Pitch-Karte erweitert um die neuen Features (sowie Hundesitting, Züchter).
Neue DTOs in DTOs.swift:
- Expense + ExpenseCreateBody
- GassiZeit + GassiZeitCreateBody (mit wochentage [String], radius_m)
- PoisonAlert + PoisonCreateBody
- LostDog + LostDogCreateBody
- WeatherForecast + WeatherDay (mit asphalt_temp, zecken, pollen-Felder)
Neue Views:
- ErsteHilfeView + Detail: sechs Notfall-Topics (Vergiftung, Hitzschlag,
Wunden, Atemnot, Krampfanfall, Magendrehung) — komplett offline, kein API
- AusgabenView: Liste mit Total, AddExpenseSheet mit Kategorie/Betrag/
Datum/Hund-Picker
- WetterView: One-Shot Location + /api/weather/forecast, 7-Tage-Vorhersage
mit Hunde-Tipps (Hitze ab 25°/30°, Frost, Asphalt ≥50°, Zecken, Regen)
- GassiZeitenView: eigene Zeiten + Add-Sheet (Wochentag-Picker, Hund-
Auswahl), automatische lokale UNCalendarNotifications via Scheduler
- GiftkoederView: Map mit Pins + Liste in 5km Umkreis, Report-Sheet mit
Typ-Auswahl
- VerloreneHundeView: Liste mit Foto/Distanz, Detail mit Karte
Support:
- OneShotLocation: kleiner CLLocationManager-Wrapper für einmalige
Positionsabfrage (Wetter, Giftköder)
- GassiZeitenScheduler: UNCalendarNotificationTrigger pro Wochentag,
Identifier-Schema "gz-{id}-{weekday}"
Navigation: Section "Hund & Alltag" im Mehr-Tab mit NavigationLinks zu
allen sechs neuen Ansichten.
232 lines
4.8 KiB
Swift
232 lines
4.8 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
|
|
}
|
|
|
|
// MARK: - Expenses
|
|
|
|
struct Expense: Decodable, Identifiable {
|
|
let id: Int
|
|
let dogId: Int?
|
|
let kategorie: String
|
|
let betrag: Double
|
|
let datum: String
|
|
let notiz: String?
|
|
let dogName: String?
|
|
}
|
|
|
|
struct ExpenseCreateBody: Encodable {
|
|
let dogId: Int?
|
|
let kategorie: String
|
|
let betrag: Double
|
|
let datum: String
|
|
let notiz: String?
|
|
}
|
|
|
|
// MARK: - Gassi-Zeiten
|
|
|
|
struct GassiZeit: Decodable, Identifiable {
|
|
let id: Int
|
|
let dogId: Int?
|
|
let wochentage: [String]
|
|
let uhrzeit: String
|
|
let ortName: String?
|
|
let lat: Double?
|
|
let lon: Double?
|
|
let radiusM: Int?
|
|
let notiz: String?
|
|
let aktiv: Int?
|
|
let distanceM: Int?
|
|
let isMine: Bool?
|
|
let userName: String?
|
|
let dogName: String?
|
|
let dogRasse: String?
|
|
}
|
|
|
|
struct GassiZeitCreateBody: Encodable {
|
|
let dogId: Int?
|
|
let wochentage: [String]
|
|
let uhrzeit: String
|
|
let ortName: String?
|
|
let lat: Double?
|
|
let lon: Double?
|
|
let radiusM: Int
|
|
let notiz: String?
|
|
}
|
|
|
|
// MARK: - Poison
|
|
|
|
struct PoisonAlert: Decodable, Identifiable {
|
|
let id: Int
|
|
let lat: Double
|
|
let lon: Double
|
|
let beschreibung: String?
|
|
let typ: String?
|
|
let distanzM: Int?
|
|
let fotoUrl: String?
|
|
let melderName: String?
|
|
let createdAt: String?
|
|
}
|
|
|
|
struct PoisonCreateBody: Encodable {
|
|
let lat: Double
|
|
let lon: Double
|
|
let beschreibung: String?
|
|
let typ: String
|
|
}
|
|
|
|
// MARK: - Lost Dogs
|
|
|
|
struct LostDog: Decodable, Identifiable {
|
|
let id: Int
|
|
let name: String
|
|
let rasse: String?
|
|
let beschreibung: String
|
|
let lat: Double
|
|
let lon: Double
|
|
let distanzM: Int?
|
|
let fotoUrl: String?
|
|
let melderName: String?
|
|
let createdAt: String?
|
|
}
|
|
|
|
struct LostDogCreateBody: Encodable {
|
|
let name: String
|
|
let rasse: String?
|
|
let beschreibung: String
|
|
let lat: Double
|
|
let lon: Double
|
|
let dogId: Int?
|
|
}
|
|
|
|
// MARK: - Weather
|
|
|
|
struct WeatherForecast: Decodable {
|
|
let days: [WeatherDay]
|
|
}
|
|
|
|
struct WeatherDay: Decodable, Identifiable {
|
|
let date: String
|
|
let wday: String?
|
|
let weathercode: Int?
|
|
let desc: String?
|
|
let icon: String?
|
|
let tempMax: Double?
|
|
let tempMin: Double?
|
|
let precipProb: Int?
|
|
let precipSum: Double?
|
|
let windKmh: Double?
|
|
let uvIndex: Double?
|
|
let sunrise: String?
|
|
let sunset: String?
|
|
let asphaltTemp: Double?
|
|
let zecken: String?
|
|
|
|
var id: String { date }
|
|
}
|
|
|
|
/// 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)
|
|
}
|
|
}
|