summaryrefslogtreecommitdiff
path: root/Sora/Views/ContentView.swift
diff options
context:
space:
mode:
Diffstat (limited to 'Sora/Views/ContentView.swift')
-rw-r--r--Sora/Views/ContentView.swift198
1 files changed, 198 insertions, 0 deletions
diff --git a/Sora/Views/ContentView.swift b/Sora/Views/ContentView.swift
new file mode 100644
index 0000000..38516ef
--- /dev/null
+++ b/Sora/Views/ContentView.swift
@@ -0,0 +1,198 @@
+import SwiftUI
+
+struct ContentView: View {
+ @State private var posts: [MoebooruPost] = []
+ #if os(macOS)
+ @State private var selectedPost: YanderePost?
+ #endif
+ @State private var searchQuery = ""
+ @State private var currentPage = 1
+ @State private var isLoading: Bool = false
+ @State private var largerThumbnails: Bool = false
+ var tags: [String] {
+ if searchQuery.isEmpty {
+ return []
+ }
+
+ return searchQuery
+ .split(separator: ",")
+ .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
+ .filter { !$0.isEmpty }
+ }
+
+ @State var softLimit: Int = 100
+ var softLimitCGFloat: CGFloat {
+ CGFloat(softLimit)
+ }
+
+ var columns: [GridItem] {
+ return [
+ GridItem(.adaptive(minimum: softLimitCGFloat)),
+ ]
+ }
+
+ var body: some View {
+ #if os(macOS)
+ NavigationSplitView {
+ ScrollView {
+ if self.posts.isEmpty {
+ ProgressView()
+ .frame(width: softLimitCGFloat, height: softLimitCGFloat)
+ }
+
+ LazyVGrid(columns: columns) {
+ ForEach(posts, id: \.id) { post in
+ Button {
+ selectedPost = post
+ } label: {
+ PostView(post: post, softLimit: softLimitCGFloat)
+ .frame(maxWidth: .infinity)
+ }
+ .buttonStyle(PlainButtonStyle())
+
+ if post == posts.last {
+ ProgressView()
+ .onAppear(perform: loadNextPage)
+ .padding()
+ }
+ }
+ }
+ }
+ .searchable(text: $searchQuery, prompt: "Tags")
+ .onSubmit(of: .search, performSearch)
+ .toolbar {
+ ToolbarItem {
+ Button(action: {
+ Task {
+ await fetchPosts(page: 1, tags: tags, replace: true)
+ }
+ }) {
+ Label("Refresh", systemImage: "arrow.clockwise")
+ }
+ }
+ }
+ } detail: {
+ if let post = selectedPost {
+ PostDetailView(post: post)
+ } else {
+ Text("Select a post")
+ .foregroundColor(.secondary)
+ }
+ }
+ .task {
+ await fetchPosts(page: currentPage)
+ }
+ #else
+ NavigationStack {
+ ScrollView {
+ if self.posts.isEmpty {
+ ProgressView()
+ .frame(width: softLimitCGFloat, height: softLimitCGFloat)
+ }
+
+ LazyVGrid(columns: columns, spacing: 10) {
+ ForEach(posts, id: \.id) { post in
+ NavigationLink(value: post) {
+ PostView(
+ post: post,
+ softLimit: softLimitCGFloat,
+ thumbnailMode: self.largerThumbnails ? .sample : .preview
+ )
+ .frame(maxWidth: .infinity)
+ }
+
+ if post == posts.last {
+ ProgressView()
+ .onAppear(perform: loadNextPage)
+ .padding()
+ }
+ }
+ }
+ }
+ .searchable(text: $searchQuery, prompt: "Tags")
+ .onSubmit(of: .search, performSearch)
+ .navigationDestination(for: MoebooruPost.self) { post in
+ PostDetailView(post: post)
+ }
+ .task {
+ await fetchPosts(page: currentPage)
+ }
+ .toolbar {
+ ToolbarItem {
+ TextField(
+ softLimit.description,
+ value: $softLimit,
+ format: .number
+ )
+ }
+
+ ToolbarItem {
+ Toggle("Full size thumbnails", isOn: $largerThumbnails)
+ }
+
+ ToolbarItem {
+ Button(action: {
+ Task {
+ await fetchPosts(page: 1, tags: tags, replace: true)
+ }
+ }) {
+ Label("Refresh", systemImage: "arrow.clockwise")
+ }
+ }
+ }
+ }
+ #endif
+ }
+
+ func fetchPosts(page: Int = 1, limit: Int = 100, tags: [String] = [], replace: Bool = false) async {
+ guard !isLoading else { return }
+
+ isLoading = true
+
+ defer { isLoading = false }
+
+ guard let url = URL(string: "https://yande.re/post.xml?page=\(page)&limit=\(limit)&tags=\(tags.joined(separator: "+"))") else { return }
+ do {
+ let (data, _) = try await URLSession.shared.data(from: url)
+
+ DispatchQueue.main.async {
+ if replace {
+ self.posts = []
+ self.currentPage = 1
+ }
+
+ self.posts += Array(Set(MoebooruXMLParser().parse(data: data))).sorted { $0.id > $1.id }
+ }
+ } catch {
+ #if DEBUG
+ print(error)
+ #endif
+ }
+ }
+
+ func performSearch() {
+ Task {
+ await fetchPosts(
+ page: 1,
+ tags: tags,
+ replace: true
+ )
+ }
+ }
+
+ func loadNextPage() {
+ guard !isLoading else { return }
+
+ Task {
+ await fetchPosts(page: currentPage + 1, tags: tags)
+
+ DispatchQueue.main.async {
+ currentPage += 1
+ }
+ }
+ }
+}
+
+#Preview {
+ ContentView()
+}