1import Foundation2import Combine3import AppKit45enum AppTab {6 case explore7 case local8 case favorites9 case history10 case settings11}1213@MainActor14class AppState: ObservableObject {15 16 @Published var activeTab: AppTab = .explore17 18 19 @Published var wallpapers: [Wallpaper] = []20 @Published var favoritesManager = FavoritesManager.shared21 @Published var historyManager = HistoryManager.shared22 23 24 @Published var searchText: String = ""25 @Published var selectedResolution: String = ""26 @Published var selectedRatio: String = ""27 @Published var isLoading: Bool = false28 @Published var errorMessage: String?29 30 31 @Published var canLoadMore = true32 private var currentPage = 133 private var isFetchingMore = false34 35 36 @Published var saveToFolderEnabled: Bool {37 didSet { UserDefaults.standard.set(saveToFolderEnabled, forKey: "saveToFolderEnabled") }38 }39 @Published var saveFolderPath: String {40 didSet { UserDefaults.standard.set(saveFolderPath, forKey: "saveFolderPath") }41 }42 @Published var launchAtLogin: Bool = AutostartManager.shared.isEnabled {43 didSet { AutostartManager.shared.isEnabled = launchAtLogin }44 }45 46 47 @Published var categories: (general: Bool, anime: Bool, people: Bool) = (true, true, true)48 @Published var purity: (sfw: Bool, sketchy: Bool) = (true, false)49 50 let availableResolutions = ["Any", "1920x1080", "2560x1440", "3840x2160", "5120x2880"]51 let availableRatios = ["Any", "16x9", "16x10", "21x9", "32x9", "4x3"]52 53 private let api = WallhavenAPI()54 private var cancellables = Set<AnyCancellable>()55 56 init() {57 58 self.saveToFolderEnabled = UserDefaults.standard.bool(forKey: "saveToFolderEnabled")59 if let path = UserDefaults.standard.string(forKey: "saveFolderPath") {60 self.saveFolderPath = path61 } else {62 let pictures = FileManager.default.urls(for: .picturesDirectory, in: .userDomainMask).first!63 self.saveFolderPath = pictures.appendingPathComponent("Haven").path64 }65 66 67 $searchText68 .debounce(for: .seconds(0.5), scheduler: DispatchQueue.main)69 .sink { [weak self] _ in70 Task { [weak self] in71 await self?.fetchWallpapers()72 }73 }74 .store(in: &cancellables)75 76 77 $categories78 .map { _ in () }79 .debounce(for: .seconds(0.5), scheduler: DispatchQueue.main)80 .sink { [weak self] _ in Task { await self?.fetchWallpapers() } }81 .store(in: &cancellables)82 83 $purity84 .map { _ in () }85 .debounce(for: .seconds(0.5), scheduler: DispatchQueue.main)86 .sink { [weak self] _ in Task { await self?.fetchWallpapers() } }87 .store(in: &cancellables)88 89 updateLocalWallpapersMonitoring()90 91 $saveToFolderEnabled92 .dropFirst()93 .sink { [weak self] _ in94 self?.updateLocalWallpapersMonitoring()95 }96 .store(in: &cancellables)97 98 $saveFolderPath99 .dropFirst()100 .sink { [weak self] _ in101 self?.updateLocalWallpapersMonitoring()102 }103 .store(in: &cancellables)104 }105 106 private func updateLocalWallpapersMonitoring() {107 if saveToFolderEnabled {108 LocalWallpapersManager.shared.startMonitoring(folderPath: saveFolderPath)109 } else {110 LocalWallpapersManager.shared.stopMonitoring()111 }112 }113 114 func fetchWallpapers(reset: Bool = true, refreshing: Bool = false) async {115 if reset {116 if !refreshing {117 isLoading = true118 wallpapers = []119 }120 errorMessage = nil121 currentPage = 1122 canLoadMore = true123 } else {124 guard !isFetchingMore && canLoadMore else { return }125 isFetchingMore = true126 }127 128 do {129 let resolutions = selectedResolution == "Any" || selectedResolution.isEmpty ? [] : [selectedResolution]130 let ratios = selectedRatio == "Any" || selectedRatio.isEmpty ? [] : [selectedRatio]131 132 133 let catStr = "\(categories.general ? "1" : "0")\(categories.anime ? "1" : "0")\(categories.people ? "1" : "0")"134 135 let purityStr = "\(purity.sfw ? "1" : "0")\(purity.sketchy ? "1" : "0")0"136 137 let (newWallpapers, meta) = try await api.searchWallpapers(138 query: searchText,139 categories: catStr,140 purity: purityStr,141 resolutions: resolutions,142 ratios: ratios,143 limit: 24,144 page: currentPage145 )146 147 if reset {148 self.wallpapers = newWallpapers149 } else {150 self.wallpapers.append(contentsOf: newWallpapers)151 }152 153 if let meta = meta {154 self.canLoadMore = meta.current_page < meta.last_page155 self.currentPage = meta.current_page + 1156 } else {157 self.canLoadMore = !newWallpapers.isEmpty158 if !newWallpapers.isEmpty { self.currentPage += 1 }159 }160 161 } catch {162 if reset {163 self.errorMessage = error.localizedDescription164 }165 }166 167 if reset {168 isLoading = false169 } else {170 isFetchingMore = false171 }172 }173 174 func loadMore() async {175 await fetchWallpapers(reset: false)176 }177 178 func setWallpaper(_ wallpaper: Wallpaper, on screen: NSScreen? = nil) async {179 do {180 let localURL = try await WallpaperManager.shared.download(from: URL(string: wallpaper.path)!)181 182 try await MainActor.run {183 try WallpaperManager.shared.setDesktopImage(from: localURL, screen: screen)184 }185 186 187 historyManager.addToHistory(wallpaper)188 189 if saveToFolderEnabled {190 let destFolder = URL(fileURLWithPath: saveFolderPath)191 try WallpaperManager.shared.saveToFolder(from: localURL, to: destFolder)192 }193 194 } catch {195 self.errorMessage = "Failed to set wallpaper: \(error.localizedDescription)"196 }197 }198 199 func setLocalWallpaper(_ wallpaper: LocalWallpaper, on screen: NSScreen? = nil) async {200 do {201 try await MainActor.run {202 try WallpaperManager.shared.setDesktopImage(from: wallpaper.url, screen: screen)203 }204 205 let historyWallpaper = Wallpaper(206 id: wallpaper.id,207 url: wallpaper.url.path,208 path: wallpaper.url.path,209 resolution: wallpaper.resolution,210 purity: "local",211 file_size: Int(wallpaper.fileSize),212 thumbs: Wallpaper.Thumbs(213 large: wallpaper.url.path,214 original: wallpaper.url.path,215 small: wallpaper.url.path216 )217 )218 historyManager.addToHistory(historyWallpaper)219 220 } catch {221 self.errorMessage = "Failed to set wallpaper: \(error.localizedDescription)"222 }223 }224 225 func downloadWallpaper(_ wallpaper: Wallpaper) async {226 do {227 let localURL = try await WallpaperManager.shared.download(from: URL(string: wallpaper.path)!)228 229 230 let destFolder = URL(fileURLWithPath: saveFolderPath)231 try WallpaperManager.shared.saveToFolder(from: localURL, to: destFolder)232 233 } catch {234 self.errorMessage = "Failed to download: \(error.localizedDescription)"235 }236 }237 238 func selectSaveFolder() {239 240 241 MenuController.shared?.beginModalPresentation()242 NSApp.activate(ignoringOtherApps: true)243244 let panel = NSOpenPanel()245 panel.canChooseFiles = false246 panel.canChooseDirectories = true247 panel.canCreateDirectories = true248 panel.allowsMultipleSelection = false249 panel.directoryURL = URL(fileURLWithPath: saveFolderPath)250 panel.prompt = "Choose"251252 panel.begin { response in253 if response == .OK, let url = panel.url {254 self.saveFolderPath = url.path255 }256 MenuController.shared?.endModalPresentation()257 }258 }259 260 func quit() {261 NSApp.terminate(nil)262 }263}