detail: improve playlist presentation

Improve playlist presentation in the detail views, especially when
it is empty.
This commit is contained in:
Alexander Capehart 2023-05-13 19:31:28 -06:00
parent 7435165929
commit 949a9c879c
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
7 changed files with 62 additions and 34 deletions

View file

@ -46,6 +46,8 @@ import org.oxycblt.auxio.util.*
* [ViewModel] that manages the Song, Album, Artist, and Genre detail views. Keeps track of the * [ViewModel] that manages the Song, Album, Artist, and Genre detail views. Keeps track of the
* current item they are showing, sub-data to display, and configuration. * current item they are showing, sub-data to display, and configuration.
* *
* FIXME: Need to do direct item comparison in equality checks, or reset on navigation.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@HiltViewModel @HiltViewModel
@ -416,15 +418,16 @@ constructor(
private fun refreshPlaylistList(playlist: Playlist, replace: Boolean = false) { private fun refreshPlaylistList(playlist: Playlist, replace: Boolean = false) {
logD("Refreshing playlist list") logD("Refreshing playlist list")
var instructions: UpdateInstructions = UpdateInstructions.Diff
val list = mutableListOf<Item>() val list = mutableListOf<Item>()
list.add(SortHeader(R.string.lbl_songs))
val instructions = if (playlist.songs.isNotEmpty()) {
list.add(SortHeader(R.string.lbl_songs))
if (replace) { if (replace) {
UpdateInstructions.Replace(list.size) instructions = UpdateInstructions.Replace(list.size)
} else {
UpdateInstructions.Diff
} }
list.addAll(playlistSongSort.songs(playlist.songs)) list.addAll(playlistSongSort.songs(playlist.songs))
}
_playlistInstructions.put(instructions) _playlistInstructions.put(instructions)
_playlistList.value = list _playlistList.value = list
} }

View file

@ -65,6 +65,17 @@ private constructor(private val binding: ItemDetailHeaderBinding) :
binding.detailType.text = binding.context.getString(R.string.lbl_artist) binding.detailType.text = binding.context.getString(R.string.lbl_artist)
binding.detailName.text = artist.name.resolve(binding.context) binding.detailName.text = artist.name.resolve(binding.context)
// Song and album counts map to the info
binding.detailInfo.text =
binding.context.getString(
R.string.fmt_two,
binding.context.getPlural(R.plurals.fmt_album_count, artist.albums.size),
if (artist.songs.isNotEmpty()) {
binding.context.getPlural(R.plurals.fmt_song_count, artist.songs.size)
} else {
binding.context.getString(R.string.def_song_count)
})
if (artist.songs.isNotEmpty()) { if (artist.songs.isNotEmpty()) {
// Information about the artist's genre(s) map to the sub-head text // Information about the artist's genre(s) map to the sub-head text
binding.detailSubhead.apply { binding.detailSubhead.apply {
@ -72,13 +83,6 @@ private constructor(private val binding: ItemDetailHeaderBinding) :
text = artist.genres.resolveNames(context) text = artist.genres.resolveNames(context)
} }
// Song and album counts map to the info
binding.detailInfo.text =
binding.context.getString(
R.string.fmt_two,
binding.context.getPlural(R.plurals.fmt_album_count, artist.albums.size),
binding.context.getPlural(R.plurals.fmt_song_count, artist.songs.size))
// In the case that this header used to he configured to have no songs, // In the case that this header used to he configured to have no songs,
// we want to reset the visibility of all information that was hidden. // we want to reset the visibility of all information that was hidden.
binding.detailPlayButton.isVisible = true binding.detailPlayButton.isVisible = true
@ -88,10 +92,8 @@ private constructor(private val binding: ItemDetailHeaderBinding) :
// ex. Play and Shuffle, Song Counts, and Genre Information. // ex. Play and Shuffle, Song Counts, and Genre Information.
// Artists are always guaranteed to have albums however, so continue to show those. // Artists are always guaranteed to have albums however, so continue to show those.
binding.detailSubhead.isVisible = false binding.detailSubhead.isVisible = false
binding.detailInfo.text = binding.detailPlayButton.isEnabled = false
binding.context.getPlural(R.plurals.fmt_album_count, artist.albums.size) binding.detailShuffleButton.isEnabled = false
binding.detailPlayButton.isVisible = false
binding.detailShuffleButton.isVisible = false
} }
binding.detailPlayButton.setOnClickListener { listener.onPlay() } binding.detailPlayButton.setOnClickListener { listener.onPlay() }

View file

@ -65,11 +65,26 @@ private constructor(private val binding: ItemDetailHeaderBinding) :
binding.detailName.text = playlist.name.resolve(binding.context) binding.detailName.text = playlist.name.resolve(binding.context)
// Nothing about a playlist is applicable to the sub-head text. // Nothing about a playlist is applicable to the sub-head text.
binding.detailSubhead.isVisible = false binding.detailSubhead.isVisible = false
// The song count of the playlist maps to the info text. // The song count of the playlist maps to the info text.
binding.detailInfo.text = binding.detailInfo.apply {
binding.context.getPlural(R.plurals.fmt_song_count, playlist.songs.size) isVisible = true
binding.detailPlayButton.setOnClickListener { listener.onPlay() } text =
binding.detailShuffleButton.setOnClickListener { listener.onShuffle() } if (playlist.songs.isNotEmpty()) {
binding.context.getPlural(R.plurals.fmt_song_count, playlist.songs.size)
} else {
binding.context.getString(R.string.def_song_count)
}
}
binding.detailPlayButton.apply {
isEnabled = playlist.songs.isNotEmpty()
setOnClickListener { listener.onPlay() }
}
binding.detailShuffleButton.apply {
isEnabled = playlist.songs.isNotEmpty()
setOnClickListener { listener.onShuffle() }
}
} }
companion object { companion object {

View file

@ -157,15 +157,14 @@ class ArtistViewHolder private constructor(private val binding: ItemParentBindin
binding.parentImage.bind(artist) binding.parentImage.bind(artist)
binding.parentName.text = artist.name.resolve(binding.context) binding.parentName.text = artist.name.resolve(binding.context)
binding.parentInfo.text = binding.parentInfo.text =
if (artist.songs.isNotEmpty()) { binding.context.getString(
binding.context.getString( R.string.fmt_two,
R.string.fmt_two, binding.context.getPlural(R.plurals.fmt_album_count, artist.albums.size),
binding.context.getPlural(R.plurals.fmt_album_count, artist.albums.size), if (artist.songs.isNotEmpty()) {
binding.context.getPlural(R.plurals.fmt_song_count, artist.songs.size)) binding.context.getPlural(R.plurals.fmt_song_count, artist.songs.size)
} else { } else {
// Artist has no songs, only display an album count. binding.context.getString(R.string.def_song_count)
binding.context.getPlural(R.plurals.fmt_album_count, artist.albums.size) })
}
} }
override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) { override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) {
@ -275,7 +274,11 @@ class PlaylistViewHolder private constructor(private val binding: ItemParentBind
binding.parentImage.bind(playlist) binding.parentImage.bind(playlist)
binding.parentName.text = playlist.name.resolve(binding.context) binding.parentName.text = playlist.name.resolve(binding.context)
binding.parentInfo.text = binding.parentInfo.text =
binding.context.getPlural(R.plurals.fmt_song_count, playlist.songs.size) if (playlist.songs.isNotEmpty()) {
binding.context.getPlural(R.plurals.fmt_song_count, playlist.songs.size)
} else {
binding.context.getString(R.string.def_song_count)
}
} }
override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) { override fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) {

View file

@ -326,6 +326,7 @@
<string name="def_genre">Unknown genre</string> <string name="def_genre">Unknown genre</string>
<string name="def_date">No date</string> <string name="def_date">No date</string>
<string name="def_track">No track</string> <string name="def_track">No track</string>
<string name="def_song_count">No songs</string>
<string name="def_playback">No music playing</string> <string name="def_playback">No music playing</string>
<!-- Codec Namespace | Format names --> <!-- Codec Namespace | Format names -->

View file

@ -62,6 +62,10 @@ open class FakeMusicRepository : MusicRepository {
throw NotImplementedError() throw NotImplementedError()
} }
override fun addToPlaylist(songs: List<Song>, playlist: Playlist) {
throw NotImplementedError()
}
override fun requestIndex(withCache: Boolean) { override fun requestIndex(withCache: Boolean) {
throw NotImplementedError() throw NotImplementedError()
} }

View file

@ -32,7 +32,7 @@ class MusicViewModelTest {
TestMusicRepository().apply { TestMusicRepository().apply {
indexingState = IndexingState.Indexing(IndexingProgress.Indeterminate) indexingState = IndexingState.Indexing(IndexingProgress.Indeterminate)
} }
val musicViewModel = MusicViewModel(indexer) val musicViewModel = MusicViewModel(indexer, FakeMusicSettings())
assertTrue(indexer.updateListener is MusicViewModel) assertTrue(indexer.updateListener is MusicViewModel)
assertTrue(indexer.indexingListener is MusicViewModel) assertTrue(indexer.indexingListener is MusicViewModel)
assertEquals( assertEquals(
@ -47,7 +47,7 @@ class MusicViewModelTest {
@Test @Test
fun statistics() { fun statistics() {
val musicRepository = TestMusicRepository() val musicRepository = TestMusicRepository()
val musicViewModel = MusicViewModel(musicRepository) val musicViewModel = MusicViewModel(musicRepository, FakeMusicSettings())
assertEquals(null, musicViewModel.statistics.value) assertEquals(null, musicViewModel.statistics.value)
musicRepository.deviceLibrary = TestDeviceLibrary() musicRepository.deviceLibrary = TestDeviceLibrary()
assertEquals( assertEquals(
@ -64,7 +64,7 @@ class MusicViewModelTest {
@Test @Test
fun requests() { fun requests() {
val indexer = TestMusicRepository() val indexer = TestMusicRepository()
val musicViewModel = MusicViewModel(indexer) val musicViewModel = MusicViewModel(indexer, FakeMusicSettings())
musicViewModel.refresh() musicViewModel.refresh()
musicViewModel.rescan() musicViewModel.rescan()
assertEquals(listOf(true, false), indexer.requests) assertEquals(listOf(true, false), indexer.requests)