Add support for multiple boorus
This commit is contained in:
parent
d00139ba34
commit
73a4377675
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
||||
func aspectRatio() -> CGFloat {
|
||||
return CGFloat(self.width) / CGFloat(self.height);
|
||||
}
|
||||
|
||||
class GelbooruClient {
|
||||
let baseUrl: String
|
||||
|
||||
init(baseUrl: String = "https://gelbooru.com") {
|
||||
self.baseUrl = baseUrl
|
||||
func displayUrl() -> String {
|
||||
if sampleWidth > 0 && sampleHeight > 0 {
|
||||
return sampleUrl;
|
||||
} else {
|
||||
return fileUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 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)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user