import SwiftUI struct WallpaperItem: View { let wallpaper: Wallpaper let action: () -> Void @ObservedObject var favoritesManager = FavoritesManager.shared @State private var retryID = UUID() @State private var attempt = 0 @State private var isHovering = false // Transient thumbnail failures (flaky network) are common; retry a few // times with growing backoff before giving up quietly. private let maxAttempts = 4 var body: some View { Button(action: action) { AsyncImage(url: URL(string: wallpaper.thumbs.small)) { phase in switch phase { case .empty: Rectangle() .fill(Color.gray.opacity(0.2)) .overlay(ProgressView()) case .success(let image): image .resizable() .aspectRatio(contentMode: .fill) case .failure: Rectangle() .fill(Color.gray.opacity(0.2)) .overlay( attempt < maxAttempts ? AnyView(ProgressView()) : AnyView(Image(systemName: "photo") .foregroundColor(.gray.opacity(0.6))) ) .task { guard attempt < maxAttempts else { return } // 0.8s, 1.6s, 2.4s, 3.2s let delay = UInt64(attempt + 1) * 800_000_000 try? await Task.sleep(nanoseconds: delay) guard !Task.isCancelled else { return } attempt += 1 retryID = UUID() } @unknown default: EmptyView() } } .id(retryID) .frame(height: 100) .clipped() .cornerRadius(8) .overlay( RoundedRectangle(cornerRadius: 8) .stroke(isHovering ? Color.accentColor : Color.clear, lineWidth: 2) ) .shadow(radius: isHovering ? 4 : 0) .overlay( // Favorite Button Group { if isHovering || favoritesManager.isFavorite(wallpaper) { Button { favoritesManager.toggleFavorite(wallpaper) } label: { Image(systemName: favoritesManager.isFavorite(wallpaper) ? "heart.fill" : "heart") .foregroundColor(favoritesManager.isFavorite(wallpaper) ? .red : .white) .shadow(radius: 2) .font(.system(size: 16, weight: .bold)) .padding(6) .background(Color.black.opacity(0.3)) .clipShape(Circle()) } .buttonStyle(.plain) .padding(6) .transition(.opacity) } }, alignment: .topTrailing ) .scaleEffect(isHovering ? 1.02 : 1.0) .animation(.spring(response: 0.3), value: isHovering) } .buttonStyle(.plain) .onHover { isHovering = $0 } .help("\(wallpaper.resolution)\n\(wallpaper.purity)") } }