import SwiftUI import Foundation // Response structure for cached image data struct CachedImageResponse: Codable { let success: Bool let data: String? let contentType: String? let cached: Bool? let error: String? } struct CachedAsyncImage: View { private let url: URL? private let scale: CGFloat private let content: (Image) -> Content private let placeholder: () -> Placeholder @State private var image: UIImage? @State private var isLoading = false @State private var loadError: Error? init( url: URL?, scale: CGFloat = 1.0, @ViewBuilder content: @escaping (Image) -> Content, @ViewBuilder placeholder: @escaping () -> Placeholder ) { self.url = url self.scale = scale self.content = content self.placeholder = placeholder } var body: some View { Group { if let image = image { content(Image(uiImage: image)) } else if isLoading { placeholder() } else if loadError != nil { placeholder() } else { placeholder() } } .onAppear { loadCachedImage() } .onChange(of: url) { _, newURL in image = nil loadError = nil loadCachedImage() } } private func loadCachedImage() { guard let url = url else { return } isLoading = true loadError = nil // Use background queue for image processing DispatchQueue.global(qos: .userInitiated).async { do { // Call Rust crate function for cached image let jsonResponse = fetchCachedImageBase64(url: url.absoluteString) // Parse JSON response guard let jsonData = jsonResponse.data(using: .utf8) else { DispatchQueue.main.async { self.isLoading = false self.loadError = NSError(domain: "CachedImageError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid JSON response"]) } return } let response = try JSONDecoder().decode(CachedImageResponse.self, from: jsonData) if response.success, let base64Data = response.data { // Decode base64 to image if let imageData = Data(base64Encoded: base64Data), let uiImage = UIImage(data: imageData) { DispatchQueue.main.async { withAnimation(.easeInOut(duration: 0.3)) { self.image = uiImage } self.isLoading = false } } else { DispatchQueue.main.async { self.isLoading = false self.loadError = NSError(domain: "CachedImageError", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to decode image data"]) } } } else { DispatchQueue.main.async { self.isLoading = false self.loadError = NSError(domain: "CachedImageError", code: 3, userInfo: [NSLocalizedDescriptionKey: response.error ?? "Unknown error"]) } } } catch { DispatchQueue.main.async { self.isLoading = false self.loadError = error } } } } } // Convenience initializer for common use case extension CachedAsyncImage where Content == Image, Placeholder == ProgressView { init(url: URL?, scale: CGFloat = 1.0) { self.init( url: url, scale: scale, content: { $0 }, placeholder: { ProgressView() } ) } }