From 462f75abfb236e274ceca49be49af554389a87aa Mon Sep 17 00:00:00 2001 From: Maximilian Bauer Date: Sun, 15 May 2022 10:05:48 +0200 Subject: [PATCH] Artist/Albums/Songs: sort by rating --- Amperfy/Screens/ViewController/AlbumsVC.swift | 63 +++++++++++++++++-- .../Screens/ViewController/ArtistsVC.swift | 61 +++++++++++++++++- .../BasicTableViewController.swift | 8 +-- Amperfy/Screens/ViewController/SearchVC.swift | 6 +- Amperfy/Screens/ViewController/SongsVC.swift | 63 +++++++++++++++++-- .../BasicFetchedResultsController.swift | 43 ++++++++++++- .../Storage/FetchedResultsControllers.swift | 43 ++++++++++--- .../AlbumMO+CoreDataClass.swift | 10 +++ .../ArtistMO+CoreDataClass.swift | 10 +++ .../ManagedObjects/SongMO+CoreDataClass.swift | 10 +++ Amperfy/Storage/PersistentStorage.swift | 28 +++++++++ 11 files changed, 313 insertions(+), 32 deletions(-) diff --git a/Amperfy/Screens/ViewController/AlbumsVC.swift b/Amperfy/Screens/ViewController/AlbumsVC.swift index 7263ce56..342b5c9d 100644 --- a/Amperfy/Screens/ViewController/AlbumsVC.swift +++ b/Amperfy/Screens/ViewController/AlbumsVC.swift @@ -5,21 +5,23 @@ class AlbumsVC: SingleFetchedResultsTableViewController { private var fetchedResultsController: AlbumFetchedResultsController! private var filterButton: UIBarButtonItem! + private var sortButton: UIBarButtonItem! private var displayFilter: DisplayCategoryFilter = .all + private var sortType: ElementSortType = .name override func viewDidLoad() { super.viewDidLoad() appDelegate.userStatistics.visited(.albums) - fetchedResultsController = AlbumFetchedResultsController(managedObjectContext: appDelegate.persistentStorage.context, isGroupedInAlphabeticSections: true) - singleFetchedResultsController = fetchedResultsController + change(sortType: appDelegate.persistentStorage.settings.albumsSortSetting) configureSearchController(placeholder: "Search in \"Albums\"", scopeButtonTitles: ["All", "Cached"], showSearchBarAtEnter: false) tableView.register(nibName: GenericTableCell.typeName) tableView.rowHeight = GenericTableCell.rowHeight filterButton = UIBarButtonItem(image: UIImage.filter, style: .plain, target: self, action: #selector(filterButtonPressed)) - navigationItem.rightBarButtonItem = filterButton + sortButton = UIBarButtonItem(image: UIImage.sort, style: .plain, target: self, action: #selector(sortButtonPressed)) + navigationItem.rightBarButtonItems = [filterButton, sortButton] self.refreshControl?.addTarget(self, action: #selector(Self.handleRefresh), for: UIControl.Event.valueChanged) containableAtIndexPathCallback = { (indexPath) in @@ -33,6 +35,17 @@ class AlbumsVC: SingleFetchedResultsTableViewController { } } + func change(sortType: ElementSortType) { + self.sortType = sortType + appDelegate.persistentStorage.settings.albumsSortSetting = sortType + singleFetchedResultsController?.clearResults() + tableView.reloadData() + fetchedResultsController = AlbumFetchedResultsController(managedObjectContext: appDelegate.persistentStorage.context, sortType: sortType, isGroupedInAlphabeticSections: true) + fetchedResultsController.fetchResultsController.sectionIndexType = sortType == .rating ? .rating : .alphabet + singleFetchedResultsController = fetchedResultsController + tableView.reloadData() + } + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) updateFilterButton() @@ -49,6 +62,28 @@ class AlbumsVC: SingleFetchedResultsTableViewController { return cell } + override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + switch sortType { + case .name: + return 0.0 + case .rating: + return CommonScreenOperations.tableSectionHeightLarge + } + } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + switch sortType { + case .name: + return super.tableView(tableView, titleForHeaderInSection: section) + case .rating: + if let sectionNameInitial = super.tableView(tableView, titleForHeaderInSection: section), sectionNameInitial != SectionIndexType.noRatingIndexSymbol { + return "\(sectionNameInitial) Star\(sectionNameInitial != "1" ? "s" : "")" + } else { + return "Not rated" + } + } + } + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let album = fetchedResultsController.getWrappedEntity(at: indexPath) performSegue(withIdentifier: Segues.toAlbumDetail.rawValue, sender: album) @@ -74,7 +109,27 @@ class AlbumsVC: SingleFetchedResultsTableViewController { fetchedResultsController.search(searchText: searchText, onlyCached: searchController.searchBar.selectedScopeButtonIndex == 1, displayFilter: displayFilter) tableView.reloadData() } - + + @objc private func sortButtonPressed() { + let alert = UIAlertController(title: "Albums sorting", message: nil, preferredStyle: .actionSheet) + if sortType != .name { + alert.addAction(UIAlertAction(title: "Sort by name", style: .default, handler: { _ in + self.change(sortType: .name) + self.updateSearchResults(for: self.searchController) + })) + } + if sortType != .rating { + alert.addAction(UIAlertAction(title: "Sort by rating", style: .default, handler: { _ in + self.change(sortType: .rating) + self.updateSearchResults(for: self.searchController) + })) + } + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) + alert.pruneNegativeWidthConstraintsToAvoidFalseConstraintWarnings() + alert.setOptionsForIPadToDisplayPopupCentricIn(view: self.view) + present(alert, animated: true, completion: nil) + } + @objc private func filterButtonPressed() { let alert = UIAlertController(title: "Albums filter", message: nil, preferredStyle: .actionSheet) diff --git a/Amperfy/Screens/ViewController/ArtistsVC.swift b/Amperfy/Screens/ViewController/ArtistsVC.swift index 682b6281..2e3ad213 100644 --- a/Amperfy/Screens/ViewController/ArtistsVC.swift +++ b/Amperfy/Screens/ViewController/ArtistsVC.swift @@ -5,21 +5,23 @@ class ArtistsVC: SingleFetchedResultsTableViewController { private var fetchedResultsController: ArtistFetchedResultsController! private var filterButton: UIBarButtonItem! + private var sortButton: UIBarButtonItem! private var displayFilter: DisplayCategoryFilter = .all + private var sortType: ElementSortType = .name override func viewDidLoad() { super.viewDidLoad() appDelegate.userStatistics.visited(.artists) - fetchedResultsController = ArtistFetchedResultsController(managedObjectContext: appDelegate.persistentStorage.context, isGroupedInAlphabeticSections: true) - singleFetchedResultsController = fetchedResultsController + change(sortType: appDelegate.persistentStorage.settings.artistsSortSetting) configureSearchController(placeholder: "Search in \"Artists\"", scopeButtonTitles: ["All", "Cached"], showSearchBarAtEnter: false) tableView.register(nibName: GenericTableCell.typeName) tableView.rowHeight = GenericTableCell.rowHeight filterButton = UIBarButtonItem(image: UIImage.filter, style: .plain, target: self, action: #selector(filterButtonPressed)) - navigationItem.rightBarButtonItem = filterButton + sortButton = UIBarButtonItem(image: UIImage.sort, style: .plain, target: self, action: #selector(sortButtonPressed)) + navigationItem.rightBarButtonItems = [filterButton, sortButton] self.refreshControl?.addTarget(self, action: #selector(Self.handleRefresh), for: UIControl.Event.valueChanged) containableAtIndexPathCallback = { (indexPath) in @@ -33,6 +35,17 @@ class ArtistsVC: SingleFetchedResultsTableViewController { } } + func change(sortType: ElementSortType) { + self.sortType = sortType + appDelegate.persistentStorage.settings.artistsSortSetting = sortType + singleFetchedResultsController?.clearResults() + tableView.reloadData() + fetchedResultsController = ArtistFetchedResultsController(managedObjectContext: appDelegate.persistentStorage.context, sortType: sortType, isGroupedInAlphabeticSections: true) + fetchedResultsController.fetchResultsController.sectionIndexType = sortType == .rating ? .rating : .alphabet + singleFetchedResultsController = fetchedResultsController + tableView.reloadData() + } + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) updateFilterButton() @@ -49,6 +62,28 @@ class ArtistsVC: SingleFetchedResultsTableViewController { return cell } + override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + switch sortType { + case .name: + return 0.0 + case .rating: + return CommonScreenOperations.tableSectionHeightLarge + } + } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + switch sortType { + case .name: + return super.tableView(tableView, titleForHeaderInSection: section) + case .rating: + if let sectionNameInitial = super.tableView(tableView, titleForHeaderInSection: section), sectionNameInitial != SectionIndexType.noRatingIndexSymbol { + return "\(sectionNameInitial) Star\(sectionNameInitial != "1" ? "s" : "")" + } else { + return "Not rated" + } + } + } + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let artist = fetchedResultsController.getWrappedEntity(at: indexPath) performSegue(withIdentifier: Segues.toArtistDetail.rawValue, sender: artist) @@ -75,6 +110,26 @@ class ArtistsVC: SingleFetchedResultsTableViewController { tableView.reloadData() } + @objc private func sortButtonPressed() { + let alert = UIAlertController(title: "Artists sorting", message: nil, preferredStyle: .actionSheet) + if sortType != .name { + alert.addAction(UIAlertAction(title: "Sort by name", style: .default, handler: { _ in + self.change(sortType: .name) + self.updateSearchResults(for: self.searchController) + })) + } + if sortType != .rating { + alert.addAction(UIAlertAction(title: "Sort by rating", style: .default, handler: { _ in + self.change(sortType: .rating) + self.updateSearchResults(for: self.searchController) + })) + } + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) + alert.pruneNegativeWidthConstraintsToAvoidFalseConstraintWarnings() + alert.setOptionsForIPadToDisplayPopupCentricIn(view: self.view) + present(alert, animated: true, completion: nil) + } + @objc private func filterButtonPressed() { let alert = UIAlertController(title: "Artists filter", message: nil, preferredStyle: .actionSheet) diff --git a/Amperfy/Screens/ViewController/BasicTableViewController.swift b/Amperfy/Screens/ViewController/BasicTableViewController.swift index b867a9bf..442a5696 100644 --- a/Amperfy/Screens/ViewController/BasicTableViewController.swift +++ b/Amperfy/Screens/ViewController/BasicTableViewController.swift @@ -257,12 +257,8 @@ extension BasicTableViewController: NSFetchedResultsControllerDelegate { case .delete: tableView.deleteRows(at: [indexPath!], with: .left) case .move: - if indexPath! != newIndexPath! { - tableView.insertRows(at: [newIndexPath!], with: .bottom) - tableView.deleteRows(at: [indexPath!], with: .left) - } else if !isEditLockedDueToActiveSwipe { - tableView.reloadRows(at: [indexPath!], with: .none) - } + tableView.insertRows(at: [newIndexPath!], with: .bottom) + tableView.deleteRows(at: [indexPath!], with: .left) case .update: if !isEditLockedDueToActiveSwipe { tableView.reloadRows(at: [indexPath!], with: .none) diff --git a/Amperfy/Screens/ViewController/SearchVC.swift b/Amperfy/Screens/ViewController/SearchVC.swift index cf36c818..c08c73fa 100644 --- a/Amperfy/Screens/ViewController/SearchVC.swift +++ b/Amperfy/Screens/ViewController/SearchVC.swift @@ -13,11 +13,11 @@ class SearchVC: BasicTableViewController { playlistFetchedResultsController = PlaylistFetchedResultsController(managedObjectContext: appDelegate.persistentStorage.context, sortType: .name, isGroupedInAlphabeticSections: false) playlistFetchedResultsController.delegate = self - artistFetchedResultsController = ArtistFetchedResultsController(managedObjectContext: appDelegate.persistentStorage.context, isGroupedInAlphabeticSections: false) + artistFetchedResultsController = ArtistFetchedResultsController(managedObjectContext: appDelegate.persistentStorage.context, sortType: .name, isGroupedInAlphabeticSections: false) artistFetchedResultsController.delegate = self - albumFetchedResultsController = AlbumFetchedResultsController(managedObjectContext: appDelegate.persistentStorage.context, isGroupedInAlphabeticSections: false) + albumFetchedResultsController = AlbumFetchedResultsController(managedObjectContext: appDelegate.persistentStorage.context, sortType: .name, isGroupedInAlphabeticSections: false) albumFetchedResultsController.delegate = self - songFetchedResultsController = SongsFetchedResultsController(managedObjectContext: appDelegate.persistentStorage.context, isGroupedInAlphabeticSections: false) + songFetchedResultsController = SongsFetchedResultsController(managedObjectContext: appDelegate.persistentStorage.context, sortType: .name, isGroupedInAlphabeticSections: false) songFetchedResultsController.delegate = self configureSearchController(placeholder: "Playlists, Songs and more", scopeButtonTitles: ["All", "Cached"]) diff --git a/Amperfy/Screens/ViewController/SongsVC.swift b/Amperfy/Screens/ViewController/SongsVC.swift index 5da00996..e59c38b1 100644 --- a/Amperfy/Screens/ViewController/SongsVC.swift +++ b/Amperfy/Screens/ViewController/SongsVC.swift @@ -6,14 +6,15 @@ class SongsVC: SingleFetchedResultsTableViewController { private var fetchedResultsController: SongsFetchedResultsController! private var optionsButton: UIBarButtonItem! private var filterButton: UIBarButtonItem! + private var sortButton: UIBarButtonItem! private var displayFilter: DisplayCategoryFilter = .all + private var sortType: ElementSortType = .name override func viewDidLoad() { super.viewDidLoad() appDelegate.userStatistics.visited(.songs) - fetchedResultsController = SongsFetchedResultsController(managedObjectContext: appDelegate.persistentStorage.context, isGroupedInAlphabeticSections: true) - singleFetchedResultsController = fetchedResultsController + change(sortType: appDelegate.persistentStorage.settings.songsSortSetting) configureSearchController(placeholder: "Search in \"Songs\"", scopeButtonTitles: ["All", "Cached"], showSearchBarAtEnter: false) tableView.register(nibName: SongTableCell.typeName) @@ -21,6 +22,7 @@ class SongsVC: SingleFetchedResultsTableViewController { optionsButton = UIBarButtonItem(image: UIImage.ellipsis, style: .plain, target: self, action: #selector(optionsPressed)) filterButton = UIBarButtonItem(image: UIImage.filter, style: .plain, target: self, action: #selector(filterButtonPressed)) + sortButton = UIBarButtonItem(image: UIImage.sort, style: .plain, target: self, action: #selector(sortButtonPressed)) self.refreshControl?.addTarget(self, action: #selector(Self.handleRefresh), for: UIControl.Event.valueChanged) containableAtIndexPathCallback = { (indexPath) in @@ -33,13 +35,24 @@ class SongsVC: SingleFetchedResultsTableViewController { } } + func change(sortType: ElementSortType) { + self.sortType = sortType + appDelegate.persistentStorage.settings.songsSortSetting = sortType + singleFetchedResultsController?.clearResults() + tableView.reloadData() + fetchedResultsController = SongsFetchedResultsController(managedObjectContext: appDelegate.persistentStorage.context, sortType: sortType, isGroupedInAlphabeticSections: true) + fetchedResultsController.fetchResultsController.sectionIndexType = sortType == .rating ? .rating : .alphabet + singleFetchedResultsController = fetchedResultsController + tableView.reloadData() + } + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) updateFilterButton() if appDelegate.persistentStorage.settings.isOnlineMode { - navigationItem.rightBarButtonItems = [optionsButton, filterButton] + navigationItem.rightBarButtonItems = [optionsButton, filterButton, sortButton] } else { - navigationItem.rightBarButtonItems = [filterButton] + navigationItem.rightBarButtonItems = [filterButton, sortButton] } } @@ -54,6 +67,28 @@ class SongsVC: SingleFetchedResultsTableViewController { return cell } + override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + switch sortType { + case .name: + return 0.0 + case .rating: + return CommonScreenOperations.tableSectionHeightLarge + } + } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + switch sortType { + case .name: + return super.tableView(tableView, titleForHeaderInSection: section) + case .rating: + if let sectionNameInitial = super.tableView(tableView, titleForHeaderInSection: section), sectionNameInitial != SectionIndexType.noRatingIndexSymbol { + return "\(sectionNameInitial) Star\(sectionNameInitial != "1" ? "s" : "")" + } else { + return "Not rated" + } + } + } + func convertIndexPathToPlayContext(songIndexPath: IndexPath) -> PlayContext? { let song = fetchedResultsController.getWrappedEntity(at: songIndexPath) return PlayContext(containable: song) @@ -83,6 +118,26 @@ class SongsVC: SingleFetchedResultsTableViewController { tableView.reloadData() } + @objc private func sortButtonPressed() { + let alert = UIAlertController(title: "Songs sorting", message: nil, preferredStyle: .actionSheet) + if sortType != .name { + alert.addAction(UIAlertAction(title: "Sort by name", style: .default, handler: { _ in + self.change(sortType: .name) + self.updateSearchResults(for: self.searchController) + })) + } + if sortType != .rating { + alert.addAction(UIAlertAction(title: "Sort by rating", style: .default, handler: { _ in + self.change(sortType: .rating) + self.updateSearchResults(for: self.searchController) + })) + } + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) + alert.pruneNegativeWidthConstraintsToAvoidFalseConstraintWarnings() + alert.setOptionsForIPadToDisplayPopupCentricIn(view: self.view) + present(alert, animated: true, completion: nil) + } + @objc private func filterButtonPressed() { let alert = UIAlertController(title: "Songs filter", message: nil, preferredStyle: .actionSheet) diff --git a/Amperfy/Storage/BasicFetchedResultsController.swift b/Amperfy/Storage/BasicFetchedResultsController.swift index 7e79e017..c8e1e849 100644 --- a/Amperfy/Storage/BasicFetchedResultsController.swift +++ b/Amperfy/Storage/BasicFetchedResultsController.swift @@ -18,13 +18,33 @@ extension NSFetchedResultsController { } } +enum SectionIndexType: Int { + case alphabet = 0 + case rating = 1 + + static let defaultValue: SectionIndexType = .alphabet + static let noRatingIndexSymbol = "#" +} + class CustomSectionIndexFetchedResultsController: NSFetchedResultsController { - public init(fetchRequest: NSFetchRequest, managedObjectContext context: NSManagedObjectContext, sectionNameKeyPath: String?, cacheName name: String?) { + var sectionIndexType: SectionIndexType + + public init(fetchRequest: NSFetchRequest, managedObjectContext context: NSManagedObjectContext, sectionNameKeyPath: String?, cacheName name: String?, sectionIndexType: SectionIndexType = .defaultValue) { + self.sectionIndexType = sectionIndexType super.init(fetchRequest: fetchRequest as! NSFetchRequest, managedObjectContext: context, sectionNameKeyPath: sectionNameKeyPath, cacheName: name) } override func sectionIndexTitle(forSectionName sectionName: String) -> String? { + switch sectionIndexType { + case .alphabet: + return sortByAlphabet(forSectionName: sectionName) + case .rating: + return sortByRating(forSectionName: sectionName) + } + } + + private func sortByAlphabet(forSectionName sectionName: String) -> String? { guard sectionName.count > 0 else { return "?" } let initial = String(sectionName.prefix(1).folding(options: .diacriticInsensitive, locale: nil).uppercased()) if let _ = initial.rangeOfCharacter(from: CharacterSet.decimalDigits) { @@ -37,7 +57,20 @@ class CustomSectionIndexFetchedResultsController String? { + guard sectionName.count > 0 else { return SectionIndexType.noRatingIndexSymbol } + let initial = String(sectionName.prefix(1)) + switch initial { + case "5": return "5" + case "4": return "4" + case "3": return "3" + case "2": return "2" + case "1": return "1" + default: return SectionIndexType.noRatingIndexSymbol + } + } + } class BasicFetchedResultsController: NSObject where ResultType : NSFetchRequestResult { @@ -221,6 +254,7 @@ class CachedFetchedResultsController: BasicFetchedResultsController< var keepAllResultsUpdated = true private let allFetchResulsController: CustomSectionIndexFetchedResultsController private let searchFetchResulsController: CustomSectionIndexFetchedResultsController + private var sortType: ElementSortType private var delegateInternal: NSFetchedResultsControllerDelegate? override var delegate: NSFetchedResultsControllerDelegate? { @@ -239,10 +273,13 @@ class CachedFetchedResultsController: BasicFetchedResultsController< get { return isSearchActiveInternal } } - override init(managedObjectContext context: NSManagedObjectContext, fetchRequest: NSFetchRequest, isGroupedInAlphabeticSections: Bool) { + init(managedObjectContext context: NSManagedObjectContext, fetchRequest: NSFetchRequest, sortType: ElementSortType = .defaultValue, isGroupedInAlphabeticSections: Bool) { + self.sortType = sortType let sectionNameKeyPath: String? = isGroupedInAlphabeticSections ? fetchRequest.sortDescriptors![0].key : nil allFetchResulsController = CustomSectionIndexFetchedResultsController(fetchRequest: fetchRequest.copy() as! NSFetchRequest, managedObjectContext: context, sectionNameKeyPath: sectionNameKeyPath, cacheName: Self.typeName) + allFetchResulsController.sectionIndexType = sortType == .rating ? .rating : .alphabet searchFetchResulsController = CustomSectionIndexFetchedResultsController(fetchRequest: fetchRequest.copy() as! NSFetchRequest, managedObjectContext: context, sectionNameKeyPath: sectionNameKeyPath, cacheName: nil) + searchFetchResulsController.sectionIndexType = sortType == .rating ? .rating : .alphabet super.init(managedObjectContext: context, fetchRequest: fetchRequest, isGroupedInAlphabeticSections: isGroupedInAlphabeticSections) fetchResultsController = allFetchResulsController } diff --git a/Amperfy/Storage/FetchedResultsControllers.swift b/Amperfy/Storage/FetchedResultsControllers.swift index 6613769f..7b9bd439 100644 --- a/Amperfy/Storage/FetchedResultsControllers.swift +++ b/Amperfy/Storage/FetchedResultsControllers.swift @@ -1,6 +1,13 @@ import Foundation import CoreData +enum ElementSortType: Int { + case name = 0 + case rating = 1 + + static let defaultValue: ElementSortType = .name +} + enum PlaylistSortType: Int { case name = 0 case lastPlayed = 1 @@ -228,14 +235,20 @@ class GenreSongsFetchedResultsController: BasicFetchedResultsController class ArtistFetchedResultsController: CachedFetchedResultsController { - init(managedObjectContext context: NSManagedObjectContext, isGroupedInAlphabeticSections: Bool) { + init(managedObjectContext context: NSManagedObjectContext, sortType: ElementSortType, isGroupedInAlphabeticSections: Bool) { let library = LibraryStorage(context: context) - let fetchRequest = ArtistMO.identifierSortedFetchRequest + var fetchRequest = ArtistMO.identifierSortedFetchRequest + switch sortType { + case .name: + fetchRequest = ArtistMO.identifierSortedFetchRequest + case .rating: + fetchRequest = ArtistMO.ratingSortedFetchRequest + } fetchRequest.predicate = NSCompoundPredicate(orPredicateWithSubpredicates: [ AbstractLibraryEntityMO.excludeRemoteDeleteFetchPredicate, library.getFetchPredicate(onlyCachedArtists: true) ]) - super.init(managedObjectContext: context, fetchRequest: fetchRequest, isGroupedInAlphabeticSections: isGroupedInAlphabeticSections) + super.init(managedObjectContext: context, fetchRequest: fetchRequest, sortType: sortType, isGroupedInAlphabeticSections: isGroupedInAlphabeticSections) } func search(searchText: String, onlyCached: Bool, displayFilter: DisplayCategoryFilter) { @@ -333,14 +346,20 @@ class ArtistSongsItemsFetchedResultsController: BasicFetchedResultsController { - init(managedObjectContext context: NSManagedObjectContext, isGroupedInAlphabeticSections: Bool) { + init(managedObjectContext context: NSManagedObjectContext, sortType: ElementSortType, isGroupedInAlphabeticSections: Bool) { let library = LibraryStorage(context: context) - let fetchRequest = AlbumMO.identifierSortedFetchRequest + var fetchRequest = AlbumMO.identifierSortedFetchRequest + switch sortType { + case .name: + fetchRequest = AlbumMO.identifierSortedFetchRequest + case .rating: + fetchRequest = AlbumMO.ratingSortedFetchRequest + } fetchRequest.predicate = NSCompoundPredicate(orPredicateWithSubpredicates: [ AbstractLibraryEntityMO.excludeRemoteDeleteFetchPredicate, library.getFetchPredicate(onlyCachedAlbums: true) ]) - super.init(managedObjectContext: context, fetchRequest: fetchRequest, isGroupedInAlphabeticSections: isGroupedInAlphabeticSections) + super.init(managedObjectContext: context, fetchRequest: fetchRequest, sortType: sortType, isGroupedInAlphabeticSections: isGroupedInAlphabeticSections) } func search(searchText: String, onlyCached: Bool, displayFilter: DisplayCategoryFilter) { @@ -364,10 +383,16 @@ class AlbumFetchedResultsController: CachedFetchedResultsController { class SongsFetchedResultsController: CachedFetchedResultsController { - init(managedObjectContext context: NSManagedObjectContext, isGroupedInAlphabeticSections: Bool) { - let fetchRequest = SongMO.identifierSortedFetchRequest + init(managedObjectContext context: NSManagedObjectContext, sortType: ElementSortType, isGroupedInAlphabeticSections: Bool) { + var fetchRequest = SongMO.identifierSortedFetchRequest + switch sortType { + case .name: + fetchRequest = SongMO.identifierSortedFetchRequest + case .rating: + fetchRequest = SongMO.ratingSortedFetchRequest + } fetchRequest.predicate = SongMO.excludeServerDeleteUncachedSongsFetchPredicate - super.init(managedObjectContext: context, fetchRequest: fetchRequest, isGroupedInAlphabeticSections: isGroupedInAlphabeticSections) + super.init(managedObjectContext: context, fetchRequest: fetchRequest, sortType: sortType, isGroupedInAlphabeticSections: isGroupedInAlphabeticSections) keepAllResultsUpdated = false } diff --git a/Amperfy/Storage/ManagedObjects/AlbumMO+CoreDataClass.swift b/Amperfy/Storage/ManagedObjects/AlbumMO+CoreDataClass.swift index 2bc991f5..89120c8e 100644 --- a/Amperfy/Storage/ManagedObjects/AlbumMO+CoreDataClass.swift +++ b/Amperfy/Storage/ManagedObjects/AlbumMO+CoreDataClass.swift @@ -16,6 +16,16 @@ public final class AlbumMO: AbstractLibraryEntityMO { ] return fetchRequest } + + static var ratingSortedFetchRequest: NSFetchRequest { + let fetchRequest: NSFetchRequest = AlbumMO.fetchRequest() + fetchRequest.sortDescriptors = [ + NSSortDescriptor(key: #keyPath(AlbumMO.rating), ascending: false), + NSSortDescriptor(key: Self.identifierKeyString, ascending: true, selector: #selector(NSString.localizedCaseInsensitiveCompare)), + NSSortDescriptor(key: #keyPath(AlbumMO.id), ascending: true, selector: #selector(NSString.caseInsensitiveCompare)) + ] + return fetchRequest + } } diff --git a/Amperfy/Storage/ManagedObjects/ArtistMO+CoreDataClass.swift b/Amperfy/Storage/ManagedObjects/ArtistMO+CoreDataClass.swift index eb39e55f..32480d23 100644 --- a/Amperfy/Storage/ManagedObjects/ArtistMO+CoreDataClass.swift +++ b/Amperfy/Storage/ManagedObjects/ArtistMO+CoreDataClass.swift @@ -12,6 +12,16 @@ extension ArtistMO: CoreDataIdentifyable { return \ArtistMO.name } + static var ratingSortedFetchRequest: NSFetchRequest { + let fetchRequest: NSFetchRequest = ArtistMO.fetchRequest() + fetchRequest.sortDescriptors = [ + NSSortDescriptor(key: #keyPath(ArtistMO.rating), ascending: false), + NSSortDescriptor(key: Self.identifierKeyString, ascending: true, selector: #selector(NSString.localizedCaseInsensitiveCompare)), + NSSortDescriptor(key: #keyPath(ArtistMO.id), ascending: true, selector: #selector(NSString.caseInsensitiveCompare)) + ] + return fetchRequest + } + func passOwnership(to targetArtist: ArtistMO) { let albumsCopy = albums?.compactMap{ $0 as? AlbumMO } albumsCopy?.forEach{ diff --git a/Amperfy/Storage/ManagedObjects/SongMO+CoreDataClass.swift b/Amperfy/Storage/ManagedObjects/SongMO+CoreDataClass.swift index 95b00bb9..8be1338c 100644 --- a/Amperfy/Storage/ManagedObjects/SongMO+CoreDataClass.swift +++ b/Amperfy/Storage/ManagedObjects/SongMO+CoreDataClass.swift @@ -32,4 +32,14 @@ extension SongMO: CoreDataIdentifyable { return fetchRequest } + static var ratingSortedFetchRequest: NSFetchRequest { + let fetchRequest: NSFetchRequest = SongMO.fetchRequest() + fetchRequest.sortDescriptors = [ + NSSortDescriptor(key: #keyPath(SongMO.rating), ascending: false), + NSSortDescriptor(key: Self.identifierKeyString, ascending: true, selector: #selector(NSString.localizedCaseInsensitiveCompare)), + NSSortDescriptor(key: #keyPath(SongMO.id), ascending: true, selector: #selector(NSString.caseInsensitiveCompare)) + ] + return fetchRequest + } + } diff --git a/Amperfy/Storage/PersistentStorage.swift b/Amperfy/Storage/PersistentStorage.swift index adf35d6a..20f7ded2 100644 --- a/Amperfy/Storage/PersistentStorage.swift +++ b/Amperfy/Storage/PersistentStorage.swift @@ -58,6 +58,9 @@ class PersistentStorage { case SwipeLeadingActionSettings = "swipeLeadingActionSettings" case SwipeTrailingActionSettings = "swipeTrailingActionSettings" case PlaylistsSortSetting = "playlistsSortSetting" + case ArtistsSortSetting = "artistsSortSetting" + case AlbumsSortSetting = "albumsSortSetting" + case SongsSortSetting = "songsSortSetting" case PodcastsShowSetting = "podcastsShowSetting" case PlayerDisplayStyle = "playerDisplayStyle" case IsOfflineMode = "isOfflineMode" @@ -93,6 +96,31 @@ class PersistentStorage { set { UserDefaults.standard.set(newValue.rawValue, forKey: UserDefaultsKey.PlaylistsSortSetting.rawValue) } } + var artistsSortSetting: ElementSortType { + get { + let artistsSortSettingRaw = UserDefaults.standard.object(forKey: UserDefaultsKey.ArtistsSortSetting.rawValue) as? Int ?? ElementSortType.defaultValue.rawValue + return ElementSortType(rawValue: artistsSortSettingRaw) ?? ElementSortType.defaultValue + } + set { UserDefaults.standard.set(newValue.rawValue, forKey: UserDefaultsKey.ArtistsSortSetting.rawValue) } + } + + + var albumsSortSetting: ElementSortType { + get { + let albumsSortSettingRaw = UserDefaults.standard.object(forKey: UserDefaultsKey.AlbumsSortSetting.rawValue) as? Int ?? ElementSortType.defaultValue.rawValue + return ElementSortType(rawValue: albumsSortSettingRaw) ?? ElementSortType.defaultValue + } + set { UserDefaults.standard.set(newValue.rawValue, forKey: UserDefaultsKey.AlbumsSortSetting.rawValue) } + } + + var songsSortSetting: ElementSortType { + get { + let songsSortSettingRaw = UserDefaults.standard.object(forKey: UserDefaultsKey.SongsSortSetting.rawValue) as? Int ?? ElementSortType.defaultValue.rawValue + return ElementSortType(rawValue: songsSortSettingRaw) ?? ElementSortType.defaultValue + } + set { UserDefaults.standard.set(newValue.rawValue, forKey: UserDefaultsKey.SongsSortSetting.rawValue) } + } + var swipeActionSettings: SwipeActionSettings { get { guard let swipeLeadingActionsRaw = UserDefaults.standard.object(forKey: UserDefaultsKey.SwipeLeadingActionSettings.rawValue) as? [Int],