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).
55 lines
1.7 KiB
Swift
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
|
|
}
|