home: make no music action generic

This way the playlist view can switch to "New Playlist" if a load
finishes but the user hasn't made any playlists.
This commit is contained in:
Alexander Capehart 2025-01-01 16:07:36 -07:00
parent 0f4a550775
commit 9161b8f777
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
8 changed files with 65 additions and 27 deletions

View file

@ -36,6 +36,8 @@ interface HomeGenerator {
fun release() fun release()
fun empty(): Boolean
fun songs(): List<Song> fun songs(): List<Song>
fun albums(): List<Album> fun albums(): List<Album>
@ -49,6 +51,8 @@ interface HomeGenerator {
fun tabs(): List<MusicType> fun tabs(): List<MusicType>
interface Invalidator { interface Invalidator {
fun invalidateEmpty() {}
fun invalidateMusic(type: MusicType, instructions: UpdateInstructions) fun invalidateMusic(type: MusicType, instructions: UpdateInstructions)
fun invalidateTabs() fun invalidateTabs()
@ -119,6 +123,8 @@ private class HomeGeneratorImpl(
} }
override fun onMusicChanges(changes: MusicRepository.Changes) { override fun onMusicChanges(changes: MusicRepository.Changes) {
invalidator.invalidateEmpty()
val library = musicRepository.library val library = musicRepository.library
if (changes.deviceLibrary && library != null) { if (changes.deviceLibrary && library != null) {
L.d("Refreshing library") L.d("Refreshing library")
@ -142,6 +148,9 @@ private class HomeGeneratorImpl(
homeSettings.unregisterListener(this) homeSettings.unregisterListener(this)
} }
override fun empty() =
musicRepository.library == null
override fun songs() = override fun songs() =
musicRepository.library?.let { listSettings.songSort.songs(it.songs) } ?: emptyList() musicRepository.library?.let { listSettings.songSort.songs(it.songs) } ?: emptyList()

View file

@ -120,6 +120,10 @@ constructor(
val playlistList: StateFlow<List<Playlist>> val playlistList: StateFlow<List<Playlist>>
get() = _playlistList get() = _playlistList
private val _empty = MutableStateFlow(false)
val empty: StateFlow<Boolean>
get() = _empty
private val _playlistInstructions = MutableEvent<UpdateInstructions>() private val _playlistInstructions = MutableEvent<UpdateInstructions>()
/** Instructions for how to update [genreList] in the UI. */ /** Instructions for how to update [genreList] in the UI. */
val playlistInstructions: Event<UpdateInstructions> val playlistInstructions: Event<UpdateInstructions>
@ -129,6 +133,7 @@ constructor(
val playlistSort: Sort val playlistSort: Sort
get() = listSettings.playlistSort get() = listSettings.playlistSort
private val homeGenerator = homeGeneratorFactory.create(this) private val homeGenerator = homeGeneratorFactory.create(this)
/** /**
@ -172,6 +177,10 @@ constructor(
homeGenerator.release() homeGenerator.release()
} }
override fun invalidateEmpty() {
_empty.value = homeGenerator.empty()
}
override fun invalidateMusic(type: MusicType, instructions: UpdateInstructions) { override fun invalidateMusic(type: MusicType, instructions: UpdateInstructions) {
when (type) { when (type) {
MusicType.SONGS -> { MusicType.SONGS -> {

View file

@ -87,9 +87,10 @@ class AlbumListFragment :
} }
binding.homeNoMusicMsg.text = getString(R.string.lng_empty_albums) binding.homeNoMusicMsg.text = getString(R.string.lng_empty_albums)
binding.homeChooseMusicSources.setOnClickListener { homeModel.startChooseMusicLocations() } binding.homeNoMusicAction.setOnClickListener { homeModel.startChooseMusicLocations() }
collectImmediately(homeModel.albumList, ::updateAlbums) collectImmediately(homeModel.albumList, ::updateAlbums)
collectImmediately(homeModel.empty, ::updateNoMusicIndicator)
collectImmediately(listModel.selected, ::updateSelection) collectImmediately(listModel.selected, ::updateSelection)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
@ -154,13 +155,15 @@ class AlbumListFragment :
} }
private fun updateAlbums(albums: List<Album>) { private fun updateAlbums(albums: List<Album>) {
requireBinding().apply {
homeRecycler.isInvisible = albums.isEmpty()
homeNoMusic.isInvisible = albums.isNotEmpty()
}
albumAdapter.update(albums, homeModel.albumInstructions.consume()) albumAdapter.update(albums, homeModel.albumInstructions.consume())
} }
private fun updateNoMusicIndicator(empty: Boolean) {
val binding = requireBinding()
binding.homeRecycler.isInvisible = empty
binding.homeNoMusic.isInvisible = !empty
}
private fun updateSelection(selection: List<Music>) { private fun updateSelection(selection: List<Music>) {
albumAdapter.setSelected(selection.filterIsInstanceTo(mutableSetOf())) albumAdapter.setSelected(selection.filterIsInstanceTo(mutableSetOf()))
} }

View file

@ -81,9 +81,10 @@ class ArtistListFragment :
} }
binding.homeNoMusicMsg.text = getString(R.string.lng_empty_artists) binding.homeNoMusicMsg.text = getString(R.string.lng_empty_artists)
binding.homeChooseMusicSources.setOnClickListener { homeModel.startChooseMusicLocations() } binding.homeNoMusicAction.setOnClickListener { homeModel.startChooseMusicLocations() }
collectImmediately(homeModel.artistList, ::updateArtists) collectImmediately(homeModel.artistList, ::updateArtists)
collectImmediately(homeModel.empty, ::updateNoMusicIndicator)
collectImmediately(listModel.selected, ::updateSelection) collectImmediately(listModel.selected, ::updateSelection)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
@ -129,13 +130,15 @@ class ArtistListFragment :
} }
private fun updateArtists(artists: List<Artist>) { private fun updateArtists(artists: List<Artist>) {
requireBinding().apply {
homeRecycler.isInvisible = artists.isEmpty()
homeNoMusic.isInvisible = artists.isNotEmpty()
}
artistAdapter.update(artists, homeModel.artistInstructions.consume()) artistAdapter.update(artists, homeModel.artistInstructions.consume())
} }
private fun updateNoMusicIndicator(empty: Boolean) {
val binding = requireBinding()
binding.homeRecycler.isInvisible = empty
binding.homeNoMusic.isInvisible = !empty
}
private fun updateSelection(selection: List<Music>) { private fun updateSelection(selection: List<Music>) {
artistAdapter.setSelected(selection.filterIsInstanceTo(mutableSetOf())) artistAdapter.setSelected(selection.filterIsInstanceTo(mutableSetOf()))
} }

View file

@ -80,9 +80,10 @@ class GenreListFragment :
} }
binding.homeNoMusicMsg.text = getString(R.string.lng_empty_genres) binding.homeNoMusicMsg.text = getString(R.string.lng_empty_genres)
binding.homeChooseMusicSources.setOnClickListener { homeModel.startChooseMusicLocations() } binding.homeNoMusicAction.setOnClickListener { homeModel.startChooseMusicLocations() }
collectImmediately(homeModel.genreList, ::updateGenres) collectImmediately(homeModel.genreList, ::updateGenres)
collectImmediately(homeModel.empty, ::updateNoMusicIndicator)
collectImmediately(listModel.selected, ::updateSelection) collectImmediately(listModel.selected, ::updateSelection)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
@ -128,13 +129,15 @@ class GenreListFragment :
} }
private fun updateGenres(genres: List<Genre>) { private fun updateGenres(genres: List<Genre>) {
requireBinding().apply {
homeRecycler.isInvisible = genres.isEmpty()
homeNoMusic.isInvisible = genres.isNotEmpty()
}
genreAdapter.update(genres, homeModel.genreInstructions.consume()) genreAdapter.update(genres, homeModel.genreInstructions.consume())
} }
private fun updateNoMusicIndicator(empty: Boolean) {
val binding = requireBinding()
binding.homeRecycler.isInvisible = empty
binding.homeNoMusic.isInvisible = !empty
}
private fun updateSelection(selection: List<Music>) { private fun updateSelection(selection: List<Music>) {
genreAdapter.setSelected(selection.filterIsInstanceTo(mutableSetOf())) genreAdapter.setSelected(selection.filterIsInstanceTo(mutableSetOf()))
} }

View file

@ -78,9 +78,8 @@ class PlaylistListFragment :
} }
binding.homeNoMusicMsg.text = getString(R.string.lng_empty_playlists) binding.homeNoMusicMsg.text = getString(R.string.lng_empty_playlists)
binding.homeChooseMusicSources.setOnClickListener { homeModel.startChooseMusicLocations() }
collectImmediately(homeModel.playlistList, ::updatePlaylists) collectImmediately(homeModel.playlistList, ::updatePlaylists)
collectImmediately(homeModel.empty, homeModel.playlistList, ::updateNoMusicIndicator)
collectImmediately(listModel.selected, ::updateSelection) collectImmediately(listModel.selected, ::updateSelection)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
@ -126,13 +125,22 @@ class PlaylistListFragment :
} }
private fun updatePlaylists(playlists: List<Playlist>) { private fun updatePlaylists(playlists: List<Playlist>) {
requireBinding().apply {
homeRecycler.isInvisible = playlists.isEmpty()
homeNoMusic.isInvisible = playlists.isNotEmpty()
}
playlistAdapter.update(playlists, homeModel.playlistInstructions.consume()) playlistAdapter.update(playlists, homeModel.playlistInstructions.consume())
} }
private fun updateNoMusicIndicator(empty: Boolean, playlists: List<Playlist>) {
val binding = requireBinding()
binding.homeRecycler.isInvisible = empty
binding.homeNoMusic.isInvisible = !empty && playlists.isNotEmpty()
if (!empty && playlists.isEmpty()) {
binding.homeNoMusicAction.text = getString(R.string.lbl_new_playlist)
binding.homeNoMusicAction.setOnClickListener { musicModel.createPlaylist() }
} else {
binding.homeNoMusicAction.text = getString(R.string.lbl_music_sources)
binding.homeNoMusicAction.setOnClickListener { homeModel.startChooseMusicLocations() }
}
}
private fun updateSelection(selection: List<Music>) { private fun updateSelection(selection: List<Music>) {
playlistAdapter.setSelected(selection.filterIsInstanceTo(mutableSetOf())) playlistAdapter.setSelected(selection.filterIsInstanceTo(mutableSetOf()))
} }

View file

@ -85,9 +85,10 @@ class SongListFragment :
} }
binding.homeNoMusicMsg.text = getString(R.string.lng_empty_songs) binding.homeNoMusicMsg.text = getString(R.string.lng_empty_songs)
binding.homeChooseMusicSources.setOnClickListener { homeModel.startChooseMusicLocations() } binding.homeNoMusicAction.setOnClickListener { homeModel.startChooseMusicLocations() }
collectImmediately(homeModel.songList, ::updateSongs) collectImmediately(homeModel.songList, ::updateSongs)
collectImmediately(homeModel.empty, ::updateNoMusicIndicator)
collectImmediately(listModel.selected, ::updateSelection) collectImmediately(listModel.selected, ::updateSelection)
collectImmediately( collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback) playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
@ -154,13 +155,15 @@ class SongListFragment :
} }
private fun updateSongs(songs: List<Song>) { private fun updateSongs(songs: List<Song>) {
requireBinding().apply {
homeRecycler.isInvisible = songs.isEmpty()
homeNoMusic.isInvisible = songs.isNotEmpty()
}
songAdapter.update(songs, homeModel.songInstructions.consume()) songAdapter.update(songs, homeModel.songInstructions.consume())
} }
private fun updateNoMusicIndicator(empty: Boolean) {
val binding = requireBinding()
binding.homeRecycler.isInvisible = empty
binding.homeNoMusic.isInvisible = !empty
}
private fun updateSelection(selection: List<Music>) { private fun updateSelection(selection: List<Music>) {
songAdapter.setSelected(selection.filterIsInstanceTo(mutableSetOf())) songAdapter.setSelected(selection.filterIsInstanceTo(mutableSetOf()))
} }

View file

@ -46,7 +46,7 @@
android:textAppearance="?attr/textAppearanceBodyLarge" /> android:textAppearance="?attr/textAppearanceBodyLarge" />
<org.oxycblt.auxio.ui.RippleFixMaterialButton <org.oxycblt.auxio.ui.RippleFixMaterialButton
android:id="@+id/home_choose_music_sources" android:id="@+id/home_no_music_action"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/lbl_music_sources" /> android:text="@string/lbl_music_sources" />