From 55f9d4c819d3d562875d17193af4f9e7e58e76f3 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Wed, 22 Jun 2022 11:08:58 -0600 Subject: [PATCH] ui: rework sorting again Rework Sort again into a new class that leverages a better Mode design and static comparator instances. This somewhat improves efficiency, but is also far easier to work with and has far less footguns with adding new sorts. --- CHANGELOG.md | 1 + .../auxio/detail/AlbumDetailFragment.kt | 10 +- .../auxio/detail/ArtistDetailFragment.kt | 12 +- .../oxycblt/auxio/detail/DetailViewModel.kt | 3 +- .../auxio/detail/GenreDetailFragment.kt | 10 +- .../org/oxycblt/auxio/home/HomeFragment.kt | 18 +- .../auxio/home/list/AlbumListFragment.kt | 12 +- .../auxio/home/list/ArtistListFragment.kt | 8 +- .../auxio/home/list/GenreListFragment.kt | 8 +- .../auxio/home/list/SongListFragment.kt | 14 +- .../org/oxycblt/auxio/image/Components.kt | 4 +- .../java/org/oxycblt/auxio/music/Indexer.kt | 14 +- .../playback/system/MediaButtonReceiver.kt | 6 +- .../playback/system/MediaSessionComponent.kt | 10 +- .../auxio/playback/system/PlaybackService.kt | 4 +- .../oxycblt/auxio/search/SearchViewModel.kt | 2 +- .../org/oxycblt/auxio/settings/Settings.kt | 31 +- .../main/java/org/oxycblt/auxio/ui/Sort.kt | 516 +++++++++--------- .../org/oxycblt/auxio/util/FrameworkUtil.kt | 1 - .../oxycblt/auxio/widgets/WidgetComponent.kt | 4 +- app/src/main/res/values/settings.xml | 6 +- 21 files changed, 346 insertions(+), 348 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6115e4ee5..22a4359cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ design guidelines - Migrated preferences from shared object to utility - Removed 2.0.0 compat code - Updated ExoPlayer to 2.18.0 +- Reworked sorting to be even more efficient ## v2.4.0 diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt index d671dd3fb..368e150b2 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -42,6 +42,7 @@ import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.ui.Header import org.oxycblt.auxio.ui.Item import org.oxycblt.auxio.ui.MenuFragment +import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.util.applySpans import org.oxycblt.auxio.util.canScroll import org.oxycblt.auxio.util.collect @@ -143,11 +144,16 @@ class AlbumDetailFragment : override fun onShowSortMenu(anchor: View) { menu(anchor, R.menu.menu_album_sort) { val sort = detailModel.albumSort - requireNotNull(menu.findItem(sort.itemId)).isChecked = true + requireNotNull(menu.findItem(sort.mode.itemId)).isChecked = true requireNotNull(menu.findItem(R.id.option_sort_asc)).isChecked = sort.isAscending setOnMenuItemClickListener { item -> item.isChecked = !item.isChecked - detailModel.albumSort = requireNotNull(sort.assignId(item.itemId)) + detailModel.albumSort = + if (item.itemId == R.id.option_sort_asc) { + sort.withAscending(item.isChecked) + } else { + sort.withMode(requireNotNull(Sort.Mode.fromItemId(item.itemId))) + } true } } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt index d3812b8b5..1a3a78b1f 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -40,6 +40,7 @@ import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.ui.Header import org.oxycblt.auxio.ui.Item import org.oxycblt.auxio.ui.MenuFragment +import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.util.applySpans import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.collectImmediately @@ -138,11 +139,18 @@ class ArtistDetailFragment : override fun onShowSortMenu(anchor: View) { menu(anchor, R.menu.menu_artist_sort) { val sort = detailModel.artistSort - requireNotNull(menu.findItem(sort.itemId)).isChecked = true + requireNotNull(menu.findItem(sort.mode.itemId)).isChecked = true requireNotNull(menu.findItem(R.id.option_sort_asc)).isChecked = sort.isAscending setOnMenuItemClickListener { item -> item.isChecked = !item.isChecked - detailModel.artistSort = requireNotNull(sort.assignId(item.itemId)) + + detailModel.artistSort = + if (item.itemId == R.id.option_sort_asc) { + sort.withAscending(item.isChecked) + } else { + sort.withMode(requireNotNull(Sort.Mode.fromItemId(item.itemId))) + } + true } } diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt index 4831fbffe..1f5dd9a52 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt @@ -93,6 +93,7 @@ class DetailViewModel(application: Application) : var artistSort: Sort get() = settings.detailArtistSort set(value) { + logD(value) settings.detailArtistSort = value currentArtist.value?.let(::refreshArtistData) } @@ -233,7 +234,7 @@ class DetailViewModel(application: Application) : logD("Refreshing artist data") val data = mutableListOf(artist) data.add(Header(-2, R.string.lbl_albums)) - data.addAll(Sort.ByYear(false).albums(artist.albums)) + data.addAll(Sort(Sort.Mode.ByYear, false).albums(artist.albums)) data.add(SortHeader(-3, R.string.lbl_songs)) data.addAll(artistSort.songs(artist.songs)) _artistData.value = data.toList() diff --git a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt index f226bf563..e419881d8 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -41,6 +41,7 @@ import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.ui.Header import org.oxycblt.auxio.ui.Item import org.oxycblt.auxio.ui.MenuFragment +import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.util.applySpans import org.oxycblt.auxio.util.collect import org.oxycblt.auxio.util.collectImmediately @@ -139,11 +140,16 @@ class GenreDetailFragment : override fun onShowSortMenu(anchor: View) { menu(anchor, R.menu.menu_genre_sort) { val sort = detailModel.genreSort - requireNotNull(menu.findItem(sort.itemId)).isChecked = true + requireNotNull(menu.findItem(sort.mode.itemId)).isChecked = true requireNotNull(menu.findItem(R.id.option_sort_asc)).isChecked = sort.isAscending setOnMenuItemClickListener { item -> item.isChecked = !item.isChecked - detailModel.genreSort = requireNotNull(sort.assignId(item.itemId)) + detailModel.genreSort = + if (item.itemId == R.id.option_sort_asc) { + sort.withAscending(item.isChecked) + } else { + sort.withMode(requireNotNull(Sort.Mode.fromItemId(item.itemId))) + } true } } diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt index 4fc1d724a..2a0515b05 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -54,6 +54,7 @@ import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.MainNavigationAction import org.oxycblt.auxio.ui.NavigationViewModel +import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.collect @@ -65,7 +66,6 @@ import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logE import org.oxycblt.auxio.util.logTraceOrThrow import org.oxycblt.auxio.util.textSafe -import org.oxycblt.auxio.util.unlikelyToBeNull /** * The main "Launching Point" fragment of Auxio, allowing navigation to the detail views for each @@ -173,19 +173,17 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI R.id.option_sort_asc -> { item.isChecked = !item.isChecked homeModel.updateCurrentSort( - unlikelyToBeNull( - homeModel - .getSortForDisplay(homeModel.currentTab.value) - .ascending(item.isChecked))) + homeModel + .getSortForDisplay(homeModel.currentTab.value) + .withAscending(item.isChecked)) } else -> { // Sorting option was selected, mark it as selected and update the mode item.isChecked = true homeModel.updateCurrentSort( - unlikelyToBeNull( - homeModel - .getSortForDisplay(homeModel.currentTab.value) - .assignId(item.itemId))) + homeModel + .getSortForDisplay(homeModel.currentTab.value) + .withMode(requireNotNull(Sort.Mode.fromItemId(item.itemId)))) } } @@ -248,7 +246,7 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI val toHighlight = homeModel.getSortForDisplay(displayMode) for (option in sortMenu) { - if (option.itemId == toHighlight.itemId) { + if (option.itemId == toHighlight.mode.itemId) { option.isChecked = true } diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt index ad34fe8eb..53cfa95be 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/AlbumListFragment.kt @@ -56,21 +56,21 @@ class AlbumListFragment : HomeListFragment() { val album = homeModel.albums.value[pos] // Change how we display the popup depending on the mode. - return when (homeModel.getSortForDisplay(DisplayMode.SHOW_ALBUMS)) { + return when (homeModel.getSortForDisplay(DisplayMode.SHOW_ALBUMS).mode) { // By Name -> Use Name - is Sort.ByName -> album.sortName.first().uppercase() + is Sort.Mode.ByName -> album.sortName.first().uppercase() // By Artist -> Use Artist Name - is Sort.ByArtist -> album.artist.sortName?.run { first().uppercase() } + is Sort.Mode.ByArtist -> album.artist.sortName?.run { first().uppercase() } // Year -> Use Full Year - is Sort.ByYear -> album.year?.toString() + is Sort.Mode.ByYear -> album.year?.toString() // Duration -> Use formatted duration - is Sort.ByDuration -> album.durationSecs.formatDuration(false) + is Sort.Mode.ByDuration -> album.durationSecs.formatDuration(false) // Count -> Use song count - is Sort.ByCount -> album.songs.size.toString() + is Sort.Mode.ByCount -> album.songs.size.toString() // Unsupported sort, error gracefully else -> null diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt index 95e7bb1a1..4c761e26c 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/ArtistListFragment.kt @@ -56,15 +56,15 @@ class ArtistListFragment : HomeListFragment() { val artist = homeModel.artists.value[pos] // Change how we display the popup depending on the mode. - return when (homeModel.getSortForDisplay(DisplayMode.SHOW_ARTISTS)) { + return when (homeModel.getSortForDisplay(DisplayMode.SHOW_ARTISTS).mode) { // By Name -> Use Name - is Sort.ByName -> artist.sortName?.run { first().uppercase() } + is Sort.Mode.ByName -> artist.sortName?.run { first().uppercase() } // Duration -> Use formatted duration - is Sort.ByDuration -> artist.durationSecs.formatDuration(false) + is Sort.Mode.ByDuration -> artist.durationSecs.formatDuration(false) // Count -> Use song count - is Sort.ByCount -> artist.songs.size.toString() + is Sort.Mode.ByCount -> artist.songs.size.toString() // Unsupported sort, error gracefully else -> null diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt index b9015e628..1f35b0f08 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/GenreListFragment.kt @@ -56,15 +56,15 @@ class GenreListFragment : HomeListFragment() { val genre = homeModel.genres.value[pos] // Change how we display the popup depending on the mode. - return when (homeModel.getSortForDisplay(DisplayMode.SHOW_GENRES)) { + return when (homeModel.getSortForDisplay(DisplayMode.SHOW_GENRES).mode) { // By Name -> Use Name - is Sort.ByName -> genre.sortName?.run { first().uppercase() } + is Sort.Mode.ByName -> genre.sortName?.run { first().uppercase() } // Duration -> Use formatted duration - is Sort.ByDuration -> genre.durationSecs.formatDuration(false) + is Sort.Mode.ByDuration -> genre.durationSecs.formatDuration(false) // Count -> Use song count - is Sort.ByCount -> genre.songs.size.toString() + is Sort.Mode.ByCount -> genre.songs.size.toString() // Unsupported sort, error gracefully else -> null diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt index c2193315a..f03cd8567 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/list/SongListFragment.kt @@ -27,8 +27,8 @@ import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.Item import org.oxycblt.auxio.ui.MenuItemListener import org.oxycblt.auxio.ui.MonoAdapter -import org.oxycblt.auxio.ui.SongViewHolder import org.oxycblt.auxio.ui.Sort +import org.oxycblt.auxio.ui.SongViewHolder import org.oxycblt.auxio.ui.SyncBackingData import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.context @@ -60,21 +60,21 @@ class SongListFragment : HomeListFragment() { // Change how we display the popup depending on the mode. // Note: We don't use the more correct individual artist name here, as sorts are largely // based off the names of the parent objects and not the child objects. - return when (homeModel.getSortForDisplay(DisplayMode.SHOW_SONGS)) { + return when (homeModel.getSortForDisplay(DisplayMode.SHOW_SONGS).mode) { // Name -> Use name - is Sort.ByName -> song.sortName.first().uppercase() + is Sort.Mode.ByName -> song.sortName.first().uppercase() // Artist -> Use Artist Name - is Sort.ByArtist -> song.album.artist.sortName?.run { first().uppercase() } + is Sort.Mode.ByArtist -> song.album.artist.sortName?.run { first().uppercase() } // Album -> Use Album Name - is Sort.ByAlbum -> song.album.sortName.first().uppercase() + is Sort.Mode.ByAlbum -> song.album.sortName.first().uppercase() // Year -> Use Full Year - is Sort.ByYear -> song.album.year?.toString() + is Sort.Mode.ByYear -> song.album.year?.toString() // Duration -> Use formatted duration - is Sort.ByDuration -> song.durationSecs.formatDuration(false) + is Sort.Mode.ByDuration -> song.durationSecs.formatDuration(false) // Unsupported sort, error gracefully else -> null diff --git a/app/src/main/java/org/oxycblt/auxio/image/Components.kt b/app/src/main/java/org/oxycblt/auxio/image/Components.kt index 075180fc1..00d06fe67 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/Components.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/Components.kt @@ -87,7 +87,7 @@ private constructor( private val artist: Artist, ) : BaseFetcher() { override suspend fun fetch(): FetchResult? { - val albums = Sort.ByName(true).albums(artist.albums) + val albums = Sort(Sort.Mode.ByName, true).albums(artist.albums) val results = albums.mapAtMost(4) { album -> fetchArt(context, album) } return createMosaic(context, results, size) } @@ -116,7 +116,7 @@ private constructor( // albums normally. val artists = genre.songs.groupBy { it.album.artist.id }.keys val albums = - Sort.ByName(true).albums(genre.songs.groupBy { it.album }.keys).run { + Sort(Sort.Mode.ByName, true).albums(genre.songs.groupBy { it.album }.keys).run { if (artists.size > 4) { distinctBy { it.artist.rawName } } else { diff --git a/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt b/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt index 7c1c6e935..319cc333e 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt @@ -257,7 +257,7 @@ class Indexer { .toMutableList() // Ensure that sorting order is consistent so that grouping is also consistent. - Sort.ByName(true).songsInPlace(songs) + Sort(Sort.Mode.ByName, true).songsInPlace(songs) logD("Successfully built ${songs.size} songs in ${System.currentTimeMillis() - start}ms") @@ -287,16 +287,8 @@ class Indexer { // Use the song with the latest year as our metadata song. // This allows us to replicate the LAST_YEAR field, which is useful as it means that // weird years like "0" wont show up if there are alternatives. - // Note: Normally we could want to use something like maxByWith, but apparently - // that does not exist in the kotlin stdlib yet. - val comparator = Sort.NullableComparator() - var templateSong = albumSongs[0] - for (i in 1..albumSongs.lastIndex) { - val candidate = albumSongs[i] - if (comparator.compare(templateSong.track, candidate.track) < 0) { - templateSong = candidate - } - } + val templateSong = + albumSongs.maxWith(compareBy(Sort.Mode.NULLABLE_INT_COMPARATOR) { it._year }) albums.add( Album( diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt index 0b3e2950e..779227c11 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt @@ -37,8 +37,10 @@ class MediaButtonReceiver : BroadcastReceiver() { val playbackManager = PlaybackStateManager.getInstance() if (playbackManager.song != null) { // We have a song, so we can assume that the service will start a foreground state. - // At least, I hope. Again, *this is why we don't do this, I cannot describe how - // stupid this is with the state of foreground services on modern android* + // At least, I hope. Again, *this is why we don't do this*. I cannot describe how + // stupid this is with the state of foreground services on modern android. One + // wrong action at the wrong time will result in the app crashing, and there is + // nothing I can do about it. intent.component = ComponentName(context, PlaybackService::class.java) ContextCompat.startForegroundService(context, intent) } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt index 6e7f70cb4..c075f95fa 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt @@ -38,8 +38,8 @@ import org.oxycblt.auxio.util.logD /** * The component managing the [MediaSessionCompat] instance. * - * I really don't like how I have to do this, but until I can feasibly work with the ExoPlayer queue - * system using something like MediaSessionConnector is more or less impossible. + * I really don't like how I have to do this, but until I can work with the ExoPlayer queue system + * using something like MediaSessionConnector is more or less impossible. * * @author OxygenCobalt */ @@ -242,6 +242,12 @@ class MediaSessionComponent(private val context: Context, private val player: Pl val state = PlaybackStateCompat.Builder() .setActions(ACTIONS) + .addCustomAction( + PlaybackStateCompat.CustomAction.Builder( + PlaybackService.ACTION_INC_REPEAT_MODE, + context.getString(R.string.desc_change_repeat), + R.drawable.ic_remote_repeat_off) + .build()) .setBufferedPosition(player.bufferedPosition) state.setState(PlaybackStateCompat.STATE_NONE, player.bufferedPosition, 1.0f) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt index 080e233dc..edfb259f6 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt @@ -64,8 +64,8 @@ import org.oxycblt.auxio.widgets.WidgetProvider * - Headset management * - Widgets * - * This service relies on [PlaybackStateManager.Callback] and [SettingsManager.Callback], so - * therefore there's no need to bind to it to deliver commands. + * This service relies on [PlaybackStateManager.Callback] and [Settings.Callback], so therefore + * there's no need to bind to it to deliver commands. * * TODO: Android Auto * diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt index 93aec29eb..1e3d83d89 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt @@ -74,7 +74,7 @@ class SearchViewModel(application: Application) : // Searching can be quite expensive, so get on a co-routine viewModelScope.launch { - val sort = Sort.ByName(true) + val sort = Sort(Sort.Mode.ByName, true) val results = mutableListOf() // Note: a filter mode of null means to not filter at all. diff --git a/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt b/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt index 2cf91f5c4..466329072 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt @@ -239,7 +239,7 @@ class Settings(private val context: Context, private val callback: Callback? = n get() = Sort.fromIntCode( inner.getInt(context.getString(R.string.set_key_lib_songs_sort), Int.MIN_VALUE)) - ?: Sort.ByName(true) + ?: Sort(Sort.Mode.ByName, true) set(value) { inner.edit { putInt(context.getString(R.string.set_key_lib_songs_sort), value.intCode) @@ -252,7 +252,7 @@ class Settings(private val context: Context, private val callback: Callback? = n get() = Sort.fromIntCode( inner.getInt(context.getString(R.string.set_key_lib_albums_sort), Int.MIN_VALUE)) - ?: Sort.ByName(true) + ?: Sort(Sort.Mode.ByName, true) set(value) { inner.edit { putInt(context.getString(R.string.set_key_lib_albums_sort), value.intCode) @@ -265,7 +265,7 @@ class Settings(private val context: Context, private val callback: Callback? = n get() = Sort.fromIntCode( inner.getInt(context.getString(R.string.set_key_lib_artists_sort), Int.MIN_VALUE)) - ?: Sort.ByName(true) + ?: Sort(Sort.Mode.ByName, true) set(value) { inner.edit { putInt(context.getString(R.string.set_key_lib_artists_sort), value.intCode) @@ -278,7 +278,7 @@ class Settings(private val context: Context, private val callback: Callback? = n get() = Sort.fromIntCode( inner.getInt(context.getString(R.string.set_key_lib_genres_sort), Int.MIN_VALUE)) - ?: Sort.ByName(true) + ?: Sort(Sort.Mode.ByName, true) set(value) { inner.edit { putInt(context.getString(R.string.set_key_lib_genres_sort), value.intCode) @@ -291,19 +291,20 @@ class Settings(private val context: Context, private val callback: Callback? = n get() { var sort = Sort.fromIntCode( - inner.getInt(context.getString(R.string.set_key_lib_album_sort), Int.MIN_VALUE)) - ?: Sort.ByDisc(true) + inner.getInt( + context.getString(R.string.set_key_detail_album_sort), Int.MIN_VALUE)) + ?: Sort(Sort.Mode.ByDisc, true) // Correct legacy album sort modes to Disc - if (sort is Sort.ByName) { - sort = Sort.ByDisc(sort.isAscending) + if (sort.mode is Sort.Mode.ByName) { + sort = sort.withMode(Sort.Mode.ByDisc) } return sort } set(value) { inner.edit { - putInt(context.getString(R.string.set_key_lib_album_sort), value.intCode) + putInt(context.getString(R.string.set_key_detail_album_sort), value.intCode) apply() } } @@ -312,11 +313,11 @@ class Settings(private val context: Context, private val callback: Callback? = n var detailArtistSort: Sort get() = Sort.fromIntCode( - inner.getInt(context.getString(R.string.set_key_lib_artist_sort), Int.MIN_VALUE)) - ?: Sort.ByYear(false) + inner.getInt(context.getString(R.string.set_key_detail_artist_sort), Int.MIN_VALUE)) + ?: Sort(Sort.Mode.ByYear, false) set(value) { inner.edit { - putInt(context.getString(R.string.set_key_lib_artists_sort), value.intCode) + putInt(context.getString(R.string.set_key_detail_artist_sort), value.intCode) apply() } } @@ -325,11 +326,11 @@ class Settings(private val context: Context, private val callback: Callback? = n var detailGenreSort: Sort get() = Sort.fromIntCode( - inner.getInt(context.getString(R.string.set_key_lib_genre_sort), Int.MIN_VALUE)) - ?: Sort.ByName(true) + inner.getInt(context.getString(R.string.set_key_detail_genre_sort), Int.MIN_VALUE)) + ?: Sort(Sort.Mode.ByName, true) set(value) { inner.edit { - putInt(context.getString(R.string.set_key_lib_genre_sort), value.intCode) + putInt(context.getString(R.string.set_key_detail_genre_sort), value.intCode) apply() } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt b/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt index 35bb14d8c..b34532216 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/Sort.kt @@ -18,6 +18,7 @@ package org.oxycblt.auxio.ui import androidx.annotation.IdRes +import kotlin.UnsupportedOperationException import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.R import org.oxycblt.auxio.music.Album @@ -25,14 +26,14 @@ import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Song -import org.oxycblt.auxio.util.logEOrThrow /** * A data class representing the sort modes used in Auxio. * * Sorting can be done by Name, Artist, Album, and others. Sorting of names is always * case-insensitive and article-aware. Certain datatypes may only support a subset of sorts since - * certain sorts cannot be easily applied to them (For Example, [Artist] and [ByYear] or [ByAlbum]). + * certain sorts cannot be easily applied to them (For Example, [Mode.ByArtist] and [Mode.ByYear] or + * [Mode.ByAlbum]). * * Internally, sorts are saved as an integer in the following format * @@ -42,14 +43,13 @@ import org.oxycblt.auxio.util.logEOrThrow * representing whether this sort is ascending or descending. * * @author OxygenCobalt - * - * TODO: Make comparators static instances - * - * TODO: Separate sort mode and ascending state */ -sealed class Sort(open val isAscending: Boolean) { - protected abstract val sortIntCode: Int - abstract val itemId: Int +data class Sort(val mode: Mode, val isAscending: Boolean) { + fun withAscending(new: Boolean) = Sort(mode, new) + fun withMode(new: Mode) = Sort(new, isAscending) + + val intCode: Int + get() = mode.intCode.shl(1) or if (isAscending) 1 else 0 fun songs(songs: Collection): List { val mutable = songs.toMutableList() @@ -75,336 +75,312 @@ sealed class Sort(open val isAscending: Boolean) { return mutable } - open fun songsInPlace(songs: MutableList) { - logEOrThrow("This sort is not supported for songs") + fun songsInPlace(songs: MutableList) { + songs.sortWith(mode.getSongComparator(isAscending)) } - open fun albumsInPlace(albums: MutableList) { - logEOrThrow("This sort is not supported for albums") + fun albumsInPlace(albums: MutableList) { + albums.sortWith(mode.getAlbumComparator(isAscending)) } - open fun artistsInPlace(artists: MutableList) { - logEOrThrow("This sort is not supported for artists") + fun artistsInPlace(artists: MutableList) { + artists.sortWith(mode.getArtistComparator(isAscending)) } - open fun genresInPlace(genres: MutableList) { - logEOrThrow("This sort is not supported for genres") + fun genresInPlace(genres: MutableList) { + genres.sortWith(mode.getGenreComparator(isAscending)) } - /** - * Apply [newIsAscending] to the status of this sort. - * @return A new [Sort] with the value of [newIsAscending] applied. - */ - abstract fun ascending(newIsAscending: Boolean): Sort + sealed class Mode { + abstract val intCode: Int + abstract val itemId: Int - /** Sort by the names of an item */ - class ByName(override val isAscending: Boolean) : Sort(isAscending) { - override val sortIntCode: Int - get() = IntegerTable.SORT_BY_NAME - - override val itemId: Int - get() = R.id.option_sort_name - - override fun songsInPlace(songs: MutableList) { - songs.sortWith(compareByDynamic(NameComparator()) { it }) + open fun getSongComparator(ascending: Boolean): Comparator { + throw UnsupportedOperationException() } - override fun albumsInPlace(albums: MutableList) { - albums.sortWith(compareByDynamic(NameComparator()) { it }) + open fun getAlbumComparator(ascending: Boolean): Comparator { + throw UnsupportedOperationException() } - override fun artistsInPlace(artists: MutableList) { - artists.sortWith(compareByDynamic(NameComparator()) { it }) + open fun getArtistComparator(ascending: Boolean): Comparator { + throw UnsupportedOperationException() } - override fun genresInPlace(genres: MutableList) { - genres.sortWith(compareByDynamic(NameComparator()) { it }) + open fun getGenreComparator(ascending: Boolean): Comparator { + throw UnsupportedOperationException() } - override fun ascending(newIsAscending: Boolean) = ByName(newIsAscending) - } + /** Sort by the names of an item */ + object ByName : Mode() { + override val intCode: Int + get() = IntegerTable.SORT_BY_NAME - /** Sort by the album of an item, only supported by [Song] */ - class ByAlbum(override val isAscending: Boolean) : Sort(isAscending) { - override val sortIntCode: Int - get() = IntegerTable.SORT_BY_ALBUM + override val itemId: Int + get() = R.id.option_sort_name - override val itemId: Int - get() = R.id.option_sort_album + override fun getSongComparator(ascending: Boolean) = + compareByDynamic(ascending, BasicComparator.SONG) - override fun songsInPlace(songs: MutableList) { - songs.sortWith( + override fun getAlbumComparator(ascending: Boolean) = + compareByDynamic(ascending, BasicComparator.ALBUM) + + override fun getArtistComparator(ascending: Boolean) = + compareByDynamic(ascending, BasicComparator.ARTIST) + + override fun getGenreComparator(ascending: Boolean) = + compareByDynamic(ascending, BasicComparator.GENRE) + } + + /** Sort by the album of an item, only supported by [Song] */ + object ByAlbum : Mode() { + override val intCode: Int + get() = IntegerTable.SORT_BY_ALBUM + + override val itemId: Int + get() = R.id.option_sort_album + + override fun getSongComparator(ascending: Boolean): Comparator = MultiComparator( - compareByDynamic(NameComparator()) { it.album }, - compareBy(NullableComparator()) { it.disc }, - compareBy(NullableComparator()) { it.track }, - compareBy(NameComparator()) { it })) + compareByDynamic(ascending, BasicComparator.ALBUM) { it.album }, + compareBy(NULLABLE_INT_COMPARATOR) { it.disc }, + compareBy(NULLABLE_INT_COMPARATOR) { it.track }, + compareBy(BasicComparator.SONG)) } - override fun ascending(newIsAscending: Boolean) = ByAlbum(newIsAscending) - } + /** Sort by the artist of an item, only supported by [Album] and [Song] */ + object ByArtist : Mode() { + override val intCode: Int + get() = IntegerTable.SORT_BY_ARTIST - /** Sort by the artist of an item, only supported by [Album] and [Song] */ - class ByArtist(override val isAscending: Boolean) : Sort(isAscending) { - override val sortIntCode: Int - get() = IntegerTable.SORT_BY_ARTIST + override val itemId: Int + get() = R.id.option_sort_artist - override val itemId: Int - get() = R.id.option_sort_artist - - override fun songsInPlace(songs: MutableList) { - songs.sortWith( + override fun getSongComparator(ascending: Boolean): Comparator = MultiComparator( - compareByDynamic(NameComparator()) { it.album.artist }, - compareByDescending(NullableComparator()) { it.album.year }, - compareByDescending(NameComparator()) { it.album }, - compareBy(NullableComparator()) { it.disc }, - compareBy(NullableComparator()) { it.track }, - compareBy(NameComparator()) { it })) - } + compareByDynamic(ascending, BasicComparator.ARTIST) { it.album.artist }, + compareByDescending(NULLABLE_INT_COMPARATOR) { it.album.year }, + compareByDescending(BasicComparator.ALBUM) { it.album }, + compareBy(NULLABLE_INT_COMPARATOR) { it.disc }, + compareBy(NULLABLE_INT_COMPARATOR) { it.track }, + compareBy(BasicComparator.SONG)) - override fun albumsInPlace(albums: MutableList) { - albums.sortWith( + override fun getAlbumComparator(ascending: Boolean): Comparator = MultiComparator( - compareByDynamic(NameComparator()) { it.artist }, - compareByDescending(NullableComparator()) { it.year }, - compareBy(NameComparator()) { it })) + compareByDynamic(ascending, BasicComparator.ARTIST) { it.artist }, + compareByDescending(NULLABLE_INT_COMPARATOR) { it.year }, + compareBy(BasicComparator.ALBUM)) } - override fun ascending(newIsAscending: Boolean) = ByArtist(newIsAscending) - } + /** Sort by the year of an item, only supported by [Album] and [Song] */ + object ByYear : Mode() { + override val intCode: Int + get() = IntegerTable.SORT_BY_YEAR - /** Sort by the year of an item, only supported by [Album] and [Song] */ - class ByYear(override val isAscending: Boolean) : Sort(isAscending) { - override val sortIntCode: Int - get() = IntegerTable.SORT_BY_YEAR + override val itemId: Int + get() = R.id.option_sort_year - override val itemId: Int - get() = R.id.option_sort_year - - override fun songsInPlace(songs: MutableList) { - songs.sortWith( + override fun getSongComparator(ascending: Boolean): Comparator = MultiComparator( - compareByDynamic(NullableComparator()) { it.album.year }, - compareByDescending(NameComparator()) { it.album }, - compareBy(NullableComparator()) { it.disc }, - compareBy(NullableComparator()) { it.track }, - compareBy(NameComparator()) { it })) - } + compareByDynamic(ascending, NULLABLE_INT_COMPARATOR) { it.album.year }, + compareByDescending(BasicComparator.ALBUM) { it.album }, + compareBy(NULLABLE_INT_COMPARATOR) { it.disc }, + compareBy(NULLABLE_INT_COMPARATOR) { it.track }, + compareBy(BasicComparator.SONG)) - override fun albumsInPlace(albums: MutableList) { - albums.sortWith( + override fun getAlbumComparator(ascending: Boolean): Comparator = MultiComparator( - compareByDynamic(NullableComparator()) { it.year }, - compareBy(NameComparator()) { it })) + compareByDynamic(ascending, NULLABLE_INT_COMPARATOR) { it.year }, + compareBy(BasicComparator.ALBUM)) } - override fun ascending(newIsAscending: Boolean) = ByYear(newIsAscending) - } + /** Sort by the duration of the item. Supports all items. */ + object ByDuration : Mode() { + override val intCode: Int + get() = IntegerTable.SORT_BY_DURATION - /** Sort by the duration of the item. Supports all items. */ - class ByDuration(override val isAscending: Boolean) : Sort(isAscending) { - override val sortIntCode: Int - get() = IntegerTable.SORT_BY_DURATION + override val itemId: Int + get() = R.id.option_sort_duration - override val itemId: Int - get() = R.id.option_sort_duration - - override fun songsInPlace(songs: MutableList) { - songs.sortWith( + override fun getSongComparator(ascending: Boolean): Comparator = MultiComparator( - compareByDynamic { it.durationSecs }, compareBy(NameComparator()) { it })) - } + compareByDynamic(ascending) { it.durationSecs }, + compareBy(BasicComparator.SONG)) - override fun albumsInPlace(albums: MutableList) { - albums.sortWith( + override fun getAlbumComparator(ascending: Boolean): Comparator = MultiComparator( - compareByDynamic { it.durationSecs }, compareBy(NameComparator()) { it })) - } + compareByDynamic(ascending) { it.durationSecs }, + compareBy(BasicComparator.ALBUM)) - override fun artistsInPlace(artists: MutableList) { - artists.sortWith( + override fun getArtistComparator(ascending: Boolean): Comparator = MultiComparator( - compareByDynamic { it.durationSecs }, compareBy(NameComparator()) { it })) - } + compareByDynamic(ascending) { it.durationSecs }, + compareBy(BasicComparator.ARTIST)) - override fun genresInPlace(genres: MutableList) { - genres.sortWith( + override fun getGenreComparator(ascending: Boolean): Comparator = MultiComparator( - compareByDynamic { it.durationSecs }, compareBy(NameComparator()) { it })) + compareByDynamic(ascending) { it.durationSecs }, + compareBy(BasicComparator.GENRE)) } - override fun ascending(newIsAscending: Boolean) = ByDuration(newIsAscending) - } + /** Sort by the amount of songs. Only applicable to music parents. */ + object ByCount : Mode() { + override val intCode: Int + get() = IntegerTable.SORT_BY_COUNT - /** Sort by the amount of songs. Only applicable to music parents. */ - class ByCount(override val isAscending: Boolean) : Sort(isAscending) { - override val sortIntCode: Int - get() = IntegerTable.SORT_BY_COUNT + override val itemId: Int + get() = R.id.option_sort_count - override val itemId: Int - get() = R.id.option_sort_count - - override fun albumsInPlace(albums: MutableList) { - albums.sortWith( + override fun getAlbumComparator(ascending: Boolean): Comparator = MultiComparator( - compareByDynamic { it.songs.size }, compareBy(NameComparator()) { it })) - } + compareByDynamic(ascending) { it.songs.size }, compareBy(BasicComparator.ALBUM)) - override fun artistsInPlace(artists: MutableList) { - artists.sortWith( + override fun getArtistComparator(ascending: Boolean): Comparator = MultiComparator( - compareByDynamic { it.songs.size }, compareBy(NameComparator()) { it })) - } + compareByDynamic(ascending) { it.songs.size }, + compareBy(BasicComparator.ARTIST)) - override fun genresInPlace(genres: MutableList) { - genres.sortWith( + override fun getGenreComparator(ascending: Boolean): Comparator = MultiComparator( - compareByDynamic { it.songs.size }, compareBy(NameComparator()) { it })) + compareByDynamic(ascending) { it.songs.size }, compareBy(BasicComparator.GENRE)) } - override fun ascending(newIsAscending: Boolean) = ByCount(newIsAscending) - } + /** Sort by the disc, and then track number of an item. Only supported by [Song]. */ + object ByDisc : Mode() { + override val intCode: Int + get() = IntegerTable.SORT_BY_DISC - /** - * Sort by the disc, and then track number of an item. Only supported by [Song]. Do not use this - * in a main sorting view, as it is not assigned to a particular item ID - */ - class ByDisc(override val isAscending: Boolean) : Sort(isAscending) { - override val sortIntCode: Int - get() = IntegerTable.SORT_BY_DISC - - // Not an available option, so no ID is set - override val itemId: Int - get() = R.id.option_sort_disc - - override fun songsInPlace(songs: MutableList) { - songs.sortWith( + override val itemId: Int + get() = R.id.option_sort_disc + override fun getSongComparator(ascending: Boolean): Comparator = MultiComparator( - compareByDynamic(NullableComparator()) { it.disc }, - compareBy(NullableComparator()) { it.track }, - compareBy(NameComparator()) { it })) + compareByDynamic(ascending, NULLABLE_INT_COMPARATOR) { it.disc }, + compareBy(NULLABLE_INT_COMPARATOR) { it.track }, + compareBy(BasicComparator.SONG)) } - override fun ascending(newIsAscending: Boolean) = ByDisc(newIsAscending) - } + /** + * Sort by the disc, and then track number of an item. Only supported by [Song]. Do not use + * this in a main sorting view, as it is not assigned to a particular item ID + */ + object ByTrack : Mode() { + override val intCode: Int + get() = IntegerTable.SORT_BY_TRACK - /** - * Sort by the disc, and then track number of an item. Only supported by [Song]. Do not use this - * in a main sorting view, as it is not assigned to a particular item ID - */ - class ByTrack(override val isAscending: Boolean) : Sort(isAscending) { - override val sortIntCode: Int - get() = IntegerTable.SORT_BY_TRACK + override val itemId: Int + get() = R.id.option_sort_track - override val itemId: Int - get() = R.id.option_sort_track - - override fun songsInPlace(songs: MutableList) { - songs.sortWith( + override fun getSongComparator(ascending: Boolean): Comparator = MultiComparator( - compareBy(NullableComparator()) { it.disc }, - compareByDynamic(NullableComparator()) { it.track }, - compareBy(NameComparator()) { it })) + compareBy(NULLABLE_INT_COMPARATOR) { it.disc }, + compareByDynamic(ascending, NULLABLE_INT_COMPARATOR) { it.track }, + compareBy(BasicComparator.SONG)) } - override fun ascending(newIsAscending: Boolean) = ByTrack(newIsAscending) - } + protected inline fun compareByDynamic( + ascending: Boolean, + comparator: Comparator, + crossinline selector: (T) -> K + ) = + if (ascending) { + compareBy(comparator, selector) + } else { + compareByDescending(comparator, selector) + } - val intCode: Int - get() = sortIntCode.shl(1) or if (isAscending) 1 else 0 + protected fun compareByDynamic( + ascending: Boolean, + comparator: Comparator + ): Comparator = compareByDynamic(ascending, comparator) { it } - /** - * Assign a new [id] to this sort - * @return A new [Sort] corresponding to the [id] given, null if the ID has no analogue. - */ - fun assignId(@IdRes id: Int): Sort? { - return when (id) { - R.id.option_sort_asc -> ascending(!isAscending) - R.id.option_sort_name -> ByName(isAscending) - R.id.option_sort_artist -> ByArtist(isAscending) - R.id.option_sort_album -> ByAlbum(isAscending) - R.id.option_sort_year -> ByYear(isAscending) - R.id.option_sort_duration -> ByDuration(isAscending) - R.id.option_sort_count -> ByCount(isAscending) - R.id.option_sort_disc -> ByDisc(isAscending) - R.id.option_sort_track -> ByTrack(isAscending) - else -> null - } - } + protected inline fun > compareByDynamic( + ascending: Boolean, + crossinline selector: (T) -> K + ) = + if (ascending) { + compareBy(selector) + } else { + compareByDescending(selector) + } - protected inline fun compareByDynamic( - comparator: Comparator, - crossinline selector: (T) -> K - ): Comparator { - return if (isAscending) { - compareBy(comparator, selector) - } else { - compareByDescending(comparator, selector) - } - } + protected fun compareBy(comparator: Comparator): Comparator = + compareBy(comparator) { it } - protected inline fun > compareByDynamic( - crossinline selector: (T) -> K - ): Comparator { - return if (isAscending) { - compareBy(selector) - } else { - compareByDescending(selector) - } - } + /** + * Chains the given comparators together to form one comparator. + * + * Sorts often need to compare multiple things at once across several hierarchies, with this + * class doing such in a more efficient manner than resorting at multiple intervals or + * grouping items up. Comparators are checked from first to last, with the first comparator + * that returns a non-equal result being propagated upwards. + */ + private class MultiComparator(vararg comparators: Comparator) : Comparator { + private val _comparators = comparators - class NameComparator : Comparator { - override fun compare(a: T, b: T): Int { - val aSortName = a.sortName - val bSortName = b.sortName - return when { - aSortName != null && bSortName != null -> - aSortName.compareTo(bSortName, ignoreCase = true) - aSortName == null && bSortName != null -> -1 // a < b - aSortName == null && bSortName == null -> 0 // a = b - aSortName != null && bSortName == null -> 1 // a < b - else -> error("Unreachable") + override fun compare(a: T?, b: T?): Int { + for (comparator in _comparators) { + val result = comparator.compare(a, b) + if (result != 0) { + return result + } + } + + return 0 } } - } - class NullableComparator> : Comparator { - override fun compare(a: T?, b: T?): Int { - return when { - a != null && b != null -> a.compareTo(b) - a == null && b != null -> -1 // a < b - a == null && b == null -> 0 // a = b - a != null && b == null -> 1 // a < b - else -> error("Unreachable") - } - } - } - - /** - * Chains the given comparators together to form one comparator. - * - * Sorts often need to compare multiple things at once across several hierarchies, with this - * class doing such in a more efficient manner than resorting at multiple intervals or grouping - * items up. Comparators are checked from first to last, with the first comparator that returns - * a non-equal result being propagated upwards. - */ - class MultiComparator(vararg comparators: Comparator) : Comparator { - private val _comparators = comparators - - override fun compare(a: T?, b: T?): Int { - for (comparator in _comparators) { - val result = comparator.compare(a, b) - if (result != 0) { - return result + private class BasicComparator private constructor() : Comparator { + override fun compare(a: T, b: T): Int { + val aSortName = a.sortName + val bSortName = b.sortName + return when { + aSortName != null && bSortName != null -> + aSortName.compareTo(bSortName, ignoreCase = true) + aSortName == null && bSortName != null -> -1 // a < b + aSortName == null && bSortName == null -> 0 // a = b + aSortName != null && bSortName == null -> 1 // a < b + else -> error("Unreachable") } } - return 0 + companion object { + val SONG: Comparator = BasicComparator() + val ALBUM: Comparator = BasicComparator() + val ARTIST: Comparator = BasicComparator() + val GENRE: Comparator = BasicComparator() + } + } + + companion object { + // Exposed as Indexer relies on it at points + val NULLABLE_INT_COMPARATOR = + Comparator { a, b -> + when { + a != null && b != null -> a.compareTo(b) + a == null && b != null -> -1 // a < b + a == null && b == null -> 0 // a = b + a != null && b == null -> 1 // a < b + else -> error("Unreachable") + } + } + + fun fromItemId(@IdRes itemId: Int) = + when (itemId) { + ByName.itemId -> ByName + ByAlbum.itemId -> ByAlbum + ByArtist.itemId -> ByArtist + ByYear.itemId -> ByYear + ByDuration.itemId -> ByDuration + ByCount.itemId -> ByCount + ByDisc.itemId -> ByDisc + ByTrack.itemId -> ByTrack + else -> null + } } } companion object { + /** * Convert a sort's integer representation into a [Sort] instance. * @@ -412,18 +388,20 @@ sealed class Sort(open val isAscending: Boolean) { */ fun fromIntCode(value: Int): Sort? { val isAscending = (value and 1) == 1 + val mode = + when (value.shr(1)) { + Mode.ByName.intCode -> Mode.ByName + Mode.ByArtist.intCode -> Mode.ByArtist + Mode.ByAlbum.intCode -> Mode.ByAlbum + Mode.ByYear.intCode -> Mode.ByYear + Mode.ByDuration.intCode -> Mode.ByDuration + Mode.ByCount.intCode -> Mode.ByCount + Mode.ByDisc.intCode -> Mode.ByDisc + Mode.ByTrack.intCode -> Mode.ByTrack + else -> return null + } - return when (value.shr(1)) { - IntegerTable.SORT_BY_NAME -> ByName(isAscending) - IntegerTable.SORT_BY_ARTIST -> ByArtist(isAscending) - IntegerTable.SORT_BY_ALBUM -> ByAlbum(isAscending) - IntegerTable.SORT_BY_YEAR -> ByYear(isAscending) - IntegerTable.SORT_BY_DURATION -> ByDuration(isAscending) - IntegerTable.SORT_BY_COUNT -> ByCount(isAscending) - IntegerTable.SORT_BY_DISC -> ByDisc(isAscending) - IntegerTable.SORT_BY_TRACK -> ByTrack(isAscending) - else -> null - } + return Sort(mode, isAscending) } } } diff --git a/app/src/main/java/org/oxycblt/auxio/util/FrameworkUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/FrameworkUtil.kt index 1e8192d0f..d37a3a9de 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/FrameworkUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/FrameworkUtil.kt @@ -46,7 +46,6 @@ import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch import org.oxycblt.auxio.R diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt index 699e28e87..c3f3de89f 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt @@ -37,8 +37,8 @@ import org.oxycblt.auxio.util.logD /** * A wrapper around each [WidgetProvider] that plugs into the main Auxio process and updates the * widget state based off of that. This cannot be rolled into [WidgetProvider] directly, as it may - * result in memory leaks if [PlaybackStateManager]/[SettingsManager] gets created and bound to - * without being released. + * result in memory leaks if [PlaybackStateManager]/[Settings] gets created and bound to without + * being released. * @author OxygenCobalt */ class WidgetComponent(private val context: Context) : diff --git a/app/src/main/res/values/settings.xml b/app/src/main/res/values/settings.xml index 995c6dad5..d319acc08 100644 --- a/app/src/main/res/values/settings.xml +++ b/app/src/main/res/values/settings.xml @@ -35,9 +35,9 @@ auxio_artists_sort auxio_genres_sort - auxio_album_sort - auxio_artist_sort - auxio_genre_sort + auxio_album_sort + auxio_artist_sort + auxio_genre_sort @string/set_theme_auto