diff options
Diffstat (limited to 'Sora')
22 files changed, 613 insertions, 47 deletions
diff --git a/Sora/Data/Booru/Booru.swift b/Sora/Data/Booru/Booru.swift new file mode 100644 index 0000000..8dce279 --- /dev/null +++ b/Sora/Data/Booru/Booru.swift @@ -0,0 +1,4 @@ +enum Booru: String, CaseIterable { + case yandere + case safebooru +} diff --git a/Sora/Data/Booru/BooruManager.swift b/Sora/Data/Booru/BooruManager.swift new file mode 100644 index 0000000..5eb6f70 --- /dev/null +++ b/Sora/Data/Booru/BooruManager.swift @@ -0,0 +1,142 @@ +import SwiftUI + +@MainActor +class BooruManager: ObservableObject { + @Published var posts: [BooruPost] = [] + @Published var allTags: [BooruTag] = [] + @Published var isLoading: Bool = false + @Published var currentPage: Int = 1 + @Published var searchText = "" + @Published var endOfData: Bool = false + #if os(macOS) + @Published var selectedPost: BooruPost? + #endif + private var currentTask: Task<Void, Never>? + private let booru: Booru? + var tags: [String] { + if searchText.isEmpty { + return [] + } + + return searchText + .split(separator: " ") + .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } + .filter { !$0.isEmpty } + } + + init(booru: Booru? = nil) { + self.booru = booru + + fetchAllTags() + } + + func fetchPosts(page: Int = 1, limit: Int = 100, tags: [String] = [], replace: Bool = false) async { + guard !isLoading else { return } + guard booru != nil else { return } + + currentTask?.cancel() + + currentTask = Task { + isLoading = true + + defer { isLoading = false } + + if replace { + self.posts = [] + self.currentPage = 1 + } + + guard let url = urlForPosts(page: self.booru == .safebooru ? page - 1 : page, limit: limit, tags: tags) else { + return + } + + do { + let (data, _) = try await URLSession.shared.data(from: url) + + if Task.isCancelled { return } + + DispatchQueue.main.async { + let newPosts = Array(Set(BooruPostXMLParser(data: data).parse())).sorted { $0.id > $1.id } + + if newPosts.isEmpty { + self.endOfData = true + } else { + self.posts += Array(Set(newPosts)) + } + } + } catch { + if (error as? URLError)?.code != .cancelled { + #if DEBUG + print("fetchPosts: \(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 { + self.currentPage += 1 + } + } + } + + func fetchAllTags(limit: Int = 100_000) { + guard booru != nil else { return } + + Task { + guard let url = urlForTags(limit: limit) else { return } + + do { + let (data, _) = try await URLSession.shared.data(from: url) + + if Task.isCancelled { return } + + DispatchQueue.main.async { + self.allTags = BooruTagXMLParser(data: data).parse().sorted { $0.count > $1.count } + } + } catch { + if (error as? URLError)?.code != .cancelled { + #if DEBUG + print("fetchAllTags: \(error)") + #endif + } + } + } + } + + private func urlForPosts(page: Int, limit: Int, tags: [String]) -> URL? { + let tagString = tags.joined(separator: "+") + + switch booru { + case .yandere: + return URL(string: "https://yande.re/post.xml?page=\(page)&limit=\(limit)&tags=\(tagString)") + case .safebooru: + return URL(string: "https://safebooru.org/index.php?page=dapi&s=post&q=index&pid=\(page)&limit=\(limit)&tags=\(tagString)") + default: + return nil + } + } + + private func urlForTags(limit: Int) -> URL? { + switch booru { + case .yandere: + return URL(string: "https://yande.re/tag.xml?limit=\(limit)") + case .safebooru: + return URL(string: "https://safebooru.org/index.php?page=dapi&s=tag&q=index&limit=\(limit)") + default: + return nil + } + } +} diff --git a/Sora/Data/Booru/BooruPost.swift b/Sora/Data/Booru/BooruPost.swift new file mode 100644 index 0000000..59b8952 --- /dev/null +++ b/Sora/Data/Booru/BooruPost.swift @@ -0,0 +1,27 @@ +import Foundation + +struct BooruPost: Identifiable, Hashable { + let id: String + let height: Int + let score: String + let fileURL: URL + let parentID: String + let sampleURL: URL + let sampleWidth: Int + let sampleHeight: Int + let previewURL: URL + let rating: String + let tags: [String] + let width: Int + let change: String + let md5: String + let creatorID: String + let hasChildren: Bool + let createdAt: Date + let status: String + let source: String + let hasNotes: Bool + let hasComments: Bool + let previewWidth: Int + let previewHeight: Int +} diff --git a/Sora/Data/Booru/BooruPostXMLParser.swift b/Sora/Data/Booru/BooruPostXMLParser.swift new file mode 100644 index 0000000..30207bc --- /dev/null +++ b/Sora/Data/Booru/BooruPostXMLParser.swift @@ -0,0 +1,116 @@ +import Foundation + +class BooruPostXMLParser: NSObject, XMLParserDelegate { + private var posts: [BooruPost] = [] + private var currentPost: BooruPost? + private var parser: XMLParser + + init(data: Data) { + parser = XMLParser(data: data) + + super.init() + + parser.delegate = self + } + + func parse() -> [BooruPost] { + parser.parse() + + return posts + } + + func parser(_: XMLParser, didStartElement elementName: String, namespaceURI _: String?, qualifiedName _: String?, attributes attributeDict: [String: String] = [:]) { + if elementName == "post" { + guard let id = attributeDict["id"], + let heightStr = attributeDict["height"], + let height = Int(heightStr), + let score = attributeDict["score"], + let fileUrl = attributeDict["file_url"], + let parentId = attributeDict["parent_id"], + let sampleUrl = attributeDict["sample_url"], + let sampleWidthStr = attributeDict["sample_width"], + let sampleWidth = Int(sampleWidthStr), + let sampleHeightStr = attributeDict["sample_height"], + let sampleHeight = Int(sampleHeightStr), + let previewUrl = attributeDict["preview_url"], + let rating = attributeDict["rating"], + let tags = attributeDict["tags"], + let widthStr = attributeDict["width"], + let width = Int(widthStr), + let change = attributeDict["change"], + let md5 = attributeDict["md5"], + let creatorId = attributeDict["creator_id"], + let hasChildrenStr = attributeDict["has_children"], + let createdAt = attributeDict["created_at"], + let status = attributeDict["status"], + let source = attributeDict["source"], + let previewWidthStr = attributeDict["preview_width"], + let previewWidth = Int(previewWidthStr), + let previewHeightStr = attributeDict["preview_height"], + let previewHeight = Int(previewHeightStr) + else { + return + } + + let hasNotesStr = attributeDict["has_notes"] ?? "false" + let hasCommentsStr = attributeDict["has_comments"] ?? "false" + + currentPost = BooruPost( + id: id, + height: height, + score: score, + fileURL: URL(string: fileUrl)!, + parentID: parentId, + sampleURL: URL(string: sampleUrl)!, + sampleWidth: sampleWidth, + sampleHeight: sampleHeight, + previewURL: URL(string: previewUrl)!, + rating: rating, + tags: tags.components(separatedBy: " ").filter { !$0.isEmpty }, + width: width, + change: change, + md5: md5, + creatorID: creatorId, + hasChildren: hasChildrenStr == "true", + createdAt: parseCreatedAt(createdAt)!, + status: status, + source: source, + hasNotes: hasNotesStr == "true", + hasComments: hasCommentsStr == "true", + previewWidth: previewWidth, + previewHeight: previewHeight + ) + } + } + + func parser(_: XMLParser, didEndElement elementName: String, namespaceURI _: String?, qualifiedName _: String?) { + if elementName == "post", let post = currentPost { + posts.append(post) + + currentPost = nil + } + } + + #if DEBUG + func parser(_: XMLParser, parseErrorOccurred parseError: any Error) { + print("parser: \(parseError)") + } + #endif + + func parseCreatedAt(_ input: String) -> Date? { + let dateFormatter = DateFormatter() + + dateFormatter.dateFormat = "EEE MMM dd HH:mm:ss Z yyyy" + dateFormatter.locale = Locale(identifier: "en_US_POSIX") + + if let date = dateFormatter.date(from: input) { + return date + } + + if let timestamp = Double(input) { + return Date(timeIntervalSince1970: timestamp) + } + + return nil + } +} diff --git a/Sora/Data/Moebooru/MoebooruTag.swift b/Sora/Data/Booru/BooruTag.swift index 6aa7449..b98bc75 100644 --- a/Sora/Data/Moebooru/MoebooruTag.swift +++ b/Sora/Data/Booru/BooruTag.swift @@ -1,6 +1,6 @@ import Foundation -struct MoebooruTag: Identifiable, Hashable { +struct BooruTag: Identifiable, Hashable { let id: String let name: String let count: Int diff --git a/Sora/Data/Moebooru/MoebooruTagXMLParser.swift b/Sora/Data/Booru/BooruTagXMLParser.swift index e8b0213..0dfebc3 100644 --- a/Sora/Data/Moebooru/MoebooruTagXMLParser.swift +++ b/Sora/Data/Booru/BooruTagXMLParser.swift @@ -1,8 +1,8 @@ import Foundation -class MoebooruTagXMLParser: NSObject, XMLParserDelegate { - private var tags: [MoebooruTag] = [] - private var currentTag: MoebooruTag? +class BooruTagXMLParser: NSObject, XMLParserDelegate { + private var tags: [BooruTag] = [] + private var currentTag: BooruTag? private var parser: XMLParser init(data: Data) { @@ -13,7 +13,7 @@ class MoebooruTagXMLParser: NSObject, XMLParserDelegate { parser.delegate = self } - func parse() -> [MoebooruTag] { + func parse() -> [BooruTag] { parser.parse() return tags @@ -32,7 +32,7 @@ class MoebooruTagXMLParser: NSObject, XMLParserDelegate { return } - currentTag = MoebooruTag( + currentTag = BooruTag( id: id, name: name, count: count, diff --git a/Sora/Views/Post/PostFileType.swift b/Sora/Data/Booru/PostFileType.swift index a7b3ca6..addd8db 100644 --- a/Sora/Views/Post/PostFileType.swift +++ b/Sora/Data/Booru/PostFileType.swift @@ -1,6 +1,5 @@ enum PostFileType: String, CaseIterable { case original case sample - case compressed case preview } diff --git a/Sora/Data/Danbooru/DanbooruManager.swift b/Sora/Data/Danbooru/DanbooruManager.swift new file mode 100644 index 0000000..7bbb3a7 --- /dev/null +++ b/Sora/Data/Danbooru/DanbooruManager.swift @@ -0,0 +1,114 @@ +import SwiftUI + +@MainActor +class DanbooruManager: ObservableObject { + @Published var posts: [DanbooruPost] = [] + @Published var allTags: [BooruTag] = [] + @Published var isLoading: Bool = false + @Published var currentPage: Int = 1 + @Published var searchText = "" + @Published var endOfData: Bool = false + #if os(macOS) + @Published var selectedPost: MoebooruPost? + #endif + private var currentTask: Task<Void, Never>? + var tags: [String] { + if searchText.isEmpty { + return [] + } + + return searchText + .split(separator: " ") + .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } + .filter { !$0.isEmpty } + } + + init() { + fetchAllTags() + } + + func fetchPosts(page: Int = 1, limit: Int = 100, tags: [String] = [], replace: Bool = false) async { + guard !isLoading else { return } + + currentTask?.cancel() + + currentTask = Task { + isLoading = true + + defer { isLoading = false } + + if replace { + self.posts = [] + self.currentPage = 1 + } + + guard let url = URL(string: "https://safebooru.org/index.php?page=dapi&s=post&q=index&pid=\(page)&limit=\(limit)&tags=\(tags.joined(separator: "+"))") else { return } + + do { + let (data, _) = try await URLSession.shared.data(from: url) + + if Task.isCancelled { return } + + DispatchQueue.main.async { + let newPosts = Array(Set(DanbooruPostXMLParser(data: data).parse())).sorted { $0.id > $1.id } + + if newPosts == [] { + self.endOfData = true + } else { + self.posts += newPosts + } + } + } catch { + if (error as? URLError)?.code != .cancelled { + #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 { + self.currentPage += 1 + } + } + } + + func fetchAllTags(limit: Int = 100_000) { + Task { + guard let url = URL(string: "https://safebooru.org/index.php?page=dapi&s=tag&q=index&limit=\(limit)") else { return } + + do { + let (data, _) = try await URLSession.shared.data(from: url) + + if Task.isCancelled { return } + + DispatchQueue.main.async { + self.allTags = (BooruTagXMLParser(data: data).parse()).sorted { $0.count > $1.count } + } + } catch { + if (error as? URLError)?.code != .cancelled { + #if DEBUG + print(error) + #endif + } + } + } + } +} diff --git a/Sora/Data/Danbooru/DanbooruPost.swift b/Sora/Data/Danbooru/DanbooruPost.swift new file mode 100644 index 0000000..17b0d0f --- /dev/null +++ b/Sora/Data/Danbooru/DanbooruPost.swift @@ -0,0 +1,27 @@ +import Foundation + +struct DanbooruPost: Identifiable, Hashable { + let id: String + let height: Int + let score: String + let fileURL: URL + let parentID: String + let sampleURL: URL + let sampleWidth: Int + let sampleHeight: Int + let previewURL: URL + let rating: String + let tags: [String] + let width: Int + let change: String + let md5: String + let creatorID: String + let hasChildren: Bool + let createdAt: String + let status: String + let source: String + let hasNotes: Bool + let hasComments: Bool + let previewWidth: Int + let previewHeight: Int +} diff --git a/Sora/Data/Danbooru/DanbooruPostXMLParser.swift b/Sora/Data/Danbooru/DanbooruPostXMLParser.swift new file mode 100644 index 0000000..3d99465 --- /dev/null +++ b/Sora/Data/Danbooru/DanbooruPostXMLParser.swift @@ -0,0 +1,98 @@ +import Foundation + +class DanbooruPostXMLParser: NSObject, XMLParserDelegate { + private var posts: [DanbooruPost] = [] + private var currentPost: DanbooruPost? + private var parser: XMLParser + + init(data: Data) { + parser = XMLParser(data: data) + + super.init() + + parser.delegate = self + } + + func parse() -> [DanbooruPost] { + parser.parse() + + return posts + } + + func parser(_: XMLParser, didStartElement elementName: String, namespaceURI _: String?, qualifiedName _: String?, attributes attributeDict: [String: String] = [:]) { + if elementName == "post" { + guard let id = attributeDict["id"], + let heightStr = attributeDict["height"], + let height = Int(heightStr), + let score = attributeDict["score"], + let fileUrl = attributeDict["file_url"], + let parentId = attributeDict["parent_id"], + let sampleUrl = attributeDict["sample_url"], + let sampleWidthStr = attributeDict["sample_width"], + let sampleWidth = Int(sampleWidthStr), + let sampleHeightStr = attributeDict["sample_height"], + let sampleHeight = Int(sampleHeightStr), + let previewUrl = attributeDict["preview_url"], + let rating = attributeDict["rating"], + let tags = attributeDict["tags"], + let widthStr = attributeDict["width"], + let width = Int(widthStr), + let change = attributeDict["change"], + let md5 = attributeDict["md5"], + let creatorId = attributeDict["creator_id"], + let hasChildrenStr = attributeDict["has_children"], + let createdAt = attributeDict["created_at"], + let status = attributeDict["status"], + let source = attributeDict["source"], + let hasNotesStr = attributeDict["has_notes"], + let hasCommentsStr = attributeDict["has_comments"], + let previewWidthStr = attributeDict["preview_width"], + let previewWidth = Int(previewWidthStr), + let previewHeightStr = attributeDict["preview_height"], + let previewHeight = Int(previewHeightStr) + else { + return + } + + currentPost = DanbooruPost( + id: id, + height: height, + score: score, + fileURL: URL(string: fileUrl)!, + parentID: parentId, + sampleURL: URL(string: sampleUrl)!, + sampleWidth: sampleWidth, + sampleHeight: sampleHeight, + previewURL: URL(string: previewUrl)!, + rating: rating, + tags: tags.components(separatedBy: " ").filter { !$0.isEmpty }, + width: width, + change: change, + md5: md5, + creatorID: creatorId, + hasChildren: hasChildrenStr == "true", + createdAt: createdAt, + status: status, + source: source, + hasNotes: hasNotesStr == "true", + hasComments: hasCommentsStr == "true", + previewWidth: previewWidth, + previewHeight: previewHeight + ) + } + } + + func parser(_: XMLParser, didEndElement elementName: String, namespaceURI _: String?, qualifiedName _: String?) { + if elementName == "post", let post = currentPost { + posts.append(post) + + currentPost = nil + } + } + + #if DEBUG + func parser(_: XMLParser, parseErrorOccurred parseError: any Error) { + print(parseError) + } + #endif +} diff --git a/Sora/Data/Moebooru/MoebooruManager.swift b/Sora/Data/Moebooru/MoebooruManager.swift index 25d2489..d4f18d2 100644 --- a/Sora/Data/Moebooru/MoebooruManager.swift +++ b/Sora/Data/Moebooru/MoebooruManager.swift @@ -3,7 +3,7 @@ import SwiftUI @MainActor class MoebooruManager: ObservableObject { @Published var posts: [MoebooruPost] = [] - @Published var allTags: [MoebooruTag] = [] + @Published var allTags: [BooruTag] = [] @Published var isLoading: Bool = false @Published var currentPage: Int = 1 @Published var searchText = "" @@ -100,7 +100,7 @@ class MoebooruManager: ObservableObject { if Task.isCancelled { return } DispatchQueue.main.async { - self.allTags = (MoebooruTagXMLParser(data: data).parse()).sorted { $0.count > $1.count } + self.allTags = (BooruTagXMLParser(data: data).parse()).sorted { $0.count > $1.count } } } catch { if (error as? URLError)?.code != .cancelled { diff --git a/Sora/Data/Settings/Settings.swift b/Sora/Data/Settings/Settings.swift index 22b91cc..6dd95c6 100644 --- a/Sora/Data/Settings/Settings.swift +++ b/Sora/Data/Settings/Settings.swift @@ -2,7 +2,7 @@ import SwiftUI class Settings: ObservableObject { #if DEBUG - @AppStorage("detailViewType") var detailViewType: PostFileType = .compressed + @AppStorage("detailViewType") var detailViewType: PostFileType = .sample #else @AppStorage("detailViewType") var detailViewType: PostFileType = .original #endif @@ -12,6 +12,7 @@ class Settings: ObservableObject { @AppStorage("blurNSFWThumbnails") var blurNSFWThumbnails: Bool = true @AppStorage("showNSFWPosts") var showNSFWPosts: Bool = false @AppStorage("bookmarks") private var bookmarksData: Data = .init() + @AppStorage("preferredBooru") var preferredBooru: Booru = .yandere var bookmarks: [Bookmark] { get { @@ -31,7 +32,7 @@ class Settings: ObservableObject { func resetToDefaults() { #if DEBUG - detailViewType = .compressed + detailViewType = .preview #else detailViewType = .original #endif diff --git a/Sora/Views/BookmarksView.swift b/Sora/Views/BookmarksView.swift index f98f949..cafb431 100644 --- a/Sora/Views/BookmarksView.swift +++ b/Sora/Views/BookmarksView.swift @@ -2,7 +2,7 @@ import SwiftUI struct BookmarksView: View { @EnvironmentObject var settings: Settings - @EnvironmentObject var manager: MoebooruManager + @EnvironmentObject var manager: BooruManager @Binding var selectedTab: Int @State private var bookmarksSearchText: String = "" diff --git a/Sora/Views/ContentView.swift b/Sora/Views/ContentView.swift index 053e98d..5878619 100644 --- a/Sora/Views/ContentView.swift +++ b/Sora/Views/ContentView.swift @@ -1,7 +1,7 @@ import SwiftUI struct ContentView: View { - @EnvironmentObject var manager: MoebooruManager + @EnvironmentObject var manager: BooruManager var body: some View { #if os(macOS) diff --git a/Sora/Views/MainView.swift b/Sora/Views/MainView.swift index 966d986..af56e06 100644 --- a/Sora/Views/MainView.swift +++ b/Sora/Views/MainView.swift @@ -3,13 +3,29 @@ import SwiftUI struct MainView: View { @EnvironmentObject var settings: Settings @State private var selectedTab: Int = 0 - @StateObject private var manager = MoebooruManager() + @State private var manager = BooruManager(booru: .yandere) var body: some View { #if os(macOS) ContentView() .environmentObject(settings) .environmentObject(manager) + .onChange(of: settings.preferredBooru) { _, newState in + self.manager = BooruManager(booru: newState) + + Task { + await self.manager.fetchPosts() + } + } + .onAppear { + self.manager = BooruManager(booru: self.settings.preferredBooru) + + Task { + if manager.posts.isEmpty { + await self.manager.fetchPosts() + } + } + } #else TabView(selection: $selectedTab) { ContentView() @@ -37,6 +53,22 @@ struct MainView: View { .tag(2) } .environmentObject(settings) + .onChange(of: settings.preferredBooru) { _, newState in + self.manager = BooruManager(booru: newState) + + Task { + await self.manager.fetchPosts() + } + } + .onAppear { + self.manager = BooruManager(booru: self.settings.preferredBooru) + + Task { + if manager.posts.isEmpty { + await self.manager.fetchPosts() + } + } + } #endif } } diff --git a/Sora/Views/Post/PostDetailsView.swift b/Sora/Views/Post/PostDetailsView.swift index fe3b558..b24b70b 100644 --- a/Sora/Views/Post/PostDetailsView.swift +++ b/Sora/Views/Post/PostDetailsView.swift @@ -2,7 +2,7 @@ import SwiftUI struct PostDetailsView: View { @EnvironmentObject var settings: Settings - let post: MoebooruPost + let post: BooruPost @State var loadingStage: PostLoadingState = .loadingPreview private var imageURL: URL? { switch settings.detailViewType { @@ -12,8 +12,6 @@ struct PostDetailsView: View { return post.sampleURL case .original: return post.fileURL - case .compressed: - return post.jpegURL } } @@ -45,9 +43,7 @@ struct PostDetailsView: View { HStack { Text( - Date( - timeIntervalSince1970: TimeInterval(post.createdAt) - ).formatted() + post.createdAt.formatted() ) .frame(maxWidth: .infinity, alignment: .leading) diff --git a/Sora/Views/Post/PostGridBookmarkButtonView.swift b/Sora/Views/Post/PostGridBookmarkButtonView.swift index c4d8dc8..531eefe 100644 --- a/Sora/Views/Post/PostGridBookmarkButtonView.swift +++ b/Sora/Views/Post/PostGridBookmarkButtonView.swift @@ -1,7 +1,7 @@ import SwiftUI struct PostGridBookmarkButtonView: View { - @EnvironmentObject private var manager: MoebooruManager + @EnvironmentObject private var manager: BooruManager @EnvironmentObject private var settings: Settings var contained: Bool { diff --git a/Sora/Views/Post/PostGridView.swift b/Sora/Views/Post/PostGridView.swift index 625cb1e..3ccc772 100644 --- a/Sora/Views/Post/PostGridView.swift +++ b/Sora/Views/Post/PostGridView.swift @@ -3,11 +3,12 @@ import WaterfallGrid struct PostGridView: View { @EnvironmentObject var settings: Settings - @ObservedObject var manager: MoebooruManager + @ObservedObject var manager: BooruManager @Environment(\.isSearching) private var isSearching - var filteredPosts: [MoebooruPost] { - settings.showNSFWPosts ? manager.posts : manager.posts.filter { $0.rating == "s" } + var filteredPosts: [BooruPost] { + (settings.showNSFWPosts ? manager.posts : manager.posts.filter { $0.rating == "s" || $0.rating == "q" }) + .sorted(by: { $0.id > $1.id }) } var body: some View { @@ -54,12 +55,7 @@ struct PostGridView: View { } } .onSubmit(of: .search, manager.performSearch) - .task { - if manager.posts.isEmpty { - await manager.fetchPosts(page: manager.currentPage) - } - } - .navigationDestination(for: MoebooruPost.self) { post in + .navigationDestination(for: BooruPost.self) { post in PostDetailsView(post: post) } .onChange(of: manager.searchText) { _, _ in @@ -69,20 +65,19 @@ struct PostGridView: View { } } } - #if os(macOS) .toolbar { - ToolbarItem { - Button(action: { - Task { - await manager.fetchPosts(page: 1, tags: manager.tags, replace: true) + #if os(macOS) + ToolbarItem { + Button(action: { + Task { + await manager.fetchPosts(page: 1, tags: manager.tags, replace: true) + } + }) { + Label("Refresh", systemImage: "arrow.clockwise") } - }) { - Label("Refresh", systemImage: "arrow.clockwise") } - } - } - #endif - .toolbar { + #endif + if !manager.tags.isEmpty { #if os(macOS) ToolbarItem { diff --git a/Sora/Views/Post/PostView.swift b/Sora/Views/Post/PostView.swift index 9c25552..2765cd5 100644 --- a/Sora/Views/Post/PostView.swift +++ b/Sora/Views/Post/PostView.swift @@ -2,9 +2,9 @@ import SwiftUI struct PostView: View { @EnvironmentObject var settings: Settings - let post: MoebooruPost - @ObservedObject var manager: MoebooruManager - let posts: [MoebooruPost] + let post: BooruPost + @ObservedObject var manager: BooruManager + let posts: [BooruPost] private var thumbnailURL: URL? { switch settings.thumbnailType { case .preview: @@ -13,8 +13,6 @@ struct PostView: View { return post.sampleURL case .original: return post.fileURL - case .compressed: - return post.jpegURL } } diff --git a/Sora/Views/SearchSuggestionsView.swift b/Sora/Views/SearchSuggestionsView.swift index cae8d43..b9c3133 100644 --- a/Sora/Views/SearchSuggestionsView.swift +++ b/Sora/Views/SearchSuggestionsView.swift @@ -1,7 +1,7 @@ import SwiftUI struct SearchSuggestionsView: View { - var tags: [MoebooruTag] + var tags: [BooruTag] @Binding var searchText: String var lastSearchTag: String { String(searchText.split(separator: " ").last ?? "") diff --git a/Sora/Views/Settings/SettingsSourceView.swift b/Sora/Views/Settings/SettingsSourceView.swift new file mode 100644 index 0000000..8663290 --- /dev/null +++ b/Sora/Views/Settings/SettingsSourceView.swift @@ -0,0 +1,13 @@ +import SwiftUI + +struct SettingsSourceView: View { + @EnvironmentObject var settings: Settings + + var body: some View { + Picker("Booru", selection: $settings.preferredBooru) { + ForEach(Booru.allCases, id: \.self) { type in + Text(type.rawValue.capitalized).tag(type) + } + } + } +} diff --git a/Sora/Views/SettingsView.swift b/Sora/Views/SettingsView.swift index c658775..e1bf2a9 100644 --- a/Sora/Views/SettingsView.swift +++ b/Sora/Views/SettingsView.swift @@ -5,6 +5,10 @@ struct SettingsView: View { var body: some View { Form { + Section(header: Text("Source")) { + SettingsSourceView() + } + Section(header: Text("Thumbnails")) { SettingsThumbnailsView() } |