diff --git a/AnimeGen.xcodeproj/project.pbxproj b/AnimeGen.xcodeproj/project.pbxproj index 6314f73d..4870fa64 100644 --- a/AnimeGen.xcodeproj/project.pbxproj +++ b/AnimeGen.xcodeproj/project.pbxproj @@ -30,6 +30,7 @@ 13C1E03C2BE9068600A27DEE /* AboutPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C1E03B2BE9068600A27DEE /* AboutPage.swift */; }; 13C1E03E2BE9183400A27DEE /* Credits-Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13C1E03D2BE9183400A27DEE /* Credits-Images.xcassets */; }; 13C1E0402BE92BB300A27DEE /* APIsCredits.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C1E03F2BE92BB300A27DEE /* APIsCredits.swift */; }; + 13C861652C033DE70066DC71 /* nekos-life.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13C861642C033DE70066DC71 /* nekos-life.swift */; }; 13D4FBE02BE7661A00BC3D1C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 13D4FBDF2BE7661A00BC3D1C /* Main.storyboard */; }; 13D4FBE62BE7689500BC3D1C /* Tags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13D4FBE52BE7689400BC3D1C /* Tags.swift */; }; 13D4FBE92BE768C100BC3D1C /* HeartButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13D4FBE82BE768C100BC3D1C /* HeartButton.swift */; }; @@ -86,6 +87,7 @@ 13C1E03B2BE9068600A27DEE /* AboutPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutPage.swift; sourceTree = ""; }; 13C1E03D2BE9183400A27DEE /* Credits-Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Credits-Images.xcassets"; sourceTree = ""; }; 13C1E03F2BE92BB300A27DEE /* APIsCredits.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIsCredits.swift; sourceTree = ""; }; + 13C861642C033DE70066DC71 /* nekos-life.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "nekos-life.swift"; sourceTree = ""; }; 13D4FBDF2BE7661A00BC3D1C /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 13D4FBE52BE7689400BC3D1C /* Tags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tags.swift; sourceTree = ""; }; 13D4FBE82BE768C100BC3D1C /* HeartButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeartButton.swift; sourceTree = ""; }; @@ -208,6 +210,7 @@ 13D4FC122BE770EB00BC3D1C /* waifu-it.swift */, 13D4FC152BE770EC00BC3D1C /* waifu-pics.swift */, 13A8496A2BF479DA009A9442 /* n-sfw.swift */, + 13C861642C033DE70066DC71 /* nekos-life.swift */, ); path = "APIs Requests"; sourceTree = ""; @@ -384,6 +387,7 @@ 13D4FC1E2BE770EC00BC3D1C /* waifu-im.swift in Sources */, 13D4FC1D2BE770EC00BC3D1C /* waifu-it.swift in Sources */, 1393C3AA2BEBC94300704137 /* Dev-History.swift in Sources */, + 13C861652C033DE70066DC71 /* nekos-life.swift in Sources */, 13D4FC1C2BE770EC00BC3D1C /* purr.swift in Sources */, 13D4FC292BE7710500BC3D1C /* HmtaiReader.swift in Sources */, 136BD7E82BE7E24100ED23AE /* Developer.swift in Sources */, diff --git a/AnimeGen.xcodeproj/project.xcworkspace/xcuserdata/Francesco.xcuserdatad/UserInterfaceState.xcuserstate b/AnimeGen.xcodeproj/project.xcworkspace/xcuserdata/Francesco.xcuserdatad/UserInterfaceState.xcuserstate index 0558d8bf..a2f8f824 100644 Binary files a/AnimeGen.xcodeproj/project.xcworkspace/xcuserdata/Francesco.xcuserdatad/UserInterfaceState.xcuserstate and b/AnimeGen.xcodeproj/project.xcworkspace/xcuserdata/Francesco.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/AnimeGen/APIs Requests/nekos-life.swift b/AnimeGen/APIs Requests/nekos-life.swift new file mode 100644 index 00000000..df79e7ba --- /dev/null +++ b/AnimeGen/APIs Requests/nekos-life.swift @@ -0,0 +1,111 @@ +// +// nekos-life.swift +// AnimeGen +// +// Created by cranci on 26/05/24. +// + +import UIKit +import SDWebImage + +extension ViewController { + + func loadImageFromNekosLife() { + startLoadingIndicator() + + let startTime = DispatchTime.now() + + DispatchQueue.global().async { [weak self] in + guard let self = self else { return } + + let endpointPrefix = "https://nekos.life/api/v2/img/" + let categories = ["tickle", "slap", "pat", "neko", "kiss", "hug", "fox_girl", "feed", "cuddle", "ngif", "smug", "wallpaper", "gecg", "avatar", "waifu"] + + let randomIndex = Int(arc4random_uniform(UInt32(categories.count))) + let randomCategory = categories[randomIndex] + + guard let url = URL(string: "\(endpointPrefix)\(randomCategory)") else { + print("Invalid URL") + DispatchQueue.main.async { + self.stopLoadingIndicator() + } + return + } + + let request = URLRequest(url: url) + + self.fetchImage(with: request, startTime: startTime, tag: randomCategory) + } + } + + private func fetchImage(with request: URLRequest, startTime: DispatchTime, tag: String) { + URLSession.shared.dataTask(with: request) { [weak self] (data, response, error) in + guard let self = self else { return } + + if let error = error { + print("Error: \(error)") + DispatchQueue.main.async { + self.stopLoadingIndicator() + } + return + } + + guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { + print("Invalid HTTP response") + DispatchQueue.main.async { + self.stopLoadingIndicator() + } + return + } + + guard let data = data, + let jsonResponse = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], + let imageUrlString = jsonResponse["url"] as? String else { + print("Invalid image data or missing response headers") + DispatchQueue.main.async { + self.stopLoadingIndicator() + } + return + } + + DispatchQueue.main.async { + let isGif = imageUrlString.hasSuffix(".gif") + + if isGif { + self.imageView.sd_setImage(with: URL(string: imageUrlString), placeholderImage: nil, options: .allowInvalidSSLCertificates, context: nil, progress: nil, completed: { (image, error, cacheType, url) in + if let error = error { + print("Error loading GIF image: \(error)") + } else { + self.handleImageLoadingCompletion(with: image ?? UIImage(), tags: [tag], imageUrlString: imageUrlString) + } + }) + } else { + self.imageView.sd_setImage(with: URL(string: imageUrlString), placeholderImage: nil, options: [], context: nil, progress: nil, completed: { (image, error, cacheType, url) in + if let error = error { + print("Error loading image: \(error)") + } else { + self.handleImageLoadingCompletion(with: image ?? UIImage(), tags: [tag], imageUrlString: imageUrlString) + } + }) + } + + let endTime = DispatchTime.now() + let executionTime = endTime.uptimeNanoseconds - startTime.uptimeNanoseconds + print("Execution time: \(Double(executionTime) / 1_000_000_000) seconds") + } + }.resume() + } + + private func handleImageLoadingCompletion(with newImage: UIImage, tags: [String], imageUrlString: String) { + addImageToHistory(image: newImage, tags: tags) + currentImageURL = imageUrlString + updateUIWithTags(tags) + addToHistory(image: newImage) + tagsLabel.isHidden = false + imageView.image = newImage + animateImageChange(with: newImage) + stopLoadingIndicator() + incrementCounter() + } +} + diff --git a/AnimeGen/Buttons func/Refresh-API-Button.swift b/AnimeGen/Buttons func/Refresh-API-Button.swift index 3c610f7d..39cbed83 100644 --- a/AnimeGen/Buttons func/Refresh-API-Button.swift +++ b/AnimeGen/Buttons func/Refresh-API-Button.swift @@ -42,6 +42,8 @@ extension ViewController { loadImageFromNekoBot() case "n-sfw.com": loadImageFromNSFW() + case "nekos.life": + loadImageFromNekosLife() default: print("Unknown API: \(title)") } @@ -64,11 +66,11 @@ extension ViewController { let apiOptions: [String] if #available(iOS 14.0, *) { - apiOptions = developerAPIs ? ["Purr", "kyoko", "n-sfw.com", "NekoBot", "nekos.moe", "Nekos api", "nekos.best", "Hmtai api", "waifu.it", "waifu.pics", "waifu.im", "pic.re"] - : ["Purr", "n-sfw.com", "NekoBot", "nekos.moe", "Nekos api", "nekos.best", "waifu.pics", "waifu.im", "pic.re"] + apiOptions = developerAPIs ? ["Purr", "kyoko", "n-sfw.com", "nekos.life", "NekoBot", "nekos.moe", "Nekos api", "nekos.best", "Hmtai api", "waifu.it", "waifu.pics", "waifu.im", "pic.re"] + : ["Purr", "n-sfw.com", "nekos.life", "NekoBot", "nekos.moe", "Nekos api", "nekos.best", "waifu.pics", "waifu.im", "pic.re"] } else { - apiOptions = developerAPIs ? ["Purr", "kyoko", "n-sfw.com", "NekoBot", "nekos.moe", "nekos.best", "Hmtai api", "waifu.it", "waifu.pics", "waifu.im"] - : ["Purr", "n-sfw.com", "NekoBot", "nekos.moe", "nekos.best", "waifu.pics", "waifu.im"] + apiOptions = developerAPIs ? ["Purr", "kyoko", "n-sfw.com", "nekos.life", "NekoBot", "nekos.moe", "nekos.best", "Hmtai api", "waifu.it", "waifu.pics", "waifu.im"] + : ["Purr", "n-sfw.com", "nekos.life", "NekoBot", "nekos.moe", "nekos.best", "waifu.pics", "waifu.im"] } apiOptions.forEach { option in diff --git a/AnimeGen/ImageSettings.swift b/AnimeGen/ImageSettings.swift index 919ebe24..2d3f0100 100644 --- a/AnimeGen/ImageSettings.swift +++ b/AnimeGen/ImageSettings.swift @@ -22,7 +22,7 @@ final class Settings { static let imageHeight: Double = 0.6 } - private let queue = DispatchQueue(label: "com.example.SettingsQueue", attributes: .concurrent) + private let queue = DispatchQueue(label: "me.cranci.SettingsQueue", attributes: .concurrent) var imageWidth: Double { get { diff --git a/AnimeGen/Main.storyboard b/AnimeGen/Main.storyboard index 40c78c6d..eaf2ad57 100644 --- a/AnimeGen/Main.storyboard +++ b/AnimeGen/Main.storyboard @@ -427,8 +427,7 @@ - - Display Activity Label: Choose to show or hide an activity control label displaying session time and images generated. (App Restart is required) + @@ -462,7 +461,7 @@ - Enable Gestures: Enable to utilize app gestures: (App Restart is required) + Utilize app gestures: (App Restart is required) - Swipe Left to Right: Last Image - Swipe Right to Left: Generate New Image @@ -506,7 +505,7 @@ Image Height: Manage the height of an image App Restart is required for both! - + @@ -534,7 +533,7 @@ App Restart is required for both! - + @@ -1294,6 +1293,9 @@ along with AnimeGen. If not, see <https://www.gnu.org/licenses/>. + + + @@ -1948,6 +1950,43 @@ Also Thanks to all the active users of the TestFlight beta which are always help + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2019,6 +2058,11 @@ Also Thanks to all the active users of the TestFlight beta which are always help + + + + + @@ -2104,6 +2148,7 @@ Also Thanks to all the active users of the TestFlight beta which are always help + diff --git a/AnimeGen/Settings/Credits/APIsCredits.swift b/AnimeGen/Settings/Credits/APIsCredits.swift index 02cfb2f6..16118b69 100644 --- a/AnimeGen/Settings/Credits/APIsCredits.swift +++ b/AnimeGen/Settings/Credits/APIsCredits.swift @@ -22,6 +22,7 @@ class APIsCredits: UITableViewController { let kyoko = "https://api.rei.my.id/docs/ANIME/WAIFU-Generator/" let purrbot = "https://purrbot.site/" let nsfw = "https://n-sfw.com/" + let nekoslife = "https://nekos.life" override func viewDidLoad() { super.viewDidLoad() @@ -80,5 +81,9 @@ class APIsCredits: UITableViewController { @IBAction func nsfwa(_ sender: UITapGestureRecognizer) { openURL(nsfw) } + + @IBAction func nekoslife(_ sender: UITapGestureRecognizer) { + openURL(nekoslife) + } } diff --git a/AnimeGen/Settings/Credits/Credits-Images.xcassets/apis/nekos.life.imageset/34457007.png b/AnimeGen/Settings/Credits/Credits-Images.xcassets/apis/nekos.life.imageset/34457007.png new file mode 100644 index 00000000..0500b10c Binary files /dev/null and b/AnimeGen/Settings/Credits/Credits-Images.xcassets/apis/nekos.life.imageset/34457007.png differ diff --git a/AnimeGen/Settings/Credits/Credits-Images.xcassets/apis/nekos.life.imageset/Contents.json b/AnimeGen/Settings/Credits/Credits-Images.xcassets/apis/nekos.life.imageset/Contents.json new file mode 100644 index 00000000..11091dcc --- /dev/null +++ b/AnimeGen/Settings/Credits/Credits-Images.xcassets/apis/nekos.life.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "34457007.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AnimeGen/ViewController.swift b/AnimeGen/ViewController.swift index b92d50a4..08c8f4bb 100644 --- a/AnimeGen/ViewController.swift +++ b/AnimeGen/ViewController.swift @@ -270,7 +270,8 @@ class ViewController: UIViewController { "Purr": loadImageFromPurr, "NekoBot": loadImageFromNekoBot, "Hmtai": startHmtaiLoader, - "n-sfw.com": loadImageFromNSFW + "n-sfw.com": loadImageFromNSFW, + "nekos.life": loadImageFromNekosLife ] apiLoaders[title]?() diff --git a/README.md b/README.md index a47ab122..6182d0b5 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ Without them this project wouldn't exist, so thanks very much to all of them! | [Kyoko](https://api.rei.my.id/docs/ANIME/WAIFU-Generator/) | SFW/NSFW | IMG/GIF | :x: | | [Purr Bot](https://purrbot.site/) | SFW/NSFW | IMG/GIF | ✅ | | [n-sfw api](https://n-sfw.com/) | SFW/NSFW | IMG/GIF | ✅ | +| [nekos.life](https://nekos.life) | SFW/NSFW | IMG/GIF | ✅ | > [!Note] > The Hmtai api is not fully supported, [why?](https://github.com/cranci1/AnimeGen/blob/main/Privacy/Hmtai.md)