banyaro-ios/BanYaroGo/Views/ErsteHilfeView.swift
rene 68b084be97 Sechs Offline-Features: Erste Hilfe, Ausgaben, Wetter, Gassi-Zeiten, Giftköder, Verlorene
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.
2026-05-30 12:03:24 +02:00

270 lines
11 KiB
Swift
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 Biss­wunden (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"
]
)
]
}