Files
ios-mail/Modules/App/Sources/UI/Views/AsyncSenderImageView.swift
T
2025-04-29 11:07:38 +02:00

107 lines
3.3 KiB
Swift

// Copyright (c) 2024 Proton Technologies AG
//
// This file is part of Proton Mail.
//
// Proton Mail is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail. If not, see https://www.gnu.org/licenses/.
import SwiftUI
struct AsyncSenderImageView<Content>: View where Content: View {
@Environment(\.colorScheme) var colorScheme: ColorScheme
@StateObject private var loader: SenderImageLoader
@ViewBuilder private var content: (SenderImageLoader.Image) -> Content
private let senderImageParams: SenderImageDataParameters
init(
senderImageParams: SenderImageDataParameters,
@ViewBuilder content: @escaping (SenderImageLoader.Image) -> Content
) {
_loader = StateObject(wrappedValue: .init(address: senderImageParams.address))
self.senderImageParams = senderImageParams
self.content = content
}
var body: some View {
content(loader.senderImage)
.onAppear() {
Task {
await loader.loadImage(for: senderImageParams, colorScheme: colorScheme)
}
}
}
}
final class SenderImageLoader: ObservableObject {
enum Image {
case empty
case image(_ image: UIImage)
}
@Published var senderImage: Image
private let dependencies: Dependencies
init(address: String, dependencies: Dependencies = .init()) {
self.dependencies = dependencies
if let image = self.dependencies.cache.object(for: address) {
senderImage = .image(image)
} else {
senderImage = .empty
}
}
@MainActor
func loadImage(for params: SenderImageDataParameters, colorScheme: ColorScheme) async {
guard let image = await dependencies.imageDataSource.senderImage(for: params, colorScheme: colorScheme) else {
senderImage = .empty
return
}
senderImage = .image(image)
}
}
extension SenderImageLoader {
struct Dependencies {
let imageDataSource: SenderImageDataSource
let cache: MemoryCache<String, UIImage>
init(
imageDataSource: SenderImageDataSource = SenderImageAPIDataSource.shared,
cache: MemoryCache<String, UIImage> = Caches.senderImageCache
) {
self.imageDataSource = imageDataSource
self.cache = cache
}
}
}
#Preview {
class DummyDataSource: SenderImageDataSource {
func senderImage(for params: SenderImageDataParameters, colorScheme: ColorScheme) async -> UIImage? {
UIImage(resource: PreviewData.senderImage)
}
}
return AsyncSenderImageView(senderImageParams: .init()) { image in
switch image {
case .empty:
Text("EMPTY".notLocalized)
case .image(let img):
Image(uiImage: img)
}
}
}