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.
270 lines
11 KiB
Swift
270 lines
11 KiB
Swift
import SwiftUI
|
||
|
||
struct ErsteHilfeView: View {
|
||
var body: some View {
|
||
List {
|
||
Section {
|
||
HStack(spacing: 12) {
|
||
Image(systemName: "phone.fill")
|
||
.foregroundStyle(.red)
|
||
VStack(alignment: .leading) {
|
||
Text("Im Ernstfall sofort den Tierarzt anrufen!").font(.subheadline.bold())
|
||
Text("Diese Hinweise ersetzen keine tierärztliche Behandlung.")
|
||
.font(.caption)
|
||
.foregroundStyle(.secondary)
|
||
}
|
||
}
|
||
.padding(.vertical, 4)
|
||
}
|
||
|
||
ForEach(ErsteHilfeContent.topics) { topic in
|
||
NavigationLink {
|
||
ErsteHilfeDetailView(topic: topic)
|
||
} label: {
|
||
HStack(spacing: 14) {
|
||
Image(systemName: topic.icon)
|
||
.font(.title3)
|
||
.foregroundStyle(topic.tint)
|
||
.frame(width: 32)
|
||
VStack(alignment: .leading, spacing: 2) {
|
||
Text(topic.title).font(.headline)
|
||
Text(topic.summary)
|
||
.font(.caption)
|
||
.foregroundStyle(.secondary)
|
||
.lineLimit(2)
|
||
}
|
||
}
|
||
.padding(.vertical, 4)
|
||
}
|
||
}
|
||
}
|
||
.navigationTitle("Erste Hilfe")
|
||
.navigationBarTitleDisplayMode(.inline)
|
||
}
|
||
}
|
||
|
||
private struct ErsteHilfeDetailView: View {
|
||
let topic: ErsteHilfeTopic
|
||
|
||
var body: some View {
|
||
ScrollView {
|
||
VStack(alignment: .leading, spacing: 18) {
|
||
HStack(spacing: 14) {
|
||
Image(systemName: topic.icon)
|
||
.font(.system(size: 44))
|
||
.foregroundStyle(topic.tint)
|
||
.frame(width: 70, height: 70)
|
||
.background(topic.tint.opacity(0.15), in: Circle())
|
||
VStack(alignment: .leading, spacing: 4) {
|
||
Text(topic.title).font(.title2.bold())
|
||
Text(topic.summary)
|
||
.font(.subheadline)
|
||
.foregroundStyle(.secondary)
|
||
}
|
||
}
|
||
|
||
ForEach(Array(topic.sections.enumerated()), id: \.offset) { _, section in
|
||
VStack(alignment: .leading, spacing: 8) {
|
||
Text(section.heading).font(.headline)
|
||
VStack(alignment: .leading, spacing: 6) {
|
||
ForEach(Array(section.steps.enumerated()), id: \.offset) { i, step in
|
||
HStack(alignment: .firstTextBaseline, spacing: 10) {
|
||
Text("\(i + 1).")
|
||
.font(.subheadline.bold())
|
||
.foregroundStyle(topic.tint)
|
||
.frame(width: 20, alignment: .trailing)
|
||
Text(step)
|
||
.font(.subheadline)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
.padding()
|
||
.background(.background.secondary, in: RoundedRectangle(cornerRadius: 12))
|
||
}
|
||
|
||
if !topic.warnings.isEmpty {
|
||
VStack(alignment: .leading, spacing: 6) {
|
||
Label("Sofort zum Tierarzt", systemImage: "exclamationmark.triangle.fill")
|
||
.font(.headline)
|
||
.foregroundStyle(.red)
|
||
ForEach(topic.warnings, id: \.self) { w in
|
||
Text("• \(w)").font(.subheadline)
|
||
}
|
||
}
|
||
.padding()
|
||
.background(Color.red.opacity(0.08), in: RoundedRectangle(cornerRadius: 12))
|
||
}
|
||
}
|
||
.padding()
|
||
}
|
||
.navigationTitle(topic.title)
|
||
.navigationBarTitleDisplayMode(.inline)
|
||
}
|
||
}
|
||
|
||
// MARK: - Content
|
||
|
||
struct ErsteHilfeTopic: Identifiable {
|
||
let id: String
|
||
let title: String
|
||
let summary: String
|
||
let icon: String
|
||
let tint: Color
|
||
let sections: [Section]
|
||
let warnings: [String]
|
||
|
||
struct Section {
|
||
let heading: String
|
||
let steps: [String]
|
||
}
|
||
}
|
||
|
||
enum ErsteHilfeContent {
|
||
static let topics: [ErsteHilfeTopic] = [
|
||
ErsteHilfeTopic(
|
||
id: "vergiftung",
|
||
title: "Vergiftung",
|
||
summary: "Schokolade, Trauben, Giftköder, Frostschutz, Medikamente.",
|
||
icon: "exclamationmark.octagon.fill",
|
||
tint: .red,
|
||
sections: [
|
||
.init(heading: "Sofortmaßnahmen", steps: [
|
||
"Hund von der Giftquelle entfernen. Reste sichern (Foto/Verpackung).",
|
||
"NICHT zum Erbrechen bringen, außer der Tierarzt sagt es ausdrücklich.",
|
||
"Tierarzt oder Tierklinik anrufen — Art und Menge des Gifts angeben.",
|
||
"Bei Bewusstlosigkeit: stabile Seitenlage, Atemwege freihalten."
|
||
]),
|
||
.init(heading: "Was du nicht tun solltest", steps: [
|
||
"Kein Salzwasser, keine Milch, kein Öl geben — kann gefährlich sein.",
|
||
"Nicht abwarten, ob Symptome verschwinden."
|
||
])
|
||
],
|
||
warnings: [
|
||
"Krämpfe, Erbrechen, Durchfall, Speicheln, Apathie",
|
||
"Schaum vor dem Maul, Blutungen",
|
||
"Verdacht auf Giftköder (Wurst mit Glas, Nägel, Tabletten)"
|
||
]
|
||
),
|
||
ErsteHilfeTopic(
|
||
id: "hitzschlag",
|
||
title: "Hitzschlag",
|
||
summary: "Hecheln, Taumeln, Bewusstseinsverlust durch Überhitzung.",
|
||
icon: "thermometer.sun.fill",
|
||
tint: .orange,
|
||
sections: [
|
||
.init(heading: "Sofort kühlen", steps: [
|
||
"Hund in den Schatten oder kühlen Raum bringen.",
|
||
"Pfoten, Innenschenkel und Bauch mit lauwarmem (nicht eiskaltem!) Wasser kühlen.",
|
||
"Kleine Schlucke Wasser anbieten, nicht zwingen.",
|
||
"Auf dem Weg zum Tierarzt weiterkühlen — feuchte Tücher auf den Körper."
|
||
])
|
||
],
|
||
warnings: [
|
||
"Körpertemperatur über 40 °C",
|
||
"Verwirrung, Krämpfe, Erbrechen",
|
||
"Zahnfleisch dunkelrot, blau oder bleich"
|
||
]
|
||
),
|
||
ErsteHilfeTopic(
|
||
id: "wunde",
|
||
title: "Wunden & Blutungen",
|
||
summary: "Schnitte, Bisse, Verletzungen an Pfoten und Körper.",
|
||
icon: "bandage.fill",
|
||
tint: .pink,
|
||
sections: [
|
||
.init(heading: "Bei starker Blutung", steps: [
|
||
"Druckverband mit sauberem Tuch anlegen — fest, aber nicht abschnüren.",
|
||
"Bei spritzendem Blut: oberhalb der Wunde mit den Fingern abdrücken.",
|
||
"Pfote/Bein hochlagern, ruhig halten.",
|
||
"Sofort zum Tierarzt."
|
||
]),
|
||
.init(heading: "Kleine Wunden", steps: [
|
||
"Fremdkörper (Glas, Splitter) NICHT selbst rausziehen, wenn tief.",
|
||
"Wunde mit klarem Wasser ausspülen.",
|
||
"Trocken tupfen, locker abdecken — Hund vom Lecken abhalten."
|
||
])
|
||
],
|
||
warnings: [
|
||
"Stark blutende oder pumpende Wunde",
|
||
"Fremdkörper steckt fest",
|
||
"Tiefe Bisswunden (auch wenn klein) → Infektionsgefahr"
|
||
]
|
||
),
|
||
ErsteHilfeTopic(
|
||
id: "atemnot",
|
||
title: "Atemnot & Bewusstlosigkeit",
|
||
summary: "Erstickung, Würgen, Kollaps, Reanimation.",
|
||
icon: "lungs.fill",
|
||
tint: .blue,
|
||
sections: [
|
||
.init(heading: "Fremdkörper im Hals", steps: [
|
||
"Maul vorsichtig öffnen, mit Taschenlampe schauen.",
|
||
"Sichtbaren Fremdkörper mit den Fingern (nicht Pinzette!) lösen.",
|
||
"Bei kleinem Hund: Kopf nach unten halten und zwischen die Schulterblätter klopfen.",
|
||
"Bei großem Hund: Heimlich-Manöver — von hinten umfassen, ruckartig nach oben drücken."
|
||
]),
|
||
.init(heading: "Reanimation (CPR)", steps: [
|
||
"Hund auf rechte Seite legen, Atemwege kontrollieren.",
|
||
"Maul schließen, in die Nase atmen — Brustkorb soll sich heben.",
|
||
"30 Herzdruckmassagen (auf Brustkorb-Höhe der Schulterblätter), dann 2 Beatmungen.",
|
||
"Weiter bis Atmung einsetzt oder Tierarzt übernimmt."
|
||
])
|
||
],
|
||
warnings: [
|
||
"Bewusstlosigkeit, keine Atmung, kein Puls",
|
||
"Blaue Schleimhäute",
|
||
"Würgen ohne Erfolg über mehrere Minuten"
|
||
]
|
||
),
|
||
ErsteHilfeTopic(
|
||
id: "krampfanfall",
|
||
title: "Krampfanfall",
|
||
summary: "Epileptischer Anfall, Zittern, Bewusstseinsstörung.",
|
||
icon: "waveform.path.ecg",
|
||
tint: .purple,
|
||
sections: [
|
||
.init(heading: "Während des Anfalls", steps: [
|
||
"Ruhe bewahren, Zeit messen (Dauer ist wichtig für den Tierarzt).",
|
||
"Umgebung sichern — Möbel/Kanten wegräumen.",
|
||
"NICHT festhalten, NICHTS ins Maul stecken.",
|
||
"Licht, Geräusche und Reize reduzieren."
|
||
]),
|
||
.init(heading: "Nach dem Anfall", steps: [
|
||
"Hund kann verwirrt, blind oder unruhig sein — Zeit geben.",
|
||
"Wasser bereitstellen, in eine ruhige Ecke legen.",
|
||
"Auch nach erstem Anfall zum Tierarzt — Ursache abklären."
|
||
])
|
||
],
|
||
warnings: [
|
||
"Anfall dauert länger als 5 Minuten (Notfall!)",
|
||
"Mehrere Anfälle in Folge",
|
||
"Erster Anfall überhaupt"
|
||
]
|
||
),
|
||
ErsteHilfeTopic(
|
||
id: "magendrehung",
|
||
title: "Magendrehung",
|
||
summary: "Akuter Notfall, vor allem bei großen Rassen.",
|
||
icon: "stomach.fill",
|
||
tint: .red,
|
||
sections: [
|
||
.init(heading: "Erkennen", steps: [
|
||
"Aufgeblähter, harter Bauch.",
|
||
"Erfolgloses Würgen ohne Erbrechen.",
|
||
"Unruhe, Speicheln, Atemnot.",
|
||
"Häufig nach dem Fressen / Trinken großer Mengen."
|
||
]),
|
||
.init(heading: "Sofort handeln", steps: [
|
||
"JEDE Minute zählt — direkt zum Notfall-Tierarzt fahren.",
|
||
"Beim Transport ruhig halten, nicht füttern, nicht tränken.",
|
||
"Vorher anrufen, damit OP-Team bereitsteht."
|
||
])
|
||
],
|
||
warnings: [
|
||
"Symptome wie oben → IMMER Notfall",
|
||
"Ohne OP innerhalb weniger Stunden tödlich"
|
||
]
|
||
)
|
||
]
|
||
}
|