// // ExploreView.swift // Pocket Poster // // Created by lemin on 7/6/25. // import SwiftUI import CachedAsyncImage let MIN_SIZE: CGFloat = 165 struct ExploreView: View { @ObservedObject var cowabungaAPI = CowabungaAPI.shared // lazyvgrid @State private var gridItemLayout = [GridItem(.adaptive(minimum: MIN_SIZE))] @State private var wallpapers: [DownloadableWallpaper] = [] @State var wallpaperTypeSelected = 0 @State var wallpaperTypeShown = DownloadableWallpaper.WallpaperType.custom @State var filterType: FilterType = .random @State var searchTerm: String = "" var body: some View { NavigationView { ScrollView { PullToRefresh(coordinateSpaceName: "pullToRefresh") { // refresh wallpapers.removeAll() Haptic.shared.play(.light) //URLCache.imageCache.removeAllCachedResponses() loadWallpapers() } VStack { Picker("", selection: $wallpaperTypeSelected) { Text("Custom").tag(0) Text("Apple").tag(1) } .pickerStyle(.segmented) } .padding(.horizontal, 24) .padding(.bottom, 10) if wallpapers.isEmpty { ProgressView() .scaleEffect(1.75) .navigationTitle("Explore") } else { LazyVGrid(columns: gridItemLayout) { ForEach(wallpapers) { wallpaper in if searchTerm == "" || wallpaper.name.lowercased().contains(searchTerm.lowercased()) || wallpaper.authors.lowercased().contains(searchTerm.lowercased()) { Button(action: { DownloadManager.shared.startTendiesDownload(for: cowabungaAPI.getDownloadURLForWallpaper(wallpaper: wallpaper)) }) { VStack(spacing: 0) { CachedAsyncImage(url: cowabungaAPI.getPreviewURLForWallpaper(wallpaper: wallpaper), urlCache: .imageCache) { image in image .resizable() .aspectRatio(contentMode: .fill) .frame(maxWidth: .infinity) .cornerRadius(10, corners: .topLeft) .cornerRadius(10, corners: .topRight) } placeholder: { Color.gray .frame(height: MIN_SIZE) } HStack { VStack(spacing: 4) { HStack { Text(wallpaper.name) .foregroundStyle(Color(uiColor: .label)) .minimumScaleFactor(0.5) Spacer() } HStack { Text(wallpaper.authors) .foregroundColor(.secondary) .font(.caption) .minimumScaleFactor(0.5) Spacer() } } .lineLimit(1) Spacer() Image(systemName: "arrow.down.circle") .foregroundColor(.blue) } .padding(.horizontal, 12) .padding(.vertical, 8) .frame(height: 58) } } .frame(minWidth: MIN_SIZE) .background(Color(uiColor: .secondarySystemBackground)) .cornerRadius(10) .padding(4) } } } .padding() } } .searchable(text: $searchTerm) .coordinateSpace(name: "pullToRefresh") .navigationTitle("Explore") .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button(action: { showFilterChangerPopup() }) { Image(systemName: "line.3.horizontal.decrease.circle") } } } } .onAppear { loadWallpapers() } .onChange(of: wallpaperTypeSelected) { newValue in let map = [0: DownloadableWallpaper.WallpaperType.custom, 1: .apple, 2: .template] wallpaperTypeShown = map[newValue]! wallpapers.removeAll() loadWallpapers() } } func loadWallpapers() { Task { do { wallpapers = try await cowabungaAPI.fetchWallpapers(type: wallpaperTypeShown) wallpapers = cowabungaAPI.filterWallpapers(wallpapers: wallpapers, filterType: filterType) } catch { UIApplication.shared.alert(title: NSLocalizedString("Failed to fetch wallpapers", comment: ""), body: error.localizedDescription) } } } func showFilterChangerPopup() { // create and configure alert controller let alert = UIAlertController(title: NSLocalizedString("Filter Wallpapers", comment: ""), message: "", preferredStyle: .actionSheet) // create the actions for type in FilterType.allCases { let newAction = UIAlertAction(title: type.rawValue, style: .default) { (action) in // apply the filter type filterType = type wallpapers.removeAll() loadWallpapers() } if filterType == type { // add a check mark newAction.setValue(true, forKey: "checked") } alert.addAction(newAction) } let cancelAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel) { (action) in // cancels the action } // add the actions alert.addAction(cancelAction) let view: UIView = UIApplication.shared.windows.first!.rootViewController!.view // present popover for iPads alert.popoverPresentationController?.sourceView = view // prevents crashing on iPads alert.popoverPresentationController?.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.maxY, width: 0, height: 0) // show up at center bottom on iPads // present the alert UIApplication.shared.windows.first?.rootViewController?.present(alert, animated: true) } } struct PullToRefresh: View { var coordinateSpaceName: String var onRefresh: ()->Void @State var needRefresh: Bool = false var body: some View { GeometryReader { geo in if (geo.frame(in: .named(coordinateSpaceName)).midY > 50) { Spacer() .onAppear { needRefresh = true } } else if (geo.frame(in: .named(coordinateSpaceName)).maxY < 10) { Spacer() .onAppear { if needRefresh { needRefresh = false onRefresh() } } } HStack { Spacer() if needRefresh { ProgressView() .scaleEffect(1.75) .onAppear { Haptic.shared.play(.light) } } else { Text("") } Spacer() } }.padding(.top, -50) } }