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

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