WallpaperItem.swift

3.5 KB · raw

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    // Transient thumbnail failures (flaky network) are common; retry a few12    // times with growing backoff before giving up quietly.13    private let maxAttempts = 41415    var body: some View {16        Button(action: action) {17            AsyncImage(url: URL(string: wallpaper.thumbs.small)) { phase in18                switch phase {19                case .empty:20                    Rectangle()21                        .fill(Color.gray.opacity(0.2))22                        .overlay(ProgressView())23                case .success(let image):24                    image25                        .resizable()26                        .aspectRatio(contentMode: .fill)27                case .failure:28                    Rectangle()29                        .fill(Color.gray.opacity(0.2))30                        .overlay(31                            attempt < maxAttempts32                                ? AnyView(ProgressView())33                                : AnyView(Image(systemName: "photo")34                                    .foregroundColor(.gray.opacity(0.6)))35                        )36                        .task {37                            guard attempt < maxAttempts else { return }38                            // 0.8s, 1.6s, 2.4s, 3.2s39                            let delay = UInt64(attempt + 1) * 800_000_00040                            try? await Task.sleep(nanoseconds: delay)41                            guard !Task.isCancelled else { return }42                            attempt += 143                            retryID = UUID()44                        }45                @unknown default:46                    EmptyView()47                }48            }49            .id(retryID)50            .frame(height: 100)51            .clipped()52            .cornerRadius(8)53            .overlay(54                RoundedRectangle(cornerRadius: 8)55                    .stroke(isHovering ? Color.accentColor : Color.clear, lineWidth: 2)56            )57            .shadow(radius: isHovering ? 4 : 0)58            .overlay(59                // Favorite Button60                Group {61                    if isHovering || favoritesManager.isFavorite(wallpaper) {62                        Button {63                            favoritesManager.toggleFavorite(wallpaper)64                        } label: {65                            Image(systemName: favoritesManager.isFavorite(wallpaper) ? "heart.fill" : "heart")66                                .foregroundColor(favoritesManager.isFavorite(wallpaper) ? .red : .white)67                                .shadow(radius: 2)68                                .font(.system(size: 16, weight: .bold))69                                .padding(6)70                                .background(Color.black.opacity(0.3))71                                .clipShape(Circle())72                        }73                        .buttonStyle(.plain)74                        .padding(6)75                        .transition(.opacity)76                    }77                },78                alignment: .topTrailing79            )80            .scaleEffect(isHovering ? 1.02 : 1.0)81            .animation(.spring(response: 0.3), value: isHovering)82        }83        .buttonStyle(.plain)84        .onHover { isHovering = $0 }85        .help("\(wallpaper.resolution)\n\(wallpaper.purity)")86    }87}