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 {
/** Called when the [homeTabs] configuration changes. */
fun onTabsChanged()
fun onTabsChanged() {}
/** 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 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<Song>())
/** 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)
}
/**

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.settings.Settings
interface ListSettings : Settings<Unit> {
interface ListSettings : Settings<ListSettings.Listener> {
/** The [Sort] mode used in Song lists. */
var songSort: Sort
/** The [Sort] mode used in Album lists. */
@ -43,10 +43,18 @@ interface ListSettings : Settings<Unit> {
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<Unit>(context), ListSettings {
Settings.Impl<ListSettings.Listener>(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()
}
}
}

View file

@ -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<String>)
}
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<String>()
@ -126,7 +143,7 @@ constructor(
return listOf()
}
return getMediaItemList(parentId, deviceLibrary, userLibrary)
return getMediaItemList(parentId)
}
suspend fun search(query: String): MutableList<MediaItem> {
@ -166,13 +183,11 @@ constructor(
}
private fun getMediaItemList(
id: String,
deviceLibrary: DeviceLibrary,
userLibrary: UserLibrary
id: String
): List<MediaItem>? {
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<MediaItem>? {