From e80bcf3c2c2fa8ae66e1c82c9d79b67f6fbf73ae Mon Sep 17 00:00:00 2001 From: uttiya10 <56562649+uttiya10@users.noreply.github.com> Date: Thu, 30 Jul 2020 16:20:59 -0400 Subject: [PATCH] Add Genres into AnimeInformationScene The UI will now show an anime's genres. --- NineAnimator.xcodeproj/project.pbxproj | 8 ++ .../Base.lproj/AnimeInformation.storyboard | 100 ++++++++++++++++-- .../AnimeInformationTableViewController.swift | 13 ++- NineAnimator/Info.plist | 8 +- .../Anilist/Anilist+AnimeInformation.swift | 8 ++ .../ListingAnimeInformation.swift | 12 +++ .../MyAnimeList+AnimeInformation.swift | 9 +- .../GenresCollectionViewCell.swift | 29 +++++ .../GenresTableViewCell.swift | 62 +++++++++++ 9 files changed, 237 insertions(+), 12 deletions(-) create mode 100644 NineAnimator/Views/Anime Information Scene/GenresCollectionViewCell.swift create mode 100644 NineAnimator/Views/Anime Information Scene/GenresTableViewCell.swift diff --git a/NineAnimator.xcodeproj/project.pbxproj b/NineAnimator.xcodeproj/project.pbxproj index d833c7329..0d75de802 100644 --- a/NineAnimator.xcodeproj/project.pbxproj +++ b/NineAnimator.xcodeproj/project.pbxproj @@ -394,6 +394,8 @@ 65A1F44422726A0E00B43F85 /* OpenLoadParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65A1F44322726A0E00B43F85 /* OpenLoadParser.swift */; }; 65D061062445B31000E866DE /* MixdropParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65D061052445B31000E866DE /* MixdropParser.swift */; }; 65E1D9BC235FC12300234409 /* VeryStreamParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E1D9BB235FC12200234409 /* VeryStreamParser.swift */; }; + BF2B8F1924CFB6370039E505 /* GenresCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF2B8F1724CF4C070039E505 /* GenresCollectionViewCell.swift */; }; + BF2B8F1B24CFCA8A0039E505 /* GenresTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF2B8F1A24CFCA8A0039E505 /* GenresTableViewCell.swift */; }; C8E12CFF2422136000B0AF6A /* AnimeUnity.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E12CFE2422136000B0AF6A /* AnimeUnity.swift */; }; C8E12D01242213CB00B0AF6A /* AnimeUnity+Anime.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E12D00242213CB00B0AF6A /* AnimeUnity+Anime.swift */; }; C8E12D03242213E000B0AF6A /* AnimeUnity+Featured.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E12D02242213E000B0AF6A /* AnimeUnity+Featured.swift */; }; @@ -878,6 +880,8 @@ 65A1F44322726A0E00B43F85 /* OpenLoadParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenLoadParser.swift; sourceTree = ""; }; 65D061052445B31000E866DE /* MixdropParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MixdropParser.swift; sourceTree = ""; }; 65E1D9BB235FC12200234409 /* VeryStreamParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VeryStreamParser.swift; sourceTree = ""; }; + BF2B8F1724CF4C070039E505 /* GenresCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenresCollectionViewCell.swift; sourceTree = ""; }; + BF2B8F1A24CFCA8A0039E505 /* GenresTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenresTableViewCell.swift; sourceTree = ""; }; C8E12CFE2422136000B0AF6A /* AnimeUnity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimeUnity.swift; sourceTree = ""; }; C8E12D00242213CB00B0AF6A /* AnimeUnity+Anime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AnimeUnity+Anime.swift"; sourceTree = ""; }; C8E12D02242213E000B0AF6A /* AnimeUnity+Featured.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AnimeUnity+Featured.swift"; sourceTree = ""; }; @@ -1056,6 +1060,8 @@ 2C93BD46221F5E62000411CB /* InformationReferenceCollectionViewCell.swift */, 2C96DAE124562610007A29B0 /* InformationSceneSchedulesCell.swift */, 2C96DAE324562A2F007A29B0 /* InformationSceneAiringEpisodeCell.swift */, + BF2B8F1724CF4C070039E505 /* GenresCollectionViewCell.swift */, + BF2B8F1A24CFCA8A0039E505 /* GenresTableViewCell.swift */, ); path = "Anime Information Scene"; sourceTree = ""; @@ -2387,6 +2393,7 @@ 2C1C717322186EC900760A69 /* TrackingServiceTableViewController.swift in Sources */, 2C93BD43221E1FE8000411CB /* InformationSceneStatisticsTableViewCell.swift in Sources */, 2C8519AD22CC949500EEF3AC /* Kissanime+Search.swift in Sources */, + BF2B8F1B24CFCA8A0039E505 /* GenresTableViewCell.swift in Sources */, 2C6C20C723FADD19009FAFF5 /* UserDefaults+CodableValue.swift in Sources */, 2C8519C622D3202B00EEF3AC /* AnimeKisa+Episode.swift in Sources */, 2C1C71852219AFB700760A69 /* TrackingContext.swift in Sources */, @@ -2427,6 +2434,7 @@ 2C60EABC22676C4400280637 /* MyAnimeList+GenericPagedAnimeList.swift in Sources */, 2C4257812325366E00FCCCCE /* Simkl+Mutation.swift in Sources */, 2C35C966227E94B900D469E6 /* CalendarAnimeCell.swift in Sources */, + BF2B8F1924CFB6370039E505 /* GenresCollectionViewCell.swift in Sources */, 2CAD375A22261C5A008AD6B4 /* MyAnimeList+Reference.swift in Sources */, 2CEB56D721DBE53A0018D804 /* Log.swift in Sources */, 2C2C54F72215D47500BAA76E /* User+Subscriptions.swift in Sources */, diff --git a/NineAnimator/Base.lproj/AnimeInformation.storyboard b/NineAnimator/Base.lproj/AnimeInformation.storyboard index 7f35ce595..cdf0620e0 100644 --- a/NineAnimator/Base.lproj/AnimeInformation.storyboard +++ b/NineAnimator/Base.lproj/AnimeInformation.storyboard @@ -1,5 +1,5 @@ - + @@ -303,8 +303,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -425,7 +513,7 @@ - + @@ -492,7 +580,7 @@ - + @@ -605,7 +693,7 @@ - + @@ -686,7 +774,7 @@ - + diff --git a/NineAnimator/Controllers/Anime Information Scene/AnimeInformationTableViewController.swift b/NineAnimator/Controllers/Anime Information Scene/AnimeInformationTableViewController.swift index bdd47c9d1..421cebe20 100644 --- a/NineAnimator/Controllers/Anime Information Scene/AnimeInformationTableViewController.swift +++ b/NineAnimator/Controllers/Anime Information Scene/AnimeInformationTableViewController.swift @@ -179,7 +179,8 @@ class AnimeInformationTableViewController: UITableViewController, DontBotherView // Update table view tableView.reloadSections(Section.indexSet([ .information, - .synopsis + .synopsis, + .genres ]), with: .automatic) // Update heading view @@ -249,7 +250,7 @@ class AnimeInformationTableViewController: UITableViewController, DontBotherView // MARK: - Table view data source override func numberOfSections(in tableView: UITableView) -> Int { - 6 + 7 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { @@ -259,6 +260,7 @@ class AnimeInformationTableViewController: UITableViewController, DontBotherView switch Section(rawValue: section)! { case .information: return enumeratedInformationList.count + 1 case .synopsis: return 1 + case .genres: return 2 case .airingSchedule: return _airingSchedules?.isEmpty == false ? 2 : 0 case .characters: return characterList?.isEmpty == false ? 2 : 0 case .statistics: return _statistics == nil ? 0 : 2 @@ -285,6 +287,7 @@ class AnimeInformationTableViewController: UITableViewController, DontBotherView // Assign header value switch section { + case .genres: cell.headingText = "Genres" case .information: cell.headingText = "Information" case .characters: cell.headingText = "Characters" case .statistics: cell.headingText = "Ratings & Statistics" @@ -299,6 +302,10 @@ class AnimeInformationTableViewController: UITableViewController, DontBotherView let itemIndex = indexPath.item - 1 switch section { + case .genres: + let cell = tableView.dequeueReusableCell(withIdentifier: "anime.genres", for: indexPath) as! GenresTableViewCell + cell.initialize(information.genres) + return cell case .information: let cell = tableView.dequeueReusableCell(withIdentifier: "anime.information", for: indexPath) cell.textLabel?.text = enumeratedInformationList[itemIndex].name @@ -727,6 +734,8 @@ fileprivate extension AnimeInformationTableViewController { enum Section: Int, Equatable { case synopsis = 0 + case genres + case airingSchedule case statistics diff --git a/NineAnimator/Info.plist b/NineAnimator/Info.plist index 8c873e70d..866cd76ff 100644 --- a/NineAnimator/Info.plist +++ b/NineAnimator/Info.plist @@ -25,6 +25,8 @@ CFBundleExecutable $(EXECUTABLE_NAME) + CFBundleExecutableID + a5b732ba924edf9dcdf0fbfbd3ca353be3acbc1b9b17dce3dfa65b43a013a3c0 CFBundleIcons CFBundleAlternateIcons @@ -103,7 +105,9 @@ com.marcuszhou.nineanimator.activity.continueEpisode UIApplicationRegistrationData - b6TFpcxX + + b6TFpcxX + UIBackgroundModes audio @@ -131,8 +135,6 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - CFBundleExecutableID - a5b732ba924edf9dcdf0fbfbd3ca353be3acbc1b9b17dce3dfa65b43a013a3c0 UTExportedTypeDeclarations diff --git a/NineAnimator/Models/Anime Listing Service/Anilist/Anilist+AnimeInformation.swift b/NineAnimator/Models/Anime Listing Service/Anilist/Anilist+AnimeInformation.swift index 2efb12364..637688f0e 100644 --- a/NineAnimator/Models/Anime Listing Service/Anilist/Anilist+AnimeInformation.swift +++ b/NineAnimator/Models/Anime Listing Service/Anilist/Anilist+AnimeInformation.swift @@ -29,6 +29,7 @@ extension Anilist { var siteUrl: URL var description: String var information: [String: String] + var genres: [ListingAnimeGenre] var futureAiringSchedules: NineAnimatorPromise<[ListingAiringEpisode]> { .success(_airingEpisodes) @@ -100,6 +101,13 @@ extension Anilist { self.description = "No synopsis found for this title." } + self.genres = try mediaEntry.value(at: "genres", type: [String].self).map { + ListingAnimeGenre( + name: $0, + id: nil + ) + } + self.wallpapers = [] if let bannerImageString = mediaEntry.valueIfPresent(at: "bannerImage", type: String.self), let url = URL(string: bannerImageString) { diff --git a/NineAnimator/Models/Anime Listing Service/ListingAnimeInformation.swift b/NineAnimator/Models/Anime Listing Service/ListingAnimeInformation.swift index a9c450cdb..4103210e1 100644 --- a/NineAnimator/Models/Anime Listing Service/ListingAnimeInformation.swift +++ b/NineAnimator/Models/Anime Listing Service/ListingAnimeInformation.swift @@ -69,6 +69,15 @@ struct ListingAnimeStatistics { let episodesCount: Int? } +/// Representing the genres of an anime +struct ListingAnimeGenre { + /// The name of the genre + let name: String + + /// Optional ID of the genre + let id: Int? +} + /// Representing the ratings struct ListingAnimeReview { /// The author of the review @@ -115,6 +124,9 @@ protocol ListingAnimeInformation { /// A list of information that is displayed in the information section var information: [String: String] { get } + /// A list of the anime's genres + var genres: [ListingAnimeGenre] { get } + // Promisified and need-based information /// Retrieve the list of characters (2D) in the anime diff --git a/NineAnimator/Models/Anime Listing Service/MyAnimeList/MyAnimeList+AnimeInformation.swift b/NineAnimator/Models/Anime Listing Service/MyAnimeList/MyAnimeList+AnimeInformation.swift index a72cfead8..82d0d375b 100644 --- a/NineAnimator/Models/Anime Listing Service/MyAnimeList/MyAnimeList+AnimeInformation.swift +++ b/NineAnimator/Models/Anime Listing Service/MyAnimeList/MyAnimeList+AnimeInformation.swift @@ -28,6 +28,7 @@ extension MyAnimeList { var siteUrl: URL var description: String var information: [String: String] + var genres: [ListingAnimeGenre] private var _meanRatings: Double? private var _numRatings: Int? @@ -108,6 +109,12 @@ extension MyAnimeList { self.wallpapers = [ preferredArtwork ] self.siteUrl = try URL(string: "https://myanimelist.net/anime/\(reference.uniqueIdentifier)").tryUnwrap(.urlError) self.description = try animeEntry.value(at: "synopsis", type: String.self) + self.genres = try animeEntry.value(at: "genres", type: [NSDictionary].self).map { + ListingAnimeGenre( + name: $0["name"] as! String, + id: $0["id"] as? Int + ) + } // Decode additional information var animeInformation = [String: CustomStringConvertible]() @@ -146,7 +153,7 @@ extension MyAnimeList { func listingAnime(from reference: ListingAnimeReference) -> NineAnimatorPromise { apiRequest("/anime/\(reference.uniqueIdentifier)", query: [ - "fields": "alternative_titles,average_episode_duration,broadcast,created_at,end_date,main_picture,mean,media_type,nsfw,num_scoring_users,popularity,rank,synopsis,title,background,related_anime,related_anime{node{my_list_status{start_date,finish_date}}},num_episodes,start_date" + "fields": "alternative_titles,average_episode_duration,broadcast,created_at,end_date,main_picture,mean,media_type,nsfw,num_scoring_users,popularity,rank,synopsis,genres,title,background,related_anime,related_anime{node{my_list_status{start_date,finish_date}}},num_episodes,start_date" ]).then { response in guard let animeEntry = response.data.first else { diff --git a/NineAnimator/Views/Anime Information Scene/GenresCollectionViewCell.swift b/NineAnimator/Views/Anime Information Scene/GenresCollectionViewCell.swift new file mode 100644 index 000000000..9764c2e59 --- /dev/null +++ b/NineAnimator/Views/Anime Information Scene/GenresCollectionViewCell.swift @@ -0,0 +1,29 @@ +// +// This file is part of the NineAnimator project. +// +// Copyright © 2018-2020 Marcus Zhou. All rights reserved. +// +// NineAnimator is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NineAnimator is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with NineAnimator. If not, see . +// + +import Foundation +import UIKit + +class GenresCollectionViewCell: UICollectionViewCell { + @IBOutlet private weak var genreLabel: UILabel! + + func initialize(_ label: ListingAnimeGenre) { + genreLabel.text = label.name + } +} diff --git a/NineAnimator/Views/Anime Information Scene/GenresTableViewCell.swift b/NineAnimator/Views/Anime Information Scene/GenresTableViewCell.swift new file mode 100644 index 000000000..425590134 --- /dev/null +++ b/NineAnimator/Views/Anime Information Scene/GenresTableViewCell.swift @@ -0,0 +1,62 @@ +// +// This file is part of the NineAnimator project. +// +// Copyright © 2018-2020 Marcus Zhou. All rights reserved. +// +// NineAnimator is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NineAnimator is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with NineAnimator. If not, see . +// + +import Foundation +import UIKit + +class GenresTableViewCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { + @IBOutlet private weak var collectionView: UICollectionView! + private var genres = [ListingAnimeGenre]() + + override func awakeFromNib() { + super.awakeFromNib() + + collectionView.delegate = self + collectionView.dataSource = self + } + + func initialize(_ genres: [ListingAnimeGenre]) { + self.genres = genres + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + genres.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "genres.chip", for: indexPath) as! GenresCollectionViewCell + cell.initialize(genres[indexPath.item]) + return cell + } + + //Ensure the cell's width is greater then the UILabel's width + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + let string = genres[indexPath.item].name + let stringWidth = textWidth(font: UIFont.systemFont(ofSize: 14, weight: UIFont.Weight.regular), text: string) + //Add 15 padding to the width + return CGSize(width: stringWidth + 15, height: 28) + } + + //If theres is a better way to calculate the width of a string, please tell me. + private func textWidth(font: UIFont, text: String) -> CGFloat { + let rect = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) + let labelSize = text.boundingRect(with: rect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil) + return ceil(labelSize.width) + } +}