MainView.swift

8.7 KB · raw

1import SwiftUI23struct MainView: View {4    @StateObject private var appState = AppState()5    6    var body: some View {7        ZStack {8            VStack(spacing: 0) {9                // Header (Universal)10                HStack {11                    Text("Haven")12                        .font(.custom("Avenir Next Medium", size: 18))13                    14                    Spacer()15                    16                    // Tab Bar17                    HStack(spacing: 16) {18                        TabButton(icon: "safari", tab: .explore, activeTab: $appState.activeTab)19                        TabButton(icon: "folder", tab: .local, activeTab: $appState.activeTab)20                        TabButton(icon: "heart", tab: .favorites, activeTab: $appState.activeTab)21                        TabButton(icon: "clock", tab: .history, activeTab: $appState.activeTab)22                        TabButton(icon: "gearshape", tab: .settings, activeTab: $appState.activeTab)23                    }24                }25                .padding()26                .background(VisualEffectView(material: .headerView, blendingMode: .behindWindow))27                28                Divider()29                30                // Content31                ZStack {32                    switch appState.activeTab {33                    case .explore:34                        ExploreView(appState: appState)35                    case .local:36                        LocalView(appState: appState)37                    case .favorites:38                        FavoritesView(appState: appState)39                    case .history:40                        HistoryView(appState: appState)41                    case .settings:42                        SettingsView(appState: appState)43                    }44                }45                .frame(maxWidth: .infinity, maxHeight: .infinity)46            }47        }48        .frame(width: 450, height: 640)49        .background(Color(nsColor: .windowBackgroundColor))50        .onAppear {51            Task { await appState.fetchWallpapers() }52        }53    }54}5556// MARK: - Subviews5758struct ExploreView: View {59    @ObservedObject var appState: AppState60    61    var body: some View {62        VStack(spacing: 0) {63            // Controls64            HStack {65                HStack {66                    Image(systemName: "magnifyingglass")67                        .foregroundColor(.secondary)68                    TextField("Search...", text: $appState.searchText)69                }70                .padding(8)71                .background(Color(nsColor: .controlBackgroundColor))72                .cornerRadius(8)73                74                Menu {75                    Section("Categories") {76                        Toggle("General", isOn: $appState.categories.general)77                        Toggle("Anime", isOn: $appState.categories.anime)78                        Toggle("People", isOn: $appState.categories.people)79                    }80                    81                    Section("Purity") {82                        Toggle("SFW", isOn: $appState.purity.sfw)83                        Toggle("Sketchy", isOn: $appState.purity.sketchy)84                    }85                    86                    Divider()87                    88                    Picker("Resolution", selection: $appState.selectedResolution) {89                        ForEach(appState.availableResolutions, id: \.self) { res in90                            Text(res).tag(res)91                        }92                    }93                    Picker("Ratio", selection: $appState.selectedRatio) {94                        ForEach(appState.availableRatios, id: \.self) { ratio in95                            Text(ratio).tag(ratio)96                        }97                    }98                } label: {99                    Image(systemName: "slider.horizontal.3")100                }101102                .menuStyle(.borderlessButton)103                .frame(width: 30)104                105                Button {106                    Task { await appState.fetchWallpapers(reset: true) }107                } label: {108                    Image(systemName: "arrow.clockwise")109                }110                .buttonStyle(.plain)111                .frame(width: 30)112            }113            .padding()114            .onChange(of: appState.selectedResolution) { _ in Task { await appState.fetchWallpapers() } }115            .onChange(of: appState.selectedRatio) { _ in Task { await appState.fetchWallpapers() } }116            117            Divider()118            119            WallpaperGrid(wallpapers: appState.wallpapers, appState: appState) {120                Task { await appState.loadMore() }121            }122        }123    }124}125126struct FavoritesView: View {127    @ObservedObject var appState: AppState128    129    var body: some View {130        if appState.favoritesManager.favorites.isEmpty {131            EmptyState(icon: "heart.slash", text: "No favorites yet")132        } else {133            WallpaperGrid(wallpapers: appState.favoritesManager.favorites, appState: appState, onEndReached: nil)134        }135    }136}137138struct HistoryView: View {139    @ObservedObject var appState: AppState140    141    var body: some View {142        VStack {143            if !appState.historyManager.history.isEmpty {144                HStack {145                    Spacer()146                    Button("Clear History") {147                        appState.historyManager.clearHistory()148                    }149                    .font(.caption)150                    .buttonStyle(.link)151                }152                .padding(.horizontal)153                .padding(.top, 8)154            }155            156            if appState.historyManager.history.isEmpty {157                EmptyState(icon: "clock.arrow.circlepath", text: "No history yet")158            } else {159                WallpaperGrid(wallpapers: appState.historyManager.history, appState: appState, onEndReached: nil)160            }161        }162    }163}164165struct SettingsView: View {166    @ObservedObject var appState: AppState167168    var body: some View {169        VStack(alignment: .leading, spacing: 24) {170            VStack(alignment: .leading, spacing: 12) {171                Text("Storage")172                    .font(.headline)173174                Toggle("Launch at Login", isOn: $appState.launchAtLogin)175                Toggle("Save wallpapers locally", isOn: $appState.saveToFolderEnabled)176177                if appState.saveToFolderEnabled {178                    HStack {179                        Text("Location")180                        Spacer()181                        Text(URL(fileURLWithPath: appState.saveFolderPath).lastPathComponent)182                            .foregroundColor(.secondary)183                            .lineLimit(1)184                            .truncationMode(.middle)185                        Button("Change…") {186                            appState.selectSaveFolder()187                        }188                    }189                }190            }191192            Spacer()193194            Button(role: .destructive) {195                appState.quit()196            } label: {197                Text("Quit Haven")198                    .frame(maxWidth: .infinity)199            }200            .controlSize(.large)201        }202        .padding()203        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)204    }205}206207struct EmptyState: View {208    let icon: String209    let text: String210    211    var body: some View {212        VStack(spacing: 16) {213            Image(systemName: icon)214                .font(.system(size: 48))215                .foregroundColor(.gray.opacity(0.5))216            Text(text)217                .foregroundColor(.secondary)218        }219        .frame(maxWidth: .infinity, maxHeight: .infinity)220    }221}222223struct TabButton: View {224    let icon: String225    let tab: AppTab226    @Binding var activeTab: AppTab227    228    var body: some View {229        Button {230            activeTab = tab231        } label: {232            Image(systemName: icon)233                .symbolVariant(activeTab == tab ? .fill : .none)234                .foregroundColor(activeTab == tab ? .accentColor : .secondary)235                .font(.system(size: 18))236        }237        .buttonStyle(.plain)238    }239}240241// Redeclaring this as it was in the original file, or we can move to a separate file242struct VisualEffectView: NSViewRepresentable {243    let material: NSVisualEffectView.Material244    let blendingMode: NSVisualEffectView.BlendingMode245    246    func makeNSView(context: Context) -> NSVisualEffectView {247        let visualEffectView = NSVisualEffectView()248        visualEffectView.material = material249        visualEffectView.blendingMode = blendingMode250        visualEffectView.state = .active251        return visualEffectView252    }253    254    func updateNSView(_ nsView: NSVisualEffectView, context: Context) {255        nsView.material = material256        nsView.blendingMode = blendingMode257    }258}