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.
58 lines
1.9 KiB
Swift
58 lines
1.9 KiB
Swift
import Foundation
|
|
import UserNotifications
|
|
|
|
/// Schedules local repeating notifications for Gassi-Zeiten so reminders work
|
|
/// even when the app is offline. One UNCalendarNotificationTrigger per weekday.
|
|
@MainActor
|
|
enum GassiZeitenScheduler {
|
|
static func reschedule(_ z: GassiZeit) async {
|
|
cancel(forId: z.id)
|
|
guard z.aktiv != 0 else { return }
|
|
|
|
let parts = z.uhrzeit.split(separator: ":")
|
|
guard parts.count == 2,
|
|
let h = Int(parts[0]),
|
|
let m = Int(parts[1])
|
|
else { return }
|
|
|
|
let content = UNMutableNotificationContent()
|
|
content.title = "Gassi-Zeit"
|
|
content.body = z.notiz?.isEmpty == false ? z.notiz! : "Zeit für deine Gassi-Runde."
|
|
content.sound = .default
|
|
|
|
let center = UNUserNotificationCenter.current()
|
|
for wt in z.wochentage {
|
|
let weekday = weekdayNumber(for: wt)
|
|
guard weekday > 0 else { continue }
|
|
var comps = DateComponents()
|
|
comps.weekday = weekday
|
|
comps.hour = h
|
|
comps.minute = m
|
|
let trigger = UNCalendarNotificationTrigger(dateMatching: comps, repeats: true)
|
|
let request = UNNotificationRequest(
|
|
identifier: "gz-\(z.id)-\(weekday)",
|
|
content: content,
|
|
trigger: trigger
|
|
)
|
|
try? await center.add(request)
|
|
}
|
|
}
|
|
|
|
static func cancel(forId id: Int) {
|
|
let ids = (1...7).map { "gz-\(id)-\($0)" }
|
|
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ids)
|
|
}
|
|
|
|
private static func weekdayNumber(for code: String) -> Int {
|
|
switch code.lowercased() {
|
|
case "so": return 1
|
|
case "mo": return 2
|
|
case "di": return 3
|
|
case "mi": return 4
|
|
case "do": return 5
|
|
case "fr": return 6
|
|
case "sa": return 7
|
|
default: return 0
|
|
}
|
|
}
|
|
}
|