RTSDA-iOS/Views/CachedAsyncImage.swift
RTSDA 00679f927c docs: Update README for v2.0 release and fix git remote URL
- Comprehensive README update documenting v2.0 architectural changes
- Updated git remote to ssh://rockvilleav@git.rockvilletollandsda.church:10443/RTSDA/RTSDA-iOS.git
- Documented unified ChurchService and 60% code reduction
- Added new features: Home Feed, responsive reading, enhanced UI
- Corrected license information (GPL v3 with church content copyright)
- Updated build instructions and technical stack details
2025-08-16 18:41:51 -04:00

122 lines
4.1 KiB
Swift

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<Content: View, Placeholder: View>: 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<EmptyView, EmptyView> {
init(url: URL?, scale: CGFloat = 1.0) {
self.init(
url: url,
scale: scale,
content: { $0 },
placeholder: { ProgressView() }
)
}
}