From d00139ba34f79f1c0f1c1a61e8caf6f1f8a98ba8 Mon Sep 17 00:00:00 2001 From: James Shiffer <2191476+scoliono@users.noreply.github.com> Date: Tue, 14 Jun 2022 00:57:43 -0700 Subject: [PATCH] Added Gelbooru viewer --- Graboo.xcodeproj/project.pbxproj | 16 +++++++- Graboo/ContentView.swift | 48 ++++++++++++++++++++++- Graboo/GrabooApp.swift | 1 + Graboo/lib/Client.swift | 65 ++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 Graboo/lib/Client.swift diff --git a/Graboo.xcodeproj/project.pbxproj b/Graboo.xcodeproj/project.pbxproj index 2358258..e206f06 100644 --- a/Graboo.xcodeproj/project.pbxproj +++ b/Graboo.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 80B3AC6628583CBD00CB7B31 /* GrabooTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80B3AC6528583CBD00CB7B31 /* GrabooTests.swift */; }; 80B3AC7028583CBD00CB7B31 /* GrabooUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80B3AC6F28583CBD00CB7B31 /* GrabooUITests.swift */; }; 80B3AC7228583CBD00CB7B31 /* GrabooUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80B3AC7128583CBD00CB7B31 /* GrabooUITestsLaunchTests.swift */; }; + 80B3AC8028584E3700CB7B31 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80B3AC7F28584E3700CB7B31 /* Client.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -44,6 +45,7 @@ 80B3AC6B28583CBD00CB7B31 /* GrabooUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GrabooUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 80B3AC6F28583CBD00CB7B31 /* GrabooUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrabooUITests.swift; sourceTree = ""; }; 80B3AC7128583CBD00CB7B31 /* GrabooUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GrabooUITestsLaunchTests.swift; sourceTree = ""; }; + 80B3AC7F28584E3700CB7B31 /* Client.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -94,6 +96,7 @@ 80B3AC5328583CBB00CB7B31 /* Graboo */ = { isa = PBXGroup; children = ( + 80B3AC7E28584E2100CB7B31 /* lib */, 80B3AC5428583CBB00CB7B31 /* GrabooApp.swift */, 80B3AC5628583CBB00CB7B31 /* ContentView.swift */, 80B3AC5828583CBD00CB7B31 /* Assets.xcassets */, @@ -127,6 +130,14 @@ path = GrabooUITests; sourceTree = ""; }; + 80B3AC7E28584E2100CB7B31 /* lib */ = { + isa = PBXGroup; + children = ( + 80B3AC7F28584E3700CB7B31 /* Client.swift */, + ); + path = lib; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -259,6 +270,7 @@ files = ( 80B3AC5728583CBB00CB7B31 /* ContentView.swift in Sources */, 80B3AC5528583CBB00CB7B31 /* GrabooApp.swift in Sources */, + 80B3AC8028584E3700CB7B31 /* Client.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -427,7 +439,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -457,7 +469,7 @@ INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Graboo/ContentView.swift b/Graboo/ContentView.swift index dd75704..9f5aa7d 100644 --- a/Graboo/ContentView.swift +++ b/Graboo/ContentView.swift @@ -7,15 +7,59 @@ import SwiftUI +final class ModelData: ObservableObject { + + @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 + + let cols = [ + GridItem(.flexible()), + GridItem(.flexible()), + GridItem(.flexible()), + ] + var body: some View { - Text("Hello, world!") - .padding() + NavigationView { + ScrollView { + Text(model.searchTerms.joined(separator: ", ")) + .font(.headline) + LazyVGrid(columns: cols, spacing: 10) { + ForEach(model.imgs, id: \.self) { pic in + VStack { + AsyncImage(url: URL(string: pic.sampleUrl)) { image in + image.resizable().aspectRatio(contentMode: .fit) + } placeholder: { + ProgressView() + } + .frame(width: 100, height: 100 * CGFloat(pic.sampleHeight)/CGFloat(pic.sampleWidth)) + Text(String(pic.id)) + } + } + } + } + .navigationTitle("Graboo") + } } } struct ContentView_Previews: PreviewProvider { + static var previews: some View { ContentView() + .environmentObject(ModelData(searchTerms: ["hatsune_miku", "rating:general"])) } + } diff --git a/Graboo/GrabooApp.swift b/Graboo/GrabooApp.swift index 158f313..86919f9 100644 --- a/Graboo/GrabooApp.swift +++ b/Graboo/GrabooApp.swift @@ -12,6 +12,7 @@ struct GrabooApp: App { var body: some Scene { WindowGroup { ContentView() + .environmentObject(ModelData(searchTerms: ["hatsune_miku", "rating:general"])) } } } diff --git a/Graboo/lib/Client.swift b/Graboo/lib/Client.swift new file mode 100644 index 0000000..0be58e5 --- /dev/null +++ b/Graboo/lib/Client.swift @@ -0,0 +1,65 @@ +// +// Client.swift +// Graboo +// +// Created by James Shiffer on 6/13/22. +// + +import Foundation + +struct GelbooruSearchResults: Decodable { + let post: [GelbooruImage]; +} + +struct GelbooruImage: Decodable, Hashable { + let id: Int; + let fileUrl: String; + let width: Int; + let height: Int; + let previewWidth: Int; + let previewUrl: String; + let previewHeight: Int; + let sampleUrl: String; + let sampleWidth: Int; + let sampleHeight: Int; + let tags: String; + let rating: String; +} + +class GelbooruClient { + let baseUrl: String + + init(baseUrl: String = "https://gelbooru.com") { + self.baseUrl = baseUrl + } + + 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 + guard let data = data, error == nil else { + DispatchQueue.main.async { + completionHandler(nil, error) + } + 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 { + completionHandler(searchResults.post, nil) + } + } + task.resume() + } +}