service: share home list logic between service/ui

This commit is contained in:
Alexander Capehart 2024-09-13 13:35:21 -06:00
parent e4310cfe17
commit 29d663f500
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
5 changed files with 187 additions and 78 deletions

View file

@ -42,9 +42,9 @@ interface HomeSettings : Settings<HomeSettings.Listener> {
interface Listener { interface Listener {
/** Called when the [homeTabs] configuration changes. */ /** Called when the [homeTabs] configuration changes. */
fun onTabsChanged() fun onTabsChanged() {}
/** Called when the [shouldHideCollaborators] configuration changes. */ /** Called when the [shouldHideCollaborators] configuration changes. */
fun onHideCollaboratorsChanged() fun onHideCollaboratorsChanged() {}
} }
} }

View file

@ -23,6 +23,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import org.oxycblt.auxio.home.list.HomeListGenerator
import org.oxycblt.auxio.home.tabs.Tab import org.oxycblt.auxio.home.tabs.Tab
import org.oxycblt.auxio.list.ListSettings import org.oxycblt.auxio.list.ListSettings
import org.oxycblt.auxio.list.adapter.UpdateInstructions import org.oxycblt.auxio.list.adapter.UpdateInstructions
@ -52,8 +53,9 @@ constructor(
private val homeSettings: HomeSettings, private val homeSettings: HomeSettings,
private val listSettings: ListSettings, private val listSettings: ListSettings,
private val playbackSettings: PlaybackSettings, private val playbackSettings: PlaybackSettings,
private val musicRepository: MusicRepository, homeGeneratorFactory: HomeListGenerator.Factory
) : ViewModel(), MusicRepository.UpdateListener, HomeSettings.Listener { ) : ViewModel(), HomeSettings.Listener, HomeListGenerator.Invalidator {
private val generator = homeGeneratorFactory.create(this)
private val _songList = MutableStateFlow(listOf<Song>()) private val _songList = MutableStateFlow(listOf<Song>())
/** A list of [Song]s, sorted by the preferred [Sort], to be shown in the home view. */ /** A list of [Song]s, sorted by the preferred [Sort], to be shown in the home view. */
@ -165,46 +167,37 @@ constructor(
get() = _showOuter get() = _showOuter
init { init {
musicRepository.addUpdateListener(this)
homeSettings.registerListener(this) homeSettings.registerListener(this)
} }
override fun onCleared() { override fun onCleared() {
super.onCleared() super.onCleared()
musicRepository.removeUpdateListener(this)
homeSettings.unregisterListener(this) homeSettings.unregisterListener(this)
generator.release()
} }
override fun onMusicChanges(changes: MusicRepository.Changes) { override fun invalidate(type: MusicType, instructions: UpdateInstructions) {
val deviceLibrary = musicRepository.deviceLibrary when (type) {
if (changes.deviceLibrary && deviceLibrary != null) { MusicType.SONGS -> {
logD("Refreshing library") _songList.value = generator.songs()
// Get the each list of items in the library to use as our list data. _songInstructions.put(instructions)
// Applying the preferred sorting to them. }
_songInstructions.put(UpdateInstructions.Diff) MusicType.ALBUMS -> {
_songList.value = listSettings.songSort.songs(deviceLibrary.songs) _albumList.value = generator.albums()
_albumInstructions.put(UpdateInstructions.Diff) _albumInstructions.put(instructions)
_albumList.value = listSettings.albumSort.albums(deviceLibrary.albums) }
_artistInstructions.put(UpdateInstructions.Diff) MusicType.ARTISTS -> {
_artistList.value = _artistList.value = generator.artists()
listSettings.artistSort.artists( _artistInstructions.put(instructions)
if (homeSettings.shouldHideCollaborators) { }
logD("Filtering collaborator artists") MusicType.GENRES -> {
// Hide Collaborators is enabled, filter out collaborators. _genreList.value = generator.genres()
deviceLibrary.artists.filter { it.explicitAlbums.isNotEmpty() } _genreInstructions.put(instructions)
} else { }
logD("Using all artists") MusicType.PLAYLISTS -> {
deviceLibrary.artists _playlistList.value = generator.playlists()
}) _playlistInstructions.put(instructions)
_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)
} }
} }
@ -215,13 +208,6 @@ constructor(
_shouldRecreate.put(Unit) _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]. * Apply a new [Sort] to [songList].
* *
@ -229,8 +215,6 @@ constructor(
*/ */
fun applySongSort(sort: Sort) { fun applySongSort(sort: Sort) {
listSettings.songSort = 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) { fun applyAlbumSort(sort: Sort) {
listSettings.albumSort = 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) { fun applyArtistSort(sort: Sort) {
listSettings.artistSort = 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) { fun applyGenreSort(sort: Sort) {
listSettings.genreSort = 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) { fun applyPlaylistSort(sort: Sort) {
listSettings.playlistSort = sort listSettings.playlistSort = sort
_playlistInstructions.put(UpdateInstructions.Replace(0))
_playlistList.value = listSettings.playlistSort.playlists(_playlistList.value)
} }
/** /**

View file

@ -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<Song>
fun albums(): List<Album>
fun artists(): List<Artist>
fun genres(): List<Genre>
fun playlists(): List<Playlist>
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)
}
}
}

View file

@ -26,7 +26,7 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.list.sort.Sort import org.oxycblt.auxio.list.sort.Sort
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
interface ListSettings : Settings<Unit> { interface ListSettings : Settings<ListSettings.Listener> {
/** The [Sort] mode used in Song lists. */ /** The [Sort] mode used in Song lists. */
var songSort: Sort var songSort: Sort
/** The [Sort] mode used in Album lists. */ /** The [Sort] mode used in Album lists. */
@ -43,10 +43,18 @@ interface ListSettings : Settings<Unit> {
var artistSongSort: Sort var artistSongSort: Sort
/** The [Sort] mode used in a Genre's Song list. */ /** The [Sort] mode used in a Genre's Song list. */
var genreSongSort: Sort var genreSongSort: Sort
interface Listener {
fun onSongSortChanged() {}
fun onAlbumSortChanged() {}
fun onArtistSortChanged() {}
fun onGenreSortChanged() {}
fun onPlaylistSortChanged() {}
}
} }
class ListSettingsImpl @Inject constructor(@ApplicationContext val context: Context) : class ListSettingsImpl @Inject constructor(@ApplicationContext val context: Context) :
Settings.Impl<Unit>(context), ListSettings { Settings.Impl<ListSettings.Listener>(context), ListSettings {
override var songSort: Sort override var songSort: Sort
get() = get() =
Sort.fromIntCode( Sort.fromIntCode(
@ -145,4 +153,14 @@ class ListSettingsImpl @Inject constructor(@ApplicationContext val context: Cont
apply() 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()
}
}
} }

View file

@ -23,13 +23,16 @@ import android.support.v4.media.MediaBrowserCompat.MediaItem
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject import javax.inject.Inject
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.home.list.HomeListGenerator
import org.oxycblt.auxio.list.ListSettings import org.oxycblt.auxio.list.ListSettings
import org.oxycblt.auxio.list.adapter.UpdateInstructions
import org.oxycblt.auxio.list.sort.Sort import org.oxycblt.auxio.list.sort.Sort
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicRepository import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.MusicType
import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Playlist
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.device.DeviceLibrary import org.oxycblt.auxio.music.device.DeviceLibrary
@ -42,12 +45,14 @@ constructor(
@ApplicationContext private val context: Context, @ApplicationContext private val context: Context,
private val musicRepository: MusicRepository, private val musicRepository: MusicRepository,
private val searchEngine: SearchEngine, private val searchEngine: SearchEngine,
private val listSettings: ListSettings private val listSettings: ListSettings,
) : MusicRepository.UpdateListener { homeGeneratorFactory: HomeListGenerator.Factory
) : MusicRepository.UpdateListener, HomeListGenerator.Invalidator {
interface Invalidator { interface Invalidator {
fun invalidateMusic(ids: Set<String>) fun invalidateMusic(ids: Set<String>)
} }
private val generator = homeGeneratorFactory.create(this)
private var invalidator: Invalidator? = null private var invalidator: Invalidator? = null
fun attach(invalidator: Invalidator) { fun attach(invalidator: Invalidator) {
@ -59,6 +64,18 @@ constructor(
musicRepository.removeUpdateListener(this) 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) { override fun onMusicChanges(changes: MusicRepository.Changes) {
val deviceLibrary = musicRepository.deviceLibrary val deviceLibrary = musicRepository.deviceLibrary
val invalidate = mutableSetOf<String>() val invalidate = mutableSetOf<String>()
@ -126,7 +143,7 @@ constructor(
return listOf() return listOf()
} }
return getMediaItemList(parentId, deviceLibrary, userLibrary) return getMediaItemList(parentId)
} }
suspend fun search(query: String): MutableList<MediaItem> { suspend fun search(query: String): MutableList<MediaItem> {
@ -166,13 +183,11 @@ constructor(
} }
private fun getMediaItemList( private fun getMediaItemList(
id: String, id: String
deviceLibrary: DeviceLibrary,
userLibrary: UserLibrary
): List<MediaItem>? { ): List<MediaItem>? {
return when (val mediaSessionUID = MediaSessionUID.fromString(id)) { return when (val mediaSessionUID = MediaSessionUID.fromString(id)) {
is MediaSessionUID.CategoryItem -> { is MediaSessionUID.CategoryItem -> {
getCategoryMediaItems(mediaSessionUID.category, deviceLibrary, userLibrary) getCategoryMediaItems(mediaSessionUID.category)
} }
is MediaSessionUID.SingleItem -> { is MediaSessionUID.SingleItem -> {
getChildMediaItems(mediaSessionUID.uid) getChildMediaItems(mediaSessionUID.uid)
@ -187,9 +202,7 @@ constructor(
} }
private fun getCategoryMediaItems( private fun getCategoryMediaItems(
category: Category, category: Category
deviceLibrary: DeviceLibrary,
userLibrary: UserLibrary
) = ) =
when (category) { when (category) {
is Category.Root -> { is Category.Root -> {
@ -203,19 +216,11 @@ constructor(
} }
is Category.More -> is Category.More ->
Category.MUSIC.takeLast(category.remainder).map { it.toMediaItem(context) } Category.MUSIC.takeLast(category.remainder).map { it.toMediaItem(context) }
is Category.Songs -> is Category.Songs -> generator.songs().map { it.toMediaItem(context) }
listSettings.songSort.songs(deviceLibrary.songs).map { is Category.Albums -> generator.albums().map { it.toMediaItem(context) }
it.toMediaItem(context, null) is Category.Artists -> generator.artists().map { it.toMediaItem(context) }
} is Category.Genres -> generator.genres().map { it.toMediaItem(context) }
is Category.Albums -> is Category.Playlists -> generator.playlists().map { it.toMediaItem(context) }
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) }
} }
private fun getChildMediaItems(uid: Music.UID): List<MediaItem>? { private fun getChildMediaItems(uid: Music.UID): List<MediaItem>? {