banyaro-ios/BanYaroGo/BanYaroGoApp.swift
rene a2646a18ef 1.1: Offline-Cache + Outbox für Touren/Tagebuch, WeatherKit-Fix, Aufräumen
App-Review-Fix (Guideline 2.1 WeatherKit):
- OneShotLocation: deterministisches async resolve() mit 10s-Timeout statt
  onChange-Lauschen; WetterView lädt bei fehlendem Standort einen Berlin-Fallback
  → kein ewiges Hängen bei "Hole Standort…", WeatherKit ist immer sichtbar.

Offline-Lesen (SwiftData):
- CachedRoute/CachedDiaryEntry/CachedImage + CachedAsyncImage: Touren, Tagebuch
  und Fotos werden cache-first geladen und sind offline verfügbar.
- Cache wird bei Logout/401 geleert (RootView), kein Durchschimmern fremder User.

Offline-Speichern (Outbox):
- PendingRoute/PendingRoutePhoto: Tour inkl. unterwegs hinzugefügter Fotos wird
  offline lokal gesichert und automatisch hochgeladen (Touren-Tab + App-Start).
- Touren-Liste zeigt offline gesicherte Touren mit "wird hochgeladen"-Badge.

FinishWalkSheet:
- Dismiss-Schutz: Speichern-Dialog lässt sich nicht mehr wegwischen — eine
  aufgezeichnete Tour geht nicht mehr durch Runterwischen verloren.

Wetter:
- Ortslabel (Reverse-Geocoding; Fallback "Berlin · Näherung").
- Saubere Offline-Meldung statt rohem networkError.

Aufräumen:
- Doppeltes "Gassi-Treffen" im Mehr-Tab entfernt.
- Veraltete Phase-1/2-Texte neu getextet.
- Tote DogsListView gelöscht (Hund-Wechsel läuft über den Heim-Picker).
2026-06-02 19:37:30 +02:00

55 lines
1.7 KiB
Swift

import SwiftUI
import SwiftData
@main
struct BanYaroGoApp: App {
@State private var auth = AuthSession()
@State private var activeDog = ActiveDogStore()
@State private var pendingGPX: GPXTrack?
var body: some Scene {
WindowGroup {
RootView()
.environment(auth)
.environment(activeDog)
.onOpenURL { url in
handleIncoming(url: url)
}
.sheet(item: Binding(
get: { pendingGPX.map { TrackBox(track: $0) } },
set: { pendingGPX = $0?.track }
)) { box in
GPXImportSheet(track: box.track) {
pendingGPX = nil
}
}
}
.modelContainer(for: [
ActiveWalk.self, PhotoLocation.self,
CachedRoute.self, CachedDiaryEntry.self, CachedImage.self,
PendingRoute.self, PendingRoutePhoto.self
])
}
private func handleIncoming(url: URL) {
// Nur GPX akzeptieren andere URLs (Deep-Links) sind anderweitig verdrahtet.
guard url.pathExtension.lowercased() == "gpx" else { return }
let didAccess = url.startAccessingSecurityScopedResource()
defer { if didAccess { url.stopAccessingSecurityScopedResource() } }
do {
let data = try Data(contentsOf: url)
let track = try GPXParser.parse(data: data)
pendingGPX = track
} catch {
print("GPX-Import fehlgeschlagen: \(error)")
}
}
}
/// Hilfs-Wrapper, damit GPXTrack als `Identifiable` in `.sheet(item:)` taugt.
private struct TrackBox: Identifiable {
let id = UUID()
let track: GPXTrack
}