SubmarinerApp / Submariner

A Subsonic client for macOS
https://submarinerapp.com
BSD 3-Clause "New" or "Revised" License
122 stars 3 forks source link

[feature] Sort albums by date #187

Closed notjosh closed 5 months ago

notjosh commented 5 months ago

Is your feature request related to a problem? Please describe.

Hiya! I've stumbled on Submariner recently, and it's really good, so thanks for your hard work!

When browsing releases by artist and artist, it'd be great to (optionally?) sort by release date, so that the older releases are shown first. For example Navidrome sorts by max_year and date to do this (ref), and afaict isn't configurable.

Describe the solution you'd like

Simplest is just updating the sort predicate for the albumsController, I guess.

Describe alternatives you've considered

If alphabetical sorting is still required/desired, this can be configurable - either globally via prefpane, or as a "right click->sort by" kind of thing.

Additional context

Sorting is obviously a huuuuge bag of edge cases, so I think keeping it naive/simple is fine, and just reverting to alphabetical sort when necessary.

The year should probably be displayed next to the title, so that sorting is intuitively obvious when looking at it.

I'm happy to contribute the feature if you want some help :)

NattyNarwhal commented 5 months ago

I'm on vacation right now, but some notes: Year is not an album property in Subsonic's API, but a track one. Annoying, but you could add a year as a derived attribute for the album in Core Data - you could derive the year from the first track or so. Then it's just a normal attribute you can sort against with a controller. This would probably lead to some inconsistencies i.e. a track doesn't have a year defined, or if albums are being loaded in for the first time and don't have tracks associated yet, but you could fall back to the current behaviour with a sort descriptor array (year first, then sort by name).

NattyNarwhal commented 5 months ago

I have a preliminary patch. It's a little ugly, because for some reason I can't get derived properties working correctly, so I just made a property here. Probably a bit janky because of that, but it might work out for you.

diff --git a/Submariner/SBAlbum.swift b/Submariner/SBAlbum.swift
index 5af3468..d991350 100644
--- a/Submariner/SBAlbum.swift
+++ b/Submariner/SBAlbum.swift
@@ -54,6 +54,18 @@ public class SBAlbum: SBMusicItem {
         return 0
     }

+    // #MARK: - Derived Attributes
+    
+    // XXX: This is better off living in Core Data, but adding a derived attribute
+    // (once you get past the other issues) causes loading the new model version
+    // to fail with "Cannot migrate store in-place".
+    //
+    // Because of this, it seems a little janky (the old order might be kept, might
+    // need to click albums to refresh the sorting properly?)
+    @objc dynamic var year: NSNumber? {
+        (self.tracks as? Set<SBTrack>)?.first?.year
+    }
+    
     // #MARK: - Core Data insert compatibility shim

     @objc(insertInManagedObjectContext:) class func insertInManagedObjectContext(context: NSManagedObjectContext) -> SBAlbum {
diff --git a/Submariner/SBMusicController.m b/Submariner/SBMusicController.m
index b1a63dc..46bc2d4 100644
--- a/Submariner/SBMusicController.m
+++ b/Submariner/SBMusicController.m
@@ -65,8 +65,9 @@ - (id)initWithManagedObjectContext:(NSManagedObjectContext *)context {
         NSSortDescriptor *artistDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"itemName" ascending:YES];
         artistSortDescriptor = [NSArray arrayWithObject:artistDescriptor];

-        NSSortDescriptor *albumDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"itemName" ascending:YES];
-        albumSortDescriptor = @[albumDescriptor];
+        NSSortDescriptor *albumYearDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"year" ascending:YES];
+        NSSortDescriptor *albumNameDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"itemName" ascending:YES];
+        albumSortDescriptor = @[albumYearDescriptor, albumNameDescriptor];

         NSSortDescriptor *trackNumberDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"trackNumber" ascending:YES];
         NSSortDescriptor *discNumberDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"discNumber" ascending:YES];
diff --git a/Submariner/SBServerHomeController.m b/Submariner/SBServerHomeController.m
index 2a629e5..2335a90 100644
--- a/Submariner/SBServerHomeController.m
+++ b/Submariner/SBServerHomeController.m
@@ -84,8 +84,10 @@ - (id)initWithManagedObjectContext:(NSManagedObjectContext *)context {
     if (self) {
         scopeGroups = [[NSMutableArray alloc] init];

-        NSSortDescriptor *albumDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"itemName" ascending:YES];
-        albumSortDescriptor = @[albumDescriptor];
+        // XXX: Does it make sense to do a year sort for this view?
+        NSSortDescriptor *albumYearDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"year" ascending:YES];
+        NSSortDescriptor *albumNameDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"itemName" ascending:YES];
+        albumSortDescriptor = @[albumYearDescriptor, albumNameDescriptor];

         NSSortDescriptor *trackNumberDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"trackNumber" ascending:YES];
         NSSortDescriptor *discNumberDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"discNumber" ascending:YES];
diff --git a/Submariner/SBServerLibraryController.m b/Submariner/SBServerLibraryController.m
index 0d837b0..8cde6aa 100644
--- a/Submariner/SBServerLibraryController.m
+++ b/Submariner/SBServerLibraryController.m
@@ -82,9 +82,9 @@ - (id)initWithManagedObjectContext:(NSManagedObjectContext *)context {
         NSSortDescriptor *artistDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"itemName" ascending:YES];
         artistSortDescriptor = [NSArray arrayWithObject:artistDescriptor];

-        // XXX: Useful to change by i.e. year instead or alphabetical, but that's not a property of Album
-        NSSortDescriptor *albumDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"itemName" ascending:YES];
-        albumSortDescriptor = @[albumDescriptor];
+        NSSortDescriptor *albumYearDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"year" ascending:YES];
+        NSSortDescriptor *albumNameDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"itemName" ascending:YES];
+        albumSortDescriptor = @[albumYearDescriptor, albumNameDescriptor];

         NSSortDescriptor *trackNumberDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"trackNumber" ascending:YES];
         NSSortDescriptor *discNumberDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"discNumber" ascending:YES];

Let me know if you need a binary build to test with if don't/can't build yourself.