banyaro-ios/BanYaroGo/Views/CachedAsyncImage.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

51 lines
1.7 KiB
Swift

import SwiftUI
import UIKit
import SwiftData
/// Wie `AsyncImage`, aber offline-fähig: zeigt zuerst ein lokal gecachtes Bild
/// (CachedImage in SwiftData); fehlt es, wird es remote geladen und dabei gleich
/// in den Offline-Cache geschrieben. `path` ist der relative Medienpfad
/// (z. B. "/media/routes/x.jpg"), die Basis-URL hängt OfflineCache an.
struct CachedAsyncImage<Content: View, Placeholder: View>: View {
private let path: String?
private let content: (Image) -> Content
private let placeholder: () -> Placeholder
@Environment(\.modelContext) private var ctx
@State private var uiImage: UIImage?
init(
path: String?,
@ViewBuilder content: @escaping (Image) -> Content,
@ViewBuilder placeholder: @escaping () -> Placeholder
) {
self.path = path
self.content = content
self.placeholder = placeholder
}
var body: some View {
Group {
if let uiImage {
content(Image(uiImage: uiImage))
} else {
placeholder()
}
}
.task(id: path) { await load() }
}
private func load() async {
uiImage = nil
guard let path, !path.isEmpty else { return }
if let data = OfflineCache.imageData(path: path, in: ctx), let img = UIImage(data: data) {
uiImage = img
return
}
guard let url = URL(string: OfflineCache.mediaBase + path) else { return }
if let (data, _) = try? await URLSession.shared.data(from: url), let img = UIImage(data: data) {
uiImage = img
OfflineCache.storeImage(path: path, data: data, in: ctx)
}
}
}