summaryrefslogtreecommitdiff
path: root/Sora
diff options
context:
space:
mode:
Diffstat (limited to 'Sora')
-rw-r--r--Sora/Data/Booru/Booru.swift4
-rw-r--r--Sora/Data/Booru/BooruManager.swift142
-rw-r--r--Sora/Data/Booru/BooruPost.swift27
-rw-r--r--Sora/Data/Booru/BooruPostXMLParser.swift116
-rw-r--r--Sora/Data/Booru/BooruTag.swift (renamed from Sora/Data/Moebooru/MoebooruTag.swift)2
-rw-r--r--Sora/Data/Booru/BooruTagXMLParser.swift (renamed from Sora/Data/Moebooru/MoebooruTagXMLParser.swift)10
-rw-r--r--Sora/Data/Booru/PostFileType.swift (renamed from Sora/Views/Post/PostFileType.swift)1
-rw-r--r--Sora/Data/Danbooru/DanbooruManager.swift114
-rw-r--r--Sora/Data/Danbooru/DanbooruPost.swift27
-rw-r--r--Sora/Data/Danbooru/DanbooruPostXMLParser.swift98
-rw-r--r--Sora/Data/Moebooru/MoebooruManager.swift4
-rw-r--r--Sora/Data/Settings/Settings.swift5
-rw-r--r--Sora/Views/BookmarksView.swift2
-rw-r--r--Sora/Views/ContentView.swift2
-rw-r--r--Sora/Views/MainView.swift34
-rw-r--r--Sora/Views/Post/PostDetailsView.swift8
-rw-r--r--Sora/Views/Post/PostGridBookmarkButtonView.swift2
-rw-r--r--Sora/Views/Post/PostGridView.swift35
-rw-r--r--Sora/Views/Post/PostView.swift8
-rw-r--r--Sora/Views/SearchSuggestionsView.swift2
-rw-r--r--Sora/Views/Settings/SettingsSourceView.swift13
-rw-r--r--Sora/Views/SettingsView.swift4
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()
}