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.
73 lines
2.2 KiB
Swift
73 lines
2.2 KiB
Swift
import Foundation
|
|
import Observation
|
|
import CoreLocation
|
|
|
|
/// Asks CLLocationManager for the user's current location once. Used by
|
|
/// Wetter and Giftköder which need a position without the full tracking setup.
|
|
@Observable
|
|
@MainActor
|
|
final class OneShotLocation: NSObject, CLLocationManagerDelegate {
|
|
private let manager = CLLocationManager()
|
|
|
|
var coordinate: CLLocationCoordinate2D?
|
|
var error: String?
|
|
var isResolving: Bool = false
|
|
|
|
override init() {
|
|
super.init()
|
|
manager.delegate = self
|
|
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
|
|
}
|
|
|
|
func request() {
|
|
error = nil
|
|
isResolving = true
|
|
switch manager.authorizationStatus {
|
|
case .notDetermined:
|
|
manager.requestWhenInUseAuthorization()
|
|
case .denied, .restricted:
|
|
error = "Standortzugriff verweigert."
|
|
isResolving = false
|
|
case .authorizedWhenInUse, .authorizedAlways:
|
|
manager.requestLocation()
|
|
@unknown default:
|
|
error = "Unbekannter Standort-Status."
|
|
isResolving = false
|
|
}
|
|
}
|
|
|
|
nonisolated func locationManager(
|
|
_ manager: CLLocationManager,
|
|
didUpdateLocations locations: [CLLocation]
|
|
) {
|
|
guard let loc = locations.first else { return }
|
|
let c = loc.coordinate
|
|
Task { @MainActor in
|
|
self.coordinate = c
|
|
self.isResolving = false
|
|
}
|
|
}
|
|
|
|
nonisolated func locationManager(_ manager: CLLocationManager, didFailWithError err: Error) {
|
|
let msg = err.localizedDescription
|
|
Task { @MainActor in
|
|
self.error = msg
|
|
self.isResolving = false
|
|
}
|
|
}
|
|
|
|
nonisolated func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
|
|
let status = manager.authorizationStatus
|
|
Task { @MainActor in
|
|
switch status {
|
|
case .authorizedWhenInUse, .authorizedAlways:
|
|
manager.requestLocation()
|
|
case .denied, .restricted:
|
|
self.error = "Standortzugriff verweigert."
|
|
self.isResolving = false
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|