LocalView.swift

7.9 KB · raw

1import SwiftUI23struct LocalView: View {4    @ObservedObject var appState: AppState5    @ObservedObject private var localManager = LocalWallpapersManager.shared6    @State private var selectedWallpaper: LocalWallpaper?7    @State private var showingDeleteConfirmation = false8    @State private var wallpaperToDelete: LocalWallpaper?9    10    var body: some View {11        VStack(spacing: 0) {12            // Header13            HStack {14                Text("Local Wallpapers")15                    .font(.headline)16                17                Spacer()18                19                if !localManager.wallpapers.isEmpty {20                    Text("\(localManager.wallpapers.count) items")21                        .font(.caption)22                        .foregroundColor(.secondary)23                }24                25                Button {26                    localManager.scanFolder(path: appState.saveFolderPath)27                } label: {28                    Image(systemName: "arrow.clockwise")29                }30                .buttonStyle(.plain)31                .disabled(localManager.isLoading)32            }33            .padding()34            35            Divider()36            37            // Content38            if localManager.isLoading && localManager.wallpapers.isEmpty {39                LoadingState()40            } else if !appState.saveToFolderEnabled {41                EmptyState(42                    icon: "folder.badge.questionmark",43                    text: "Enable 'Save wallpapers locally' in Settings to view local wallpapers"44                )45            } else if localManager.wallpapers.isEmpty {46                EmptyState(47                    icon: "photo.on.rectangle.angled",48                    text: "No wallpapers found in your save folder"49                )50            } else {51                LocalWallpaperGrid(52                    wallpapers: localManager.wallpapers,53                    onSetWallpaper: { wallpaper in54                        Task {55                            await appState.setLocalWallpaper(wallpaper)56                        }57                    },58                    onDeleteWallpaper: { wallpaper in59                        wallpaperToDelete = wallpaper60                        showingDeleteConfirmation = true61                    }62                )63            }64        }65        .alert("Delete Wallpaper?", isPresented: $showingDeleteConfirmation, presenting: wallpaperToDelete) { wallpaper in66            Button("Cancel", role: .cancel) {}67            Button("Delete", role: .destructive) {68                localManager.deleteWallpaper(wallpaper)69            }70        } message: { wallpaper in71            Text("This will permanently delete '\(wallpaper.filename)' from your save folder.")72        }73        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)74        .onAppear {75            if appState.saveToFolderEnabled {76                localManager.scanFolder(path: appState.saveFolderPath)77            }78        }79    }80}8182// MARK: - Local Wallpaper Grid8384struct LocalWallpaperGrid: View {85    let wallpapers: [LocalWallpaper]86    let onSetWallpaper: (LocalWallpaper) -> Void87    let onDeleteWallpaper: (LocalWallpaper) -> Void88    89    var body: some View {90        ScrollView {91            LazyVGrid(columns: [GridItem(.adaptive(minimum: 140), spacing: 12)], spacing: 12) {92                ForEach(wallpapers) { wallpaper in93                    LocalWallpaperItem(94                        wallpaper: wallpaper,95                        onSet: { onSetWallpaper(wallpaper) },96                        onDelete: { onDeleteWallpaper(wallpaper) }97                    )98                }99            }100            .padding(12)101        }102    }103}104105// MARK: - Local Wallpaper Item106107struct LocalWallpaperItem: View {108    let wallpaper: LocalWallpaper109    let onSet: () -> Void110    let onDelete: () -> Void111    112    @State private var isHovering = false113    @State private var image: NSImage?114    @State private var isLoadingImage = true115    116    var body: some View {117        Button(action: onSet) {118            ZStack {119                if let image = image {120                    Image(nsImage: image)121                        .resizable()122                        .aspectRatio(contentMode: .fill)123                        .clipped()124                } else if isLoadingImage {125                    Rectangle()126                        .fill(Color.gray.opacity(0.2))127                        .overlay(ProgressView())128                } else {129                    Rectangle()130                        .fill(Color.gray.opacity(0.1))131                        .overlay(132                            VStack {133                                Image(systemName: "photo")134                                    .foregroundColor(.gray)135                                Text(wallpaper.filename)136                                    .font(.caption2)137                                    .foregroundColor(.secondary)138                                    .lineLimit(1)139                            }140                        )141                }142            }143            .frame(maxWidth: .infinity, minHeight: 100, maxHeight: 100)144            .cornerRadius(8)145            .overlay(146                RoundedRectangle(cornerRadius: 8)147                    .stroke(isHovering ? Color.accentColor : Color.clear, lineWidth: 2)148            )149            .shadow(radius: isHovering ? 4 : 0)150            .overlay(151                // Action Buttons152                HStack {153                    // Delete button154                    Button {155                        onDelete()156                    } label: {157                        Image(systemName: "trash")158                            .foregroundColor(.white)159                            .shadow(radius: 2)160                            .font(.system(size: 14, weight: .bold))161                            .padding(6)162                            .background(Color.red.opacity(0.7))163                            .clipShape(Circle())164                    }165                    .buttonStyle(.plain)166                    .padding(6)167                    168                    Spacer()169                    170                    // Resolution badge171                    Text(wallpaper.resolution)172                        .font(.caption2)173                        .padding(.horizontal, 6)174                        .padding(.vertical, 2)175                        .background(Color.black.opacity(0.6))176                        .foregroundColor(.white)177                        .cornerRadius(4)178                        .padding(6)179                }180                .opacity(isHovering ? 1 : 0)181                .animation(.easeInOut(duration: 0.15), value: isHovering),182                alignment: .top183            )184            .scaleEffect(isHovering ? 1.02 : 1.0)185            .animation(.spring(response: 0.3), value: isHovering)186        }187        .buttonStyle(.plain)188        .onHover { isHovering = $0 }189        .help("\(wallpaper.resolution) • \(formatFileSize(wallpaper.fileSize))")190        .onAppear {191            loadImage()192        }193    }194    195    private func loadImage() {196        isLoadingImage = true197        198        DispatchQueue.global(qos: .userInitiated).async {199            let loadedImage = NSImage(contentsOf: self.wallpaper.url)200            DispatchQueue.main.async {201                self.image = loadedImage202                self.isLoadingImage = false203            }204        }205    }206    207    private func formatFileSize(_ bytes: Int64) -> String {208        let formatter = ByteCountFormatter()209        formatter.allowedUnits = [.useKB, .useMB, .useGB]210        formatter.countStyle = .file211        return formatter.string(fromByteCount: bytes)212    }213}214215// MARK: - Loading State216217struct LoadingState: View {218    var body: some View {219        VStack(spacing: 16) {220            ProgressView()221            Text("Loading wallpapers...")222                .foregroundColor(.secondary)223        }224        .frame(maxWidth: .infinity, maxHeight: .infinity)225    }226}