Add support for multiple boorus
This commit is contained in:
parent
d00139ba34
commit
73a4377675
@ -7,23 +7,11 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
final class ModelData: ObservableObject {
|
struct ContentView<C: BooruClient>: View {
|
||||||
|
|
||||||
@Published private(set) var imgs: [GelbooruImage] = []
|
var booru: C
|
||||||
var searchTerms: [String]
|
@State var searchTerm: String
|
||||||
|
@State private var pics: [C.T] = []
|
||||||
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
|
|
||||||
|
|
||||||
let cols = [
|
let cols = [
|
||||||
GridItem(.flexible()),
|
GridItem(.flexible()),
|
||||||
@ -31,26 +19,37 @@ struct ContentView: View {
|
|||||||
GridItem(.flexible()),
|
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 {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
Text(model.searchTerms.joined(separator: ", "))
|
Text(self.searchTerm)
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
LazyVGrid(columns: cols, spacing: 10) {
|
LazyVGrid(columns: cols, spacing: 2) {
|
||||||
ForEach(model.imgs, id: \.self) { pic in
|
ForEach(self.pics, id: \.self) { pic in
|
||||||
VStack {
|
AsyncImage(url: URL(string: pic.displayUrl())) { image in
|
||||||
AsyncImage(url: URL(string: pic.sampleUrl)) { image in
|
image.resizable().aspectRatio(contentMode: .fit)
|
||||||
image.resizable().aspectRatio(contentMode: .fit)
|
} placeholder: {
|
||||||
} placeholder: {
|
ProgressView()
|
||||||
ProgressView()
|
|
||||||
}
|
|
||||||
.frame(width: 100, height: 100 * CGFloat(pic.sampleHeight)/CGFloat(pic.sampleWidth))
|
|
||||||
Text(String(pic.id))
|
|
||||||
}
|
}
|
||||||
|
.frame(width: 100, height: 150)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("Graboo")
|
}
|
||||||
|
.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 {
|
struct ContentView_Previews: PreviewProvider {
|
||||||
|
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
ContentView()
|
ContentView(booru: SafebooruClient(), searchTerm: "doki_doki_literature_club")
|
||||||
.environmentObject(ModelData(searchTerms: ["hatsune_miku", "rating:general"]))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,7 @@ import SwiftUI
|
|||||||
struct GrabooApp: App {
|
struct GrabooApp: App {
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView()
|
ContentView(booru: SafebooruClient(), searchTerm: "doki_doki_literature_club")
|
||||||
.environmentObject(ModelData(searchTerms: ["hatsune_miku", "rating:general"]))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,30 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
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];
|
let post: [GelbooruImage];
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GelbooruImage: Decodable, Hashable {
|
struct GelbooruImage: BooruImage {
|
||||||
let id: Int;
|
let id: Int;
|
||||||
let fileUrl: String;
|
let fileUrl: String;
|
||||||
let width: Int;
|
let width: Int;
|
||||||
@ -24,19 +42,59 @@ struct GelbooruImage: Decodable, Hashable {
|
|||||||
let sampleHeight: Int;
|
let sampleHeight: Int;
|
||||||
let tags: String;
|
let tags: String;
|
||||||
let rating: 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) {
|
func displayUrl() -> String {
|
||||||
let joinedTags = tags.joined(separator: " ").addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
|
if sampleWidth > 0 && sampleHeight > 0 {
|
||||||
let url = URL(string: String(format: "%@/?page=dapi&s=post&q=index&json=1&tags=%@", self.baseUrl, joinedTags))!
|
return sampleUrl;
|
||||||
let task = URLSession.shared.dataTask(with: url) { (data, _, error) in
|
} 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)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let parser = JSONDecoder()
|
||||||
|
parser.keyDecodingStrategy = .convertFromSnakeCase
|
||||||
|
var result: T
|
||||||
|
do {
|
||||||
|
result = try parser.decode(T.self, from: data)
|
||||||
|
} catch let e {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
completionHandler(nil, e)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
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 {
|
guard let data = data, error == nil else {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
completionHandler(nil, error)
|
completionHandler(nil, error)
|
||||||
@ -44,22 +102,45 @@ class GelbooruClient {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let parser = JSONDecoder()
|
|
||||||
parser.keyDecodingStrategy = .convertFromSnakeCase
|
|
||||||
var searchResults: GelbooruSearchResults
|
|
||||||
do {
|
|
||||||
searchResults = try parser.decode(GelbooruSearchResults.self, from: data)
|
|
||||||
} catch let e {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
completionHandler(nil, e)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
completionHandler(searchResults.post, nil)
|
completionHandler(data.post, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
task.resume()
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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