Statistik weg, Mehr entrümpelt, Gassi-Zeiten korrekt gerahmt

- Statistik-Tab raus (für Go-Companion nicht relevant)
- Mehr-Duplikate raus: Meine Hunde, Tagebuch, Wetter, Erste Hilfe sitzen
  bereits auf Heim als Quick-Action bzw. im Dog-Picker
- Im PWA ist 'Gassi' der social walks-Bereich (walks.py) und 'Stamm-Gassi-
  Zeiten' nur ein Tab darin (Community-Pool, gassi_zeiten.py). Meine
  Implementierung als 'tägliche Erinnerungen' war fachlich falsch:
  + Mehr-Eintrag heißt jetzt 'Stamm-Gassi-Zeiten'
  + ContentUnavailableView + Footer erklären die Community-Komponente
  + Pitch-Karte unterscheidet jetzt klar: 'Gassi-Treffen' (sich verabreden)
    und 'Stamm-Gassi-Zeiten' (regelmäßige Runden + Pool)
  + 'Hunde-Orte' getrennt als eigener Pitch-Punkt
This commit is contained in:
rene 2026-05-30 13:04:35 +02:00
parent 5dc76db8cb
commit 0867a2171f
5 changed files with 26 additions and 30 deletions

View file

@ -15,7 +15,7 @@ struct GassiZeitenView: View {
var body: some View { var body: some View {
content content
.navigationTitle("Gassi-Zeiten") .navigationTitle("Stamm-Gassi-Zeiten")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar { .toolbar {
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {
@ -41,9 +41,9 @@ struct GassiZeitenView: View {
ContentUnavailableView("Konnte nicht laden", systemImage: "wifi.slash", description: Text(errorMessage)) ContentUnavailableView("Konnte nicht laden", systemImage: "wifi.slash", description: Text(errorMessage))
} else if items.isEmpty { } else if items.isEmpty {
ContentUnavailableView( ContentUnavailableView(
"Noch keine Gassi-Zeiten", "Noch keine Stamm-Gassi-Zeiten",
systemImage: "alarm", systemImage: "alarm",
description: Text("Tippe oben rechts auf +, um regelmäßige Erinnerungen zu setzen.") description: Text("Trag deine regelmäßigen Gassi-Runden ein — du bekommst lokale Erinnerungen, und in der banyaro.app sehen andere, wann ihr euch verabreden könnt.")
) )
} else { } else {
List { List {
@ -52,7 +52,7 @@ struct GassiZeitenView: View {
row(z) row(z)
} }
} footer: { } footer: {
Text("Erinnerungen kommen lokal vom iPhone — auch ohne Internet.") Text("Deine Zeiten landen auch im Stamm-Gassi-Pool der Community (sichtbar in banyaro.app → Gassi). Erinnerungen kommen lokal vom iPhone — auch ohne Internet.")
.font(.caption2) .font(.caption2)
.foregroundStyle(.tertiary) .foregroundStyle(.tertiary)
} }

View file

@ -73,8 +73,9 @@ struct LoginView: View {
VStack(alignment: .leading, spacing: 14) { VStack(alignment: .leading, spacing: 14) {
Divider() Divider()
feature(icon: "map.fill", title: "Gassi-Touren aufzeichnen", subtitle: "GPS-Tracking auch im Hintergrund — mit Pause, Live Activity und HealthKit-Sync.") feature(icon: "map.fill", title: "Gassi-Touren aufzeichnen", subtitle: "GPS-Tracking auch im Hintergrund — mit Pause, Live Activity und HealthKit-Sync.")
feature(icon: "alarm.fill", title: "Gassi-Zeiten", subtitle: "Tägliche Erinnerungen, damit keine Runde vergessen wird.") feature(icon: "person.2.fill", title: "Gassi-Treffen", subtitle: "Triff andere Hundebesitzer in deiner Nähe — verabrede dich für gemeinsame Runden.")
feature(icon: "person.2.fill", title: "Hunde-Community", subtitle: "Gassi-Treffen, Tierärzte und Orte in deiner Nähe.") feature(icon: "alarm.fill", title: "Stamm-Gassi-Zeiten", subtitle: "Trag ein, wann du regelmäßig läufst — bekommst Erinnerungen und triffst Gleichgesinnte.")
feature(icon: "mappin.and.ellipse", title: "Hunde-Orte", subtitle: "Tierärzte, Hundeparks und gute Plätze in deiner Nähe.")
feature(icon: "book.fill", title: "Tagebuch & Impfpass", subtitle: "Alles rund um deinen Hund an einem Ort.") feature(icon: "book.fill", title: "Tagebuch & Impfpass", subtitle: "Alles rund um deinen Hund an einem Ort.")
feature(icon: "rosette", title: "Verifizierte Züchter", subtitle: "Züchter-Profile, aktuelle Würfe und Welpen-Vermittlung — kein Hinterhof.") feature(icon: "rosette", title: "Verifizierte Züchter", subtitle: "Züchter-Profile, aktuelle Würfe und Welpen-Vermittlung — kein Hinterhof.")
feature(icon: "exclamationmark.shield.fill", title: "Giftköder-Alarm", subtitle: "Warnungen aus deiner Region direkt aufs iPhone.") feature(icon: "exclamationmark.shield.fill", title: "Giftköder-Alarm", subtitle: "Warnungen aus deiner Region direkt aufs iPhone.")

View file

@ -14,9 +14,6 @@ struct MainTabView: View {
TrackingView() TrackingView()
.tabItem { Label("Aufnehmen", systemImage: "figure.walk") } .tabItem { Label("Aufnehmen", systemImage: "figure.walk") }
StatisticsView()
.tabItem { Label("Statistik", systemImage: "chart.bar.fill") }
SettingsView() SettingsView()
.tabItem { Label("Mehr", systemImage: "person.crop.circle") } .tabItem { Label("Mehr", systemImage: "person.crop.circle") }
} }

View file

@ -28,30 +28,10 @@ struct SettingsView: View {
} }
Section("Hund & Alltag") { Section("Hund & Alltag") {
NavigationLink {
DogsListView()
} label: {
Label("Meine Hunde", systemImage: "pawprint.fill")
}
NavigationLink {
TagebuchView()
} label: {
Label("Tagebuch", systemImage: "book.fill")
}
NavigationLink {
ErsteHilfeView()
} label: {
Label("Erste Hilfe", systemImage: "cross.case.fill")
}
NavigationLink {
WetterView()
} label: {
Label("Wetter", systemImage: "cloud.sun.fill")
}
NavigationLink { NavigationLink {
GassiZeitenView() GassiZeitenView()
} label: { } label: {
Label("Gassi-Zeiten", systemImage: "alarm.fill") Label("Stamm-Gassi-Zeiten", systemImage: "alarm.fill")
} }
NavigationLink { NavigationLink {
GiftkoederView() GiftkoederView()

View file

@ -65,10 +65,28 @@ struct TagebuchView: View {
defer { isLoading = false } defer { isLoading = false }
do { do {
entries = try await APIClient.shared.get("/api/dogs/\(dog.id)/diary?limit=50") entries = try await APIClient.shared.get("/api/dogs/\(dog.id)/diary?limit=50")
} catch let decodingError as DecodingError {
errorMessage = Self.describe(decodingError)
print("Tagebuch decode error: \(decodingError)")
} catch { } catch {
errorMessage = error.localizedDescription errorMessage = error.localizedDescription
} }
} }
private static func describe(_ error: DecodingError) -> String {
switch error {
case .typeMismatch(let type, let ctx):
return "Feldtyp falsch (\(type)) bei „\(ctx.codingPath.map(\.stringValue).joined(separator: "."))"
case .valueNotFound(let type, let ctx):
return "Feld fehlt (\(type)) bei „\(ctx.codingPath.map(\.stringValue).joined(separator: "."))"
case .keyNotFound(let key, let ctx):
return "Key fehlt: \(key.stringValue) bei „\(ctx.codingPath.map(\.stringValue).joined(separator: "."))"
case .dataCorrupted(let ctx):
return "Datenfehler bei „\(ctx.codingPath.map(\.stringValue).joined(separator: ".")): \(ctx.debugDescription)"
@unknown default:
return String(describing: error)
}
}
} }
private struct DiaryRow: View { private struct DiaryRow: View {