import SwiftUI struct LocalView: View { @ObservedObject var appState: AppState @ObservedObject private var localManager = LocalWallpapersManager.shared @State private var selectedWallpaper: LocalWallpaper? @State private var showingDeleteConfirmation = false @State private var wallpaperToDelete: LocalWallpaper? var body: some View { VStack(spacing: 0) { // Header HStack { Text("Local Wallpapers") .font(.headline) Spacer() if !localManager.wallpapers.isEmpty { Text("\(localManager.wallpapers.count) items") .font(.caption) .foregroundColor(.secondary) } Button { localManager.scanFolder(path: appState.saveFolderPath) } label: { Image(systemName: "arrow.clockwise") } .buttonStyle(.plain) .disabled(localManager.isLoading) } .padding() Divider() // Content if localManager.isLoading && localManager.wallpapers.isEmpty { LoadingState() } else if !appState.saveToFolderEnabled { EmptyState( icon: "folder.badge.questionmark", text: "Enable 'Save wallpapers locally' in Settings to view local wallpapers" ) } else if localManager.wallpapers.isEmpty { EmptyState( icon: "photo.on.rectangle.angled", text: "No wallpapers found in your save folder" ) } else { LocalWallpaperGrid( wallpapers: localManager.wallpapers, onSetWallpaper: { wallpaper in Task { await appState.setLocalWallpaper(wallpaper) } }, onDeleteWallpaper: { wallpaper in wallpaperToDelete = wallpaper showingDeleteConfirmation = true } ) } } .alert("Delete Wallpaper?", isPresented: $showingDeleteConfirmation, presenting: wallpaperToDelete) { wallpaper in Button("Cancel", role: .cancel) {} Button("Delete", role: .destructive) { localManager.deleteWallpaper(wallpaper) } } message: { wallpaper in Text("This will permanently delete '\(wallpaper.filename)' from your save folder.") } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) .onAppear { if appState.saveToFolderEnabled { localManager.scanFolder(path: appState.saveFolderPath) } } } } // MARK: - Local Wallpaper Grid struct LocalWallpaperGrid: View { let wallpapers: [LocalWallpaper] let onSetWallpaper: (LocalWallpaper) -> Void let onDeleteWallpaper: (LocalWallpaper) -> Void var body: some View { ScrollView { LazyVGrid(columns: [GridItem(.adaptive(minimum: 140), spacing: 12)], spacing: 12) { ForEach(wallpapers) { wallpaper in LocalWallpaperItem( wallpaper: wallpaper, onSet: { onSetWallpaper(wallpaper) }, onDelete: { onDeleteWallpaper(wallpaper) } ) } } .padding(12) } } } // MARK: - Local Wallpaper Item struct LocalWallpaperItem: View { let wallpaper: LocalWallpaper let onSet: () -> Void let onDelete: () -> Void @State private var isHovering = false @State private var image: NSImage? @State private var isLoadingImage = true var body: some View { Button(action: onSet) { ZStack { if let image = image { Image(nsImage: image) .resizable() .aspectRatio(contentMode: .fill) .clipped() } else if isLoadingImage { Rectangle() .fill(Color.gray.opacity(0.2)) .overlay(ProgressView()) } else { Rectangle() .fill(Color.gray.opacity(0.1)) .overlay( VStack { Image(systemName: "photo") .foregroundColor(.gray) Text(wallpaper.filename) .font(.caption2) .foregroundColor(.secondary) .lineLimit(1) } ) } } .frame(maxWidth: .infinity, minHeight: 100, maxHeight: 100) .cornerRadius(8) .overlay( RoundedRectangle(cornerRadius: 8) .stroke(isHovering ? Color.accentColor : Color.clear, lineWidth: 2) ) .shadow(radius: isHovering ? 4 : 0) .overlay( // Action Buttons HStack { // Delete button Button { onDelete() } label: { Image(systemName: "trash") .foregroundColor(.white) .shadow(radius: 2) .font(.system(size: 14, weight: .bold)) .padding(6) .background(Color.red.opacity(0.7)) .clipShape(Circle()) } .buttonStyle(.plain) .padding(6) Spacer() // Resolution badge Text(wallpaper.resolution) .font(.caption2) .padding(.horizontal, 6) .padding(.vertical, 2) .background(Color.black.opacity(0.6)) .foregroundColor(.white) .cornerRadius(4) .padding(6) } .opacity(isHovering ? 1 : 0) .animation(.easeInOut(duration: 0.15), value: isHovering), alignment: .top ) .scaleEffect(isHovering ? 1.02 : 1.0) .animation(.spring(response: 0.3), value: isHovering) } .buttonStyle(.plain) .onHover { isHovering = $0 } .help("\(wallpaper.resolution) • \(formatFileSize(wallpaper.fileSize))") .onAppear { loadImage() } } private func loadImage() { isLoadingImage = true DispatchQueue.global(qos: .userInitiated).async { let loadedImage = NSImage(contentsOf: self.wallpaper.url) DispatchQueue.main.async { self.image = loadedImage self.isLoadingImage = false } } } private func formatFileSize(_ bytes: Int64) -> String { let formatter = ByteCountFormatter() formatter.allowedUnits = [.useKB, .useMB, .useGB] formatter.countStyle = .file return formatter.string(fromByteCount: bytes) } } // MARK: - Loading State struct LoadingState: View { var body: some View { VStack(spacing: 16) { ProgressView() Text("Loading wallpapers...") .foregroundColor(.secondary) } .frame(maxWidth: .infinity, maxHeight: .infinity) } }