1import SwiftUI23struct WallpaperItem: View {4 let wallpaper: Wallpaper5 let action: () -> Void6 @ObservedObject var favoritesManager = FavoritesManager.shared7 @State private var retryID = UUID()8 @State private var attempt = 09 @State private var isHovering = false1011 12 13 let maxAttempts: Int14 let retryDelayNanoseconds: UInt641516 init(17 wallpaper: Wallpaper, action: @escaping () -> Void, attempt: Int = 0,18 maxAttempts: Int = 4, retryDelayNanoseconds: UInt64 = 800_000_00019 ) {20 self.wallpaper = wallpaper21 self.action = action22 self.maxAttempts = maxAttempts23 self.retryDelayNanoseconds = retryDelayNanoseconds24 _attempt = State(initialValue: attempt)25 }2627 var body: some View {28 Button(action: action) {29 AsyncImage(url: URL(string: wallpaper.thumbs.small)) { thumbnail(for: $0) }30 .id(retryID)31 .frame(height: 100)32 .clipped()33 .cornerRadius(8)34 .overlay(35 RoundedRectangle(cornerRadius: 8)36 .stroke(isHovering ? Color.accentColor : Color.clear, lineWidth: 2)37 )38 .shadow(radius: isHovering ? 4 : 0)39 .overlay(40 41 Group {42 if isHovering || favoritesManager.isFavorite(wallpaper) {43 Button(action: toggleFavorite) {44 Image(systemName: favoritesManager.isFavorite(wallpaper) ? "heart.fill" : "heart")45 .foregroundColor(favoritesManager.isFavorite(wallpaper) ? .red : .white)46 .shadow(radius: 2)47 .font(.system(size: 16, weight: .bold))48 .padding(6)49 .background(Color.black.opacity(0.3))50 .clipShape(Circle())51 }52 .buttonStyle(.plain)53 .padding(6)54 .transition(.opacity)55 }56 },57 alignment: .topTrailing58 )59 .scaleEffect(isHovering ? 1.02 : 1.0)60 .animation(.spring(response: 0.3), value: isHovering)61 }62 .buttonStyle(.plain)63 .onHover { isHovering = $0 }64 .help("\(wallpaper.resolution)\n\(wallpaper.purity)")65 }6667 func thumbnail(for phase: AsyncImagePhase) -> AnyView {68 if case .success(let image) = phase {69 return AnyView(image.resizable().aspectRatio(contentMode: .fill))70 }71 if case .failure = phase {72 return AnyView(73 Rectangle().fill(Color.gray.opacity(0.2)).overlay(74 attempt < maxAttempts75 ? AnyView(ProgressView())76 : AnyView(Image(systemName: "photo").foregroundColor(.gray.opacity(0.6)))77 ).task { await retryAfterFailure() })78 }79 return AnyView(Rectangle().fill(Color.gray.opacity(0.2)).overlay(ProgressView()))80 }8182 func retryAfterFailure() async {83 guard attempt < maxAttempts else { return }84 let delay = UInt64(attempt + 1) * retryDelayNanoseconds85 try? await Task.sleep(nanoseconds: delay)86 guard !Task.isCancelled else { return }87 attempt += 188 retryID = UUID()89 }9091 func toggleFavorite() { favoritesManager.toggleFavorite(wallpaper) }92}