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).
This commit is contained in:
parent
9e51f3910e
commit
a2646a18ef
16 changed files with 769 additions and 199 deletions
|
|
@ -1,7 +1,9 @@
|
|||
import SwiftUI
|
||||
import SwiftData
|
||||
|
||||
struct TagebuchView: View {
|
||||
@Environment(ActiveDogStore.self) private var activeDog
|
||||
@Environment(\.modelContext) private var ctx
|
||||
|
||||
@State private var entries: [DiaryEntry] = []
|
||||
@State private var isLoading = false
|
||||
|
|
@ -66,16 +68,26 @@ struct TagebuchView: View {
|
|||
|
||||
private func load() async {
|
||||
guard let dog = activeDog.activeDog else { return }
|
||||
isLoading = true
|
||||
// 1) Cache-first: sofort anzeigen (auch offline)
|
||||
if entries.isEmpty {
|
||||
let cached = OfflineCache.cachedDiary(dogId: dog.id, in: ctx)
|
||||
if !cached.isEmpty { entries = cached }
|
||||
}
|
||||
isLoading = entries.isEmpty
|
||||
errorMessage = nil
|
||||
defer { isLoading = false }
|
||||
// 2) Netzwerk → Cache aktualisieren; bei Fehler bleibt der Cache stehen
|
||||
do {
|
||||
entries = try await APIClient.shared.get("/api/dogs/\(dog.id)/diary?limit=50")
|
||||
let fetched: [DiaryEntry] = try await APIClient.shared.get("/api/dogs/\(dog.id)/diary?limit=50")
|
||||
entries = fetched
|
||||
OfflineCache.upsertDiary(fetched, in: ctx)
|
||||
let paths = fetched.flatMap { ($0.mediaItems ?? []).map(\.url) }
|
||||
Task { await OfflineCache.prefetchImages(paths: paths, in: ctx) }
|
||||
} catch let decodingError as DecodingError {
|
||||
errorMessage = Self.describe(decodingError)
|
||||
if entries.isEmpty { errorMessage = Self.describe(decodingError) }
|
||||
print("Tagebuch decode error: \(decodingError)")
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
if entries.isEmpty { errorMessage = error.localizedDescription }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -131,11 +143,10 @@ private struct DiaryRow: View {
|
|||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(spacing: 6) {
|
||||
ForEach(media) { m in
|
||||
AsyncImage(url: URL(string: "https://banyaro.app\(m.url)")) { phase in
|
||||
switch phase {
|
||||
case .success(let img): img.resizable().scaledToFill()
|
||||
default: Rectangle().fill(.gray.opacity(0.15))
|
||||
}
|
||||
CachedAsyncImage(path: m.url) { img in
|
||||
img.resizable().scaledToFill()
|
||||
} placeholder: {
|
||||
Rectangle().fill(.gray.opacity(0.15))
|
||||
}
|
||||
.frame(width: 80, height: 80)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue