78 lines
2.3 KiB
Swift
78 lines
2.3 KiB
Swift
import Foundation
|
|
import SwiftUI
|
|
import UIKit
|
|
|
|
struct LoadIconView: View {
|
|
let remoteIconURLString: String?
|
|
let fallbackSystemName: String
|
|
let fallbackColor: Color
|
|
let size: CGFloat
|
|
|
|
private var cornerRadius: CGFloat {
|
|
max(6, size / 4)
|
|
}
|
|
|
|
@State private var cachedImage: Image?
|
|
@State private var isLoading = false
|
|
@State private var hasAttemptedLoad = false
|
|
|
|
var body: some View {
|
|
Group {
|
|
if let cachedImage {
|
|
cachedImage
|
|
.resizable()
|
|
.scaledToFit()
|
|
.frame(width: size, height: size)
|
|
.clipShape(RoundedRectangle(cornerRadius: cornerRadius))
|
|
} else if let url = remoteURL, !hasAttemptedLoad {
|
|
ProgressView()
|
|
.frame(width: size, height: size)
|
|
.task(id: url) {
|
|
await loadImage(from: url)
|
|
}
|
|
} else {
|
|
fallbackView
|
|
}
|
|
}
|
|
.frame(width: size, height: size)
|
|
.onChange(of: remoteIconURLString) { oldValue, newValue in
|
|
guard oldValue != newValue else { return }
|
|
cachedImage = nil
|
|
hasAttemptedLoad = false
|
|
}
|
|
}
|
|
|
|
private var fallbackView: some View {
|
|
ZStack {
|
|
RoundedRectangle(cornerRadius: cornerRadius)
|
|
.fill(fallbackColor)
|
|
Image(systemName: fallbackSystemName.isEmpty ? "lightbulb" : fallbackSystemName)
|
|
.font(.system(size: size * 0.5))
|
|
.foregroundColor(.white)
|
|
}
|
|
}
|
|
|
|
private var remoteURL: URL? {
|
|
guard let remoteIconURLString, let url = URL(string: remoteIconURLString) else { return nil }
|
|
return url
|
|
}
|
|
|
|
private func loadImage(from url: URL) async {
|
|
guard !isLoading else { return }
|
|
isLoading = true
|
|
defer { isLoading = false }
|
|
|
|
if let uiImage = try? await IconCache.shared.image(for: url) {
|
|
await MainActor.run {
|
|
cachedImage = Image(uiImage: uiImage)
|
|
hasAttemptedLoad = true
|
|
}
|
|
} else {
|
|
await MainActor.run {
|
|
cachedImage = nil
|
|
hasAttemptedLoad = true
|
|
}
|
|
}
|
|
}
|
|
}
|