Add support for multiple boorus

This commit is contained in:
James Shiffer 2022-06-14 23:49:04 -07:00
parent d00139ba34
commit 73a4377675
No known key found for this signature in database
GPG Key ID: C0DB8774A1B3BA45
3 changed files with 136 additions and 58 deletions

View File

@ -7,23 +7,11 @@
import SwiftUI
final class ModelData: ObservableObject {
struct ContentView<C: BooruClient>: View {
@Published private(set) var imgs: [GelbooruImage] = []
var searchTerms: [String]
init(searchTerms: [String]) {
self.searchTerms = searchTerms
GelbooruClient().searchTagImages(tags: searchTerms) { data, error in
self.imgs = error != nil ? [] : data!
}
}
}
struct ContentView: View {
@EnvironmentObject var model: ModelData
var booru: C
@State var searchTerm: String
@State private var pics: [C.T] = []
let cols = [
GridItem(.flexible()),
@ -31,26 +19,37 @@ struct ContentView: View {
GridItem(.flexible()),
]
func reloadSearchResults(_ tags: String) {
self.booru.searchTagImages(tags: tags) { (data: [C.T]?, error) in
self.pics = error != nil ? [] : data!
}
}
var body: some View {
NavigationView {
ScrollView {
Text(model.searchTerms.joined(separator: ", "))
Text(self.searchTerm)
.font(.headline)
LazyVGrid(columns: cols, spacing: 10) {
ForEach(model.imgs, id: \.self) { pic in
VStack {
AsyncImage(url: URL(string: pic.sampleUrl)) { image in
LazyVGrid(columns: cols, spacing: 2) {
ForEach(self.pics, id: \.self) { pic in
AsyncImage(url: URL(string: pic.displayUrl())) { image in
image.resizable().aspectRatio(contentMode: .fit)
} placeholder: {
ProgressView()
}
.frame(width: 100, height: 100 * CGFloat(pic.sampleHeight)/CGFloat(pic.sampleWidth))
Text(String(pic.id))
.frame(width: 100, height: 150)
}
}
}
}
.navigationTitle("Graboo")
.searchable(
text: self.$searchTerm,
placement: .sidebar
)
.onSubmit(of: .search) {
reloadSearchResults(self.searchTerm)
}
}
}
@ -58,8 +57,7 @@ struct ContentView: View {
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(ModelData(searchTerms: ["hatsune_miku", "rating:general"]))
ContentView(booru: SafebooruClient(), searchTerm: "doki_doki_literature_club")
}
}

View File

@ -11,8 +11,7 @@ import SwiftUI
struct GrabooApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(ModelData(searchTerms: ["hatsune_miku", "rating:general"]))
ContentView(booru: SafebooruClient(), searchTerm: "doki_doki_literature_club")
}
}
}

View File

@ -6,12 +6,30 @@
//
import Foundation
import CoreGraphics
struct GelbooruSearchResults: Decodable {
protocol BooruClient {
associatedtype T: BooruImage
var baseUrl: String { get }
func searchTagImages(tags: String, completionHandler: @escaping ([T]?, Error?) -> Void)
}
protocol BooruSearchResults: Decodable {
}
protocol BooruImage: Decodable, Hashable {
func aspectRatio() -> CGFloat
func displayUrl() -> String
}
struct GelbooruSearchResults: BooruSearchResults {
let post: [GelbooruImage];
}
struct GelbooruImage: Decodable, Hashable {
struct GelbooruImage: BooruImage {
let id: Int;
let fileUrl: String;
let width: Int;
@ -24,19 +42,23 @@ struct GelbooruImage: Decodable, Hashable {
let sampleHeight: Int;
let tags: String;
let rating: String;
}
class GelbooruClient {
let baseUrl: String
init(baseUrl: String = "https://gelbooru.com") {
self.baseUrl = baseUrl
func aspectRatio() -> CGFloat {
return CGFloat(self.width) / CGFloat(self.height);
}
func searchTagImages(tags: [String], completionHandler: @escaping ([GelbooruImage]?, Error?) -> Void) {
let joinedTags = tags.joined(separator: " ").addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
let url = URL(string: String(format: "%@/?page=dapi&s=post&q=index&json=1&tags=%@", self.baseUrl, joinedTags))!
let task = URLSession.shared.dataTask(with: url) { (data, _, error) in
func displayUrl() -> String {
if sampleWidth > 0 && sampleHeight > 0 {
return sampleUrl;
} else {
return fileUrl;
}
}
}
func httpGetJson<T: Decodable>(url: String, completionHandler: @escaping (T?, Error?) -> Void) {
let task = URLSession.shared.dataTask(with: URL(string: url)!) { (data, _, error) in
guard let data = data, error == nil else {
DispatchQueue.main.async {
completionHandler(nil, error)
@ -46,9 +68,9 @@ class GelbooruClient {
let parser = JSONDecoder()
parser.keyDecodingStrategy = .convertFromSnakeCase
var searchResults: GelbooruSearchResults
var result: T
do {
searchResults = try parser.decode(GelbooruSearchResults.self, from: data)
result = try parser.decode(T.self, from: data)
} catch let e {
DispatchQueue.main.async {
completionHandler(nil, e)
@ -57,9 +79,68 @@ class GelbooruClient {
}
DispatchQueue.main.async {
completionHandler(searchResults.post, nil)
completionHandler(result, nil)
}
}
task.resume()
}
class GelbooruClient: BooruClient {
private(set) var baseUrl: String
init() {
self.baseUrl = "https://gelbooru.com"
}
func searchTagImages(tags: String, completionHandler: @escaping ([GelbooruImage]?, Error?) -> Void) {
let joinedTags = tags.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
httpGetJson(url: String(format: "%@/?page=dapi&s=post&q=index&json=1&tags=%@", self.baseUrl, joinedTags)) { (data: GelbooruSearchResults?, error) in
guard let data = data, error == nil else {
DispatchQueue.main.async {
completionHandler(nil, error)
}
return
}
DispatchQueue.main.async {
completionHandler(data.post, nil)
}
}
}
}
struct SafebooruImage: BooruImage {
let id: Int;
let image: String;
let width: Int;
let height: Int;
let sample: Bool;
let sampleWidth: Int;
let sampleHeight: Int;
let tags: String;
let rating: String;
let directory: String;
func aspectRatio() -> CGFloat {
return CGFloat(self.width) / CGFloat(self.height);
}
func displayUrl() -> String {
return String(format: "https://safebooru.org/images/%@/%@", self.directory, self.image);
}
}
class SafebooruClient: BooruClient {
private(set) var baseUrl: String
init() {
self.baseUrl = "https://safebooru.org"
}
func searchTagImages(tags: String, completionHandler: @escaping ([SafebooruImage]?, Error?) -> Void) {
let joinedTags = tags.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
httpGetJson(url: String(format: "%@/?page=dapi&s=post&q=index&json=1&tags=%@", self.baseUrl, joinedTags), completionHandler: completionHandler)
}
}