banyaro-ios/BanYaroGo/Support/GassiZeitenScheduler.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

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
}
}
}