import SwiftUI struct MainView: View { @StateObject private var appState: AppState init(appState: AppState? = nil) { let appState = appState ?? AppState() _appState = StateObject(wrappedValue: appState) } var body: some View { ZStack { VStack(spacing: 0) { // Header (Universal) HStack { Text("Haven") .font(.custom("Avenir Next Medium", size: 18)) Spacer() // Tab Bar HStack(spacing: 16) { TabButton(icon: "safari", tab: .explore, activeTab: $appState.activeTab) TabButton(icon: "folder", tab: .local, activeTab: $appState.activeTab) TabButton(icon: "heart", tab: .favorites, activeTab: $appState.activeTab) TabButton(icon: "clock", tab: .history, activeTab: $appState.activeTab) TabButton(icon: "gearshape", tab: .settings, activeTab: $appState.activeTab) } } .padding() .background(VisualEffectView(material: .headerView, blendingMode: .behindWindow)) Divider() // Content ZStack { switch appState.activeTab { case .explore: ExploreView(appState: appState) case .local: LocalView(appState: appState) case .favorites: FavoritesView(appState: appState) case .history: HistoryView(appState: appState) case .settings: SettingsView(appState: appState) } } .frame(maxWidth: .infinity, maxHeight: .infinity) } } .frame(width: 450, height: 640) .background(Color(nsColor: .windowBackgroundColor)) .onAppear { Task { await appState.fetchWallpapers() } } } } // MARK: - Subviews struct ExploreView: View { @ObservedObject var appState: AppState var body: some View { VStack(spacing: 0) { // Controls HStack { HStack { Image(systemName: "magnifyingglass") .foregroundColor(.secondary) TextField("Search...", text: $appState.searchText) } .padding(8) .background(Color(nsColor: .controlBackgroundColor)) .cornerRadius(8) Menu { Section("Categories") { Toggle("General", isOn: $appState.categories.general) Toggle("Anime", isOn: $appState.categories.anime) Toggle("People", isOn: $appState.categories.people) } Section("Purity") { Toggle("SFW", isOn: $appState.purity.sfw) Toggle("Sketchy", isOn: $appState.purity.sketchy) } Divider() Picker("Resolution", selection: $appState.selectedResolution) { ForEach(appState.availableResolutions, id: \.self) { resolutionOption($0) } } Picker("Ratio", selection: $appState.selectedRatio) { ForEach(appState.availableRatios, id: \.self) { ratioOption($0) } } } label: { Image(systemName: "slider.horizontal.3") } .menuStyle(.borderlessButton) .frame(width: 30) Button(action: refreshAction) { Image(systemName: "arrow.clockwise") } .buttonStyle(.plain) .frame(width: 30) } .padding() .onChange(of: appState.selectedResolution) { _ in Task { await appState.fetchWallpapers() } } .onChange(of: appState.selectedRatio) { _ in Task { await appState.fetchWallpapers() } } Divider() // Feed-mode selector: Latest, Toplist, Random. The active mode is // visually emphasised; taps update appState.feedMode and trigger // a reset fetch via the Combine observer in AppState. HStack(spacing: 10) { ForEach(FeedMode.allCases) { mode in FeedModePill(mode: mode, isActive: appState.feedMode == mode) { select(mode) } } } .padding(.horizontal) .padding(.vertical, 10) Divider() WallpaperGrid( wallpapers: appState.wallpapers, appState: appState, onEndReached: loadMoreAction) } } func resolutionOption(_ resolution: String) -> some View { Text(resolution).tag(resolution) } func ratioOption(_ ratio: String) -> some View { Text(ratio).tag(ratio) } func refresh() async { await appState.fetchWallpapers(reset: true) } func refreshAction() { Task { await refresh() } } func select(_ mode: FeedMode) { if appState.feedMode != mode { appState.feedMode = mode } } func loadMore() async { await appState.loadMore() } func loadMoreAction() { Task { await loadMore() } } } struct FeedModePill: View { let mode: FeedMode let isActive: Bool let action: () -> Void private var tint: Color { switch mode { case .latest: return Color(red: 0.45, green: 0.85, blue: 0.30) // green case .toplist: return Color(red: 0.85, green: 0.40, blue: 0.85) // magenta case .random: return Color(red: 0.98, green: 0.55, blue: 0.20) // orange } } var body: some View { Button(action: action) { HStack(spacing: 6) { Image(systemName: mode.systemImage) Text(mode.displayName) .font(.system(size: 13, weight: .medium)) } .padding(.horizontal, 14) .padding(.vertical, 8) .frame(maxWidth: .infinity) .foregroundColor(isActive ? .white : tint) .background( RoundedRectangle(cornerRadius: 10) .fill(isActive ? tint : Color.clear) ) .overlay( RoundedRectangle(cornerRadius: 10) .stroke(tint.opacity(isActive ? 0 : 0.6), lineWidth: 1.5) ) } .buttonStyle(.plain) } } struct FavoritesView: View { @ObservedObject var appState: AppState var body: some View { if appState.favoritesManager.favorites.isEmpty { EmptyState(icon: "heart.slash", text: "No favorites yet") } else { WallpaperGrid( wallpapers: appState.favoritesManager.favorites, appState: appState, onEndReached: nil) } } } struct HistoryView: View { @ObservedObject var appState: AppState var body: some View { VStack { if !appState.historyManager.history.isEmpty { HStack { Spacer() Button("Clear History") { clearHistory() } .font(.caption) .buttonStyle(.link) } .padding(.horizontal) .padding(.top, 8) } if appState.historyManager.history.isEmpty { EmptyState(icon: "clock.arrow.circlepath", text: "No history yet") } else { WallpaperGrid( wallpapers: appState.historyManager.history, appState: appState, onEndReached: nil) } } } func clearHistory() { appState.historyManager.clearHistory() } } struct SettingsView: View { @ObservedObject var appState: AppState var body: some View { VStack(alignment: .leading, spacing: 24) { VStack(alignment: .leading, spacing: 12) { Text("Storage") .font(.headline) Toggle("Launch at Login", isOn: $appState.launchAtLogin) Toggle("Save wallpapers locally", isOn: $appState.saveToFolderEnabled) if appState.saveToFolderEnabled { HStack { Text("Location") Spacer() Text(URL(fileURLWithPath: appState.saveFolderPath).lastPathComponent) .foregroundColor(.secondary) .lineLimit(1) .truncationMode(.middle) Button("Change…") { selectFolder() } } } } Spacer() Button(role: .destructive, action: quit) { Text("Quit Haven") .frame(maxWidth: .infinity) } .controlSize(.large) } .padding() .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) } func selectFolder() { appState.selectSaveFolder() } func quit() { appState.quit() } } struct EmptyState: View { let icon: String let text: String var body: some View { VStack(spacing: 16) { Image(systemName: icon) .font(.system(size: 48)) .foregroundColor(.gray.opacity(0.5)) Text(text) .foregroundColor(.secondary) } .frame(maxWidth: .infinity, maxHeight: .infinity) } } struct TabButton: View { let icon: String let tab: AppTab @Binding var activeTab: AppTab var body: some View { Button(action: select) { Image(systemName: icon) .symbolVariant(activeTab == tab ? .fill : .none) .foregroundColor(activeTab == tab ? .accentColor : .secondary) .font(.system(size: 18)) } .buttonStyle(.plain) } func select() { activeTab = tab } } // Redeclaring this as it was in the original file, or we can move to a separate file struct VisualEffectView: NSViewRepresentable { let material: NSVisualEffectView.Material let blendingMode: NSVisualEffectView.BlendingMode func makeNSView(context: Context) -> NSVisualEffectView { let visualEffectView = NSVisualEffectView() visualEffectView.material = material visualEffectView.blendingMode = blendingMode visualEffectView.state = .active return visualEffectView } func updateNSView(_ nsView: NSVisualEffectView, context: Context) { nsView.material = material nsView.blendingMode = blendingMode } }