From 29d663f5000358ccc321f4bc855217f6b728f891 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Fri, 13 Sep 2024 13:35:21 -0600 Subject: [PATCH] service: share home list logic between service/ui --- .../org/oxycblt/auxio/home/HomeSettings.kt | 4 +- .../org/oxycblt/auxio/home/HomeViewModel.kt | 78 +++++-------- .../auxio/home/list/HomeListGenerator.kt | 110 ++++++++++++++++++ .../org/oxycblt/auxio/list/ListSettings.kt | 22 +++- .../auxio/music/service/MusicBrowser.kt | 51 ++++---- 5 files changed, 187 insertions(+), 78 deletions(-) create mode 100644 app/src/main/java/org/oxycblt/auxio/home/list/HomeListGenerator.kt diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeSettings.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeSettings.kt index 5fc218cfe..ec54942f3 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeSettings.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeSettings.kt @@ -42,9 +42,9 @@ interface HomeSettings : Settings { interface Listener { /** Called when the [homeTabs] configuration changes. */ - fun onTabsChanged() + fun onTabsChanged() {} /** Called when the [shouldHideCollaborators] configuration changes. */ - fun onHideCollaboratorsChanged() + fun onHideCollaboratorsChanged() {} } } diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt index 51bc04976..d681d9ac1 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt @@ -23,6 +23,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import org.oxycblt.auxio.home.list.HomeListGenerator import org.oxycblt.auxio.home.tabs.Tab import org.oxycblt.auxio.list.ListSettings import org.oxycblt.auxio.list.adapter.UpdateInstructions @@ -52,8 +53,9 @@ constructor( private val homeSettings: HomeSettings, private val listSettings: ListSettings, private val playbackSettings: PlaybackSettings, - private val musicRepository: MusicRepository, -) : ViewModel(), MusicRepository.UpdateListener, HomeSettings.Listener { + homeGeneratorFactory: HomeListGenerator.Factory +) : ViewModel(), HomeSettings.Listener, HomeListGenerator.Invalidator { + private val generator = homeGeneratorFactory.create(this) private val _songList = MutableStateFlow(listOf()) /** A list of [Song]s, sorted by the preferred [Sort], to be shown in the home view. */ @@ -165,46 +167,37 @@ constructor( get() = _showOuter init { - musicRepository.addUpdateListener(this) homeSettings.registerListener(this) } override fun onCleared() { super.onCleared() - musicRepository.removeUpdateListener(this) homeSettings.unregisterListener(this) + generator.release() } - override fun onMusicChanges(changes: MusicRepository.Changes) { - val deviceLibrary = musicRepository.deviceLibrary - if (changes.deviceLibrary && deviceLibrary != null) { - logD("Refreshing library") - // Get the each list of items in the library to use as our list data. - // Applying the preferred sorting to them. - _songInstructions.put(UpdateInstructions.Diff) - _songList.value = listSettings.songSort.songs(deviceLibrary.songs) - _albumInstructions.put(UpdateInstructions.Diff) - _albumList.value = listSettings.albumSort.albums(deviceLibrary.albums) - _artistInstructions.put(UpdateInstructions.Diff) - _artistList.value = - listSettings.artistSort.artists( - if (homeSettings.shouldHideCollaborators) { - logD("Filtering collaborator artists") - // Hide Collaborators is enabled, filter out collaborators. - deviceLibrary.artists.filter { it.explicitAlbums.isNotEmpty() } - } else { - logD("Using all artists") - deviceLibrary.artists - }) - _genreInstructions.put(UpdateInstructions.Diff) - _genreList.value = listSettings.genreSort.genres(deviceLibrary.genres) - } - - val userLibrary = musicRepository.userLibrary - if (changes.userLibrary && userLibrary != null) { - logD("Refreshing playlists") - _playlistInstructions.put(UpdateInstructions.Diff) - _playlistList.value = listSettings.playlistSort.playlists(userLibrary.playlists) + override fun invalidate(type: MusicType, instructions: UpdateInstructions) { + when (type) { + MusicType.SONGS -> { + _songList.value = generator.songs() + _songInstructions.put(instructions) + } + MusicType.ALBUMS -> { + _albumList.value = generator.albums() + _albumInstructions.put(instructions) + } + MusicType.ARTISTS -> { + _artistList.value = generator.artists() + _artistInstructions.put(instructions) + } + MusicType.GENRES -> { + _genreList.value = generator.genres() + _genreInstructions.put(instructions) + } + MusicType.PLAYLISTS -> { + _playlistList.value = generator.playlists() + _playlistInstructions.put(instructions) + } } } @@ -215,13 +208,6 @@ constructor( _shouldRecreate.put(Unit) } - override fun onHideCollaboratorsChanged() { - // Changes in the hide collaborator setting will change the artist contents - // of the library, consider it a library update. - logD("Collaborator setting changed, forwarding update") - onMusicChanges(MusicRepository.Changes(deviceLibrary = true, userLibrary = false)) - } - /** * Apply a new [Sort] to [songList]. * @@ -229,8 +215,6 @@ constructor( */ fun applySongSort(sort: Sort) { listSettings.songSort = sort - _songInstructions.put(UpdateInstructions.Replace(0)) - _songList.value = listSettings.songSort.songs(_songList.value) } /** @@ -240,8 +224,6 @@ constructor( */ fun applyAlbumSort(sort: Sort) { listSettings.albumSort = sort - _albumInstructions.put(UpdateInstructions.Replace(0)) - _albumList.value = listSettings.albumSort.albums(_albumList.value) } /** @@ -251,8 +233,6 @@ constructor( */ fun applyArtistSort(sort: Sort) { listSettings.artistSort = sort - _artistInstructions.put(UpdateInstructions.Replace(0)) - _artistList.value = listSettings.artistSort.artists(_artistList.value) } /** @@ -262,8 +242,6 @@ constructor( */ fun applyGenreSort(sort: Sort) { listSettings.genreSort = sort - _genreInstructions.put(UpdateInstructions.Replace(0)) - _genreList.value = listSettings.genreSort.genres(_genreList.value) } /** @@ -273,8 +251,6 @@ constructor( */ fun applyPlaylistSort(sort: Sort) { listSettings.playlistSort = sort - _playlistInstructions.put(UpdateInstructions.Replace(0)) - _playlistList.value = listSettings.playlistSort.playlists(_playlistList.value) } /** diff --git a/app/src/main/java/org/oxycblt/auxio/home/list/HomeListGenerator.kt b/app/src/main/java/org/oxycblt/auxio/home/list/HomeListGenerator.kt new file mode 100644 index 000000000..faca4bb56 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/home/list/HomeListGenerator.kt @@ -0,0 +1,110 @@ +package org.oxycblt.auxio.home.list + +import org.oxycblt.auxio.home.HomeSettings +import org.oxycblt.auxio.list.ListSettings +import org.oxycblt.auxio.list.adapter.UpdateInstructions +import org.oxycblt.auxio.music.Album +import org.oxycblt.auxio.music.Artist +import org.oxycblt.auxio.music.Genre +import org.oxycblt.auxio.music.MusicRepository +import org.oxycblt.auxio.music.MusicType +import org.oxycblt.auxio.music.Playlist +import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.playback.PlaybackSettings +import org.oxycblt.auxio.util.logD +import javax.inject.Inject + +interface HomeListGenerator { + fun songs(): List + fun albums(): List + fun artists(): List + fun genres(): List + fun playlists(): List + fun release() + + interface Invalidator { + fun invalidate(type: MusicType, instructions: UpdateInstructions) + } + + interface Factory { + fun create(invalidator: Invalidator): HomeListGenerator + } +} + +private class HomeListGeneratorImpl( + private val invalidator: HomeListGenerator.Invalidator, + private val homeSettings: HomeSettings, + private val listSettings: ListSettings, + private val musicRepository: MusicRepository, +) : HomeListGenerator, HomeSettings.Listener, ListSettings.Listener, MusicRepository.UpdateListener { + override fun songs() = + musicRepository.deviceLibrary?.let { listSettings.songSort.songs(it.songs) } ?: emptyList() + override fun albums() = musicRepository.deviceLibrary?.let { listSettings.albumSort.albums(it.albums) } ?: emptyList() + override fun artists() = musicRepository.deviceLibrary?.let { listSettings.artistSort.artists(it.artists) } ?: emptyList() + override fun genres() = musicRepository.deviceLibrary?.let { listSettings.genreSort.genres(it.genres) } ?: emptyList() + override fun playlists() = musicRepository.userLibrary?.let { listSettings.playlistSort.playlists(it.playlists) } ?: emptyList() + + init { + homeSettings.registerListener(this) + listSettings.registerListener(this) + musicRepository.addUpdateListener(this) + } + + override fun release() { + homeSettings.unregisterListener(this) + listSettings.unregisterListener(this) + musicRepository.removeUpdateListener(this) + } + + override fun onHideCollaboratorsChanged() { + // Changes in the hide collaborator setting will change the artist contents + // of the library, consider it a library update. + logD("Collaborator setting changed, forwarding update") + onMusicChanges(MusicRepository.Changes(deviceLibrary = true, userLibrary = false)) + } + + override fun onSongSortChanged() { + super.onSongSortChanged() + invalidator.invalidate(MusicType.SONGS, UpdateInstructions.Replace(0)) + } + + override fun onAlbumSortChanged() { + super.onAlbumSortChanged() + invalidator.invalidate(MusicType.ALBUMS, UpdateInstructions.Replace(0)) + } + + override fun onArtistSortChanged() { + super.onArtistSortChanged() + invalidator.invalidate(MusicType.ARTISTS, UpdateInstructions.Replace(0)) + } + + override fun onGenreSortChanged() { + super.onGenreSortChanged() + invalidator.invalidate(MusicType.GENRES, UpdateInstructions.Replace(0)) + } + + override fun onPlaylistSortChanged() { + super.onPlaylistSortChanged() + invalidator.invalidate(MusicType.PLAYLISTS, UpdateInstructions.Replace(0)) + } + + override fun onMusicChanges(changes: MusicRepository.Changes) { + val deviceLibrary = musicRepository.deviceLibrary + if (changes.deviceLibrary && deviceLibrary != null) { + logD("Refreshing library") + // Get the each list of items in the library to use as our list data. + // Applying the preferred sorting to them. + invalidator.invalidate(MusicType.SONGS, UpdateInstructions.Diff) + invalidator.invalidate(MusicType.ALBUMS, UpdateInstructions.Diff) + invalidator.invalidate(MusicType.ARTISTS, UpdateInstructions.Diff) + invalidator.invalidate(MusicType.GENRES, UpdateInstructions.Diff) + } + + val userLibrary = musicRepository.userLibrary + if (changes.userLibrary && userLibrary != null) { + logD("Refreshing playlists") + invalidator.invalidate(MusicType.PLAYLISTS, UpdateInstructions.Diff) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/oxycblt/auxio/list/ListSettings.kt b/app/src/main/java/org/oxycblt/auxio/list/ListSettings.kt index 3f3388b73..9b0bb7f4f 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/ListSettings.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/ListSettings.kt @@ -26,7 +26,7 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.list.sort.Sort import org.oxycblt.auxio.settings.Settings -interface ListSettings : Settings { +interface ListSettings : Settings { /** The [Sort] mode used in Song lists. */ var songSort: Sort /** The [Sort] mode used in Album lists. */ @@ -43,10 +43,18 @@ interface ListSettings : Settings { var artistSongSort: Sort /** The [Sort] mode used in a Genre's Song list. */ var genreSongSort: Sort + + interface Listener { + fun onSongSortChanged() {} + fun onAlbumSortChanged() {} + fun onArtistSortChanged() {} + fun onGenreSortChanged() {} + fun onPlaylistSortChanged() {} + } } class ListSettingsImpl @Inject constructor(@ApplicationContext val context: Context) : - Settings.Impl(context), ListSettings { + Settings.Impl(context), ListSettings { override var songSort: Sort get() = Sort.fromIntCode( @@ -145,4 +153,14 @@ class ListSettingsImpl @Inject constructor(@ApplicationContext val context: Cont apply() } } + + override fun onSettingChanged(key: String, listener: ListSettings.Listener) { + when (key) { + getString(R.string.set_key_songs_sort) -> listener.onSongSortChanged() + getString(R.string.set_key_albums_sort) -> listener.onAlbumSortChanged() + getString(R.string.set_key_artists_sort) -> listener.onArtistSortChanged() + getString(R.string.set_key_genres_sort) -> listener.onGenreSortChanged() + getString(R.string.set_key_playlists_sort) -> listener.onPlaylistSortChanged() + } + } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/service/MusicBrowser.kt b/app/src/main/java/org/oxycblt/auxio/music/service/MusicBrowser.kt index 9988a049b..c4d83f6f9 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/service/MusicBrowser.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/service/MusicBrowser.kt @@ -23,13 +23,16 @@ import android.support.v4.media.MediaBrowserCompat.MediaItem import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject import org.oxycblt.auxio.R +import org.oxycblt.auxio.home.list.HomeListGenerator import org.oxycblt.auxio.list.ListSettings +import org.oxycblt.auxio.list.adapter.UpdateInstructions import org.oxycblt.auxio.list.sort.Sort import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.MusicRepository +import org.oxycblt.auxio.music.MusicType import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.device.DeviceLibrary @@ -42,12 +45,14 @@ constructor( @ApplicationContext private val context: Context, private val musicRepository: MusicRepository, private val searchEngine: SearchEngine, - private val listSettings: ListSettings -) : MusicRepository.UpdateListener { + private val listSettings: ListSettings, + homeGeneratorFactory: HomeListGenerator.Factory +) : MusicRepository.UpdateListener, HomeListGenerator.Invalidator { interface Invalidator { fun invalidateMusic(ids: Set) } + private val generator = homeGeneratorFactory.create(this) private var invalidator: Invalidator? = null fun attach(invalidator: Invalidator) { @@ -59,6 +64,18 @@ constructor( musicRepository.removeUpdateListener(this) } + override fun invalidate(type: MusicType, instructions: UpdateInstructions) { + val category = when (type) { + MusicType.SONGS -> Category.Songs + MusicType.ALBUMS -> Category.Albums + MusicType.ARTISTS -> Category.Artists + MusicType.GENRES -> Category.Genres + MusicType.PLAYLISTS -> Category.Playlists + } + val id = MediaSessionUID.CategoryItem(category).toString() + invalidator?.invalidateMusic(setOf(id)) + } + override fun onMusicChanges(changes: MusicRepository.Changes) { val deviceLibrary = musicRepository.deviceLibrary val invalidate = mutableSetOf() @@ -126,7 +143,7 @@ constructor( return listOf() } - return getMediaItemList(parentId, deviceLibrary, userLibrary) + return getMediaItemList(parentId) } suspend fun search(query: String): MutableList { @@ -166,13 +183,11 @@ constructor( } private fun getMediaItemList( - id: String, - deviceLibrary: DeviceLibrary, - userLibrary: UserLibrary + id: String ): List? { return when (val mediaSessionUID = MediaSessionUID.fromString(id)) { is MediaSessionUID.CategoryItem -> { - getCategoryMediaItems(mediaSessionUID.category, deviceLibrary, userLibrary) + getCategoryMediaItems(mediaSessionUID.category) } is MediaSessionUID.SingleItem -> { getChildMediaItems(mediaSessionUID.uid) @@ -187,9 +202,7 @@ constructor( } private fun getCategoryMediaItems( - category: Category, - deviceLibrary: DeviceLibrary, - userLibrary: UserLibrary + category: Category ) = when (category) { is Category.Root -> { @@ -203,19 +216,11 @@ constructor( } is Category.More -> Category.MUSIC.takeLast(category.remainder).map { it.toMediaItem(context) } - is Category.Songs -> - listSettings.songSort.songs(deviceLibrary.songs).map { - it.toMediaItem(context, null) - } - is Category.Albums -> - listSettings.albumSort.albums(deviceLibrary.albums).map { it.toMediaItem(context) } - is Category.Artists -> - listSettings.artistSort.artists(deviceLibrary.artists).map { - it.toMediaItem(context) - } - is Category.Genres -> - listSettings.genreSort.genres(deviceLibrary.genres).map { it.toMediaItem(context) } - is Category.Playlists -> userLibrary.playlists.map { it.toMediaItem(context) } + is Category.Songs -> generator.songs().map { it.toMediaItem(context) } + is Category.Albums -> generator.albums().map { it.toMediaItem(context) } + is Category.Artists -> generator.artists().map { it.toMediaItem(context) } + is Category.Genres -> generator.genres().map { it.toMediaItem(context) } + is Category.Playlists -> generator.playlists().map { it.toMediaItem(context) } } private fun getChildMediaItems(uid: Music.UID): List? {