Improve genre loading

Temporarily remove the ability to play from a genre, update MusicLoader to not load genres/artists simultaniously so that Artist ID's arent completely garbled. Improve PlaybackFragment slightly.
This commit is contained in:
OxygenCobalt 2020-10-15 08:49:32 -06:00
parent c17666752b
commit 339100e436
12 changed files with 81 additions and 154 deletions

View file

@ -121,13 +121,13 @@ class AlbumDetailFragment : Fragment() {
} }
// Update the play button depending on the current playback status // Update the play button depending on the current playback status
// If the shown album is currently playing, set the button icon to the current isPlaying // If playing this album -> Make button show media controls
// status, and then set its behavior to modify isPlaying. // If not playing this album -> Make button update playback to the artist
// If the shown album isn't currently playing, set the button to Play and its behavior
// to start the playback of the album.
private fun updatePlayButton(mode: PlaybackMode, binding: FragmentAlbumDetailBinding) { private fun updatePlayButton(mode: PlaybackMode, binding: FragmentAlbumDetailBinding) {
playbackModel.currentSong.value?.let { song -> playbackModel.currentParent.value?.let { parent ->
if (mode == PlaybackMode.IN_ALBUM && song.album == detailModel.currentAlbum.value) { if (mode == PlaybackMode.IN_ALBUM &&
parent.id == detailModel.currentAlbum.value!!.id
) {
if (playbackModel.isPlaying.value!!) { if (playbackModel.isPlaying.value!!) {
binding.albumPlay.setImageResource(R.drawable.ic_pause) binding.albumPlay.setImageResource(R.drawable.ic_pause)
} else { } else {

View file

@ -106,14 +106,12 @@ class ArtistDetailFragment : Fragment() {
} }
// Update the play button depending on the current playback status // Update the play button depending on the current playback status
// If the shown artist is currently playing, set the button icon to the current isPlaying // If playing this artist -> Make button show media controls
// status, and then set its behavior to modify isPlaying. // If not playing this artist -> Make button update playback to the artist
// If the shown artist isn't currently playing, set the button to Play and its behavior
// to start the playback of the artist.
private fun updatePlayButton(mode: PlaybackMode, binding: FragmentArtistDetailBinding) { private fun updatePlayButton(mode: PlaybackMode, binding: FragmentArtistDetailBinding) {
playbackModel.currentSong.value?.let { song -> playbackModel.currentParent.value?.let { parent ->
if (mode == PlaybackMode.IN_ARTIST && if (mode == PlaybackMode.IN_ARTIST &&
song.album.artist == detailModel.currentArtist.value parent.id == detailModel.currentArtist.value!!.id
) { ) {
if (playbackModel.isPlaying.value!!) { if (playbackModel.isPlaying.value!!) {
binding.artistPlay.setImageResource(R.drawable.ic_pause) binding.artistPlay.setImageResource(R.drawable.ic_pause)

View file

@ -9,12 +9,9 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentGenreDetailBinding import org.oxycblt.auxio.databinding.FragmentGenreDetailBinding
import org.oxycblt.auxio.detail.adapters.DetailArtistAdapter import org.oxycblt.auxio.detail.adapters.DetailArtistAdapter
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.playback.PlaybackMode
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.theme.applyDivider import org.oxycblt.auxio.theme.applyDivider
import org.oxycblt.auxio.theme.disable import org.oxycblt.auxio.theme.disable
@ -22,7 +19,6 @@ class GenreDetailFragment : Fragment() {
private val args: GenreDetailFragmentArgs by navArgs() private val args: GenreDetailFragmentArgs by navArgs()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private val playbackModel: PlaybackViewModel by activityViewModels()
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -57,7 +53,6 @@ class GenreDetailFragment : Fragment() {
binding.lifecycleOwner = this binding.lifecycleOwner = this
binding.detailModel = detailModel binding.detailModel = detailModel
binding.playbackModel = playbackModel
binding.genre = detailModel.currentGenre.value binding.genre = detailModel.currentGenre.value
binding.genreToolbar.setNavigationOnClickListener { binding.genreToolbar.setNavigationOnClickListener {
@ -89,14 +84,6 @@ class GenreDetailFragment : Fragment() {
) )
} }
playbackModel.currentMode.observe(viewLifecycleOwner) {
updatePlayButton(it, binding)
}
playbackModel.isPlaying.observe(viewLifecycleOwner) {
updatePlayButton(playbackModel.currentMode.value!!, binding)
}
Log.d(this::class.simpleName, "Fragment created.") Log.d(this::class.simpleName, "Fragment created.")
return binding.root return binding.root
@ -107,27 +94,4 @@ class GenreDetailFragment : Fragment() {
detailModel.updateNavigationStatus(false) detailModel.updateNavigationStatus(false)
} }
private fun updatePlayButton(mode: PlaybackMode, binding: FragmentGenreDetailBinding) {
if (mode == PlaybackMode.IN_GENRE &&
detailModel.currentGenre.value == playbackModel.currentGenre.value
) {
Log.d(this::class.simpleName, "Retard")
if (playbackModel.isPlaying.value!!) {
binding.genrePlay.setImageResource(R.drawable.ic_pause)
} else {
binding.genrePlay.setImageResource(R.drawable.ic_play)
}
binding.genrePlay.setOnClickListener {
playbackModel.invertPlayingStatus()
}
} else {
binding.genrePlay.setImageResource(R.drawable.ic_play)
binding.genrePlay.setOnClickListener {
playbackModel.play(detailModel.currentGenre.value!!, false)
}
}
}
} }

View file

@ -31,7 +31,7 @@ data class Song(
data class Album( data class Album(
override val id: Long = -1, override val id: Long = -1,
override val name: String, override val name: String,
val artistName: String, val artistId: Long = -1,
val coverUri: Uri = Uri.EMPTY, val coverUri: Uri = Uri.EMPTY,
val year: Int = 0 val year: Int = 0
) : BaseModel() { ) : BaseModel() {
@ -52,8 +52,7 @@ data class Album(
// Artist // Artist
data class Artist( data class Artist(
override val id: Long = -1, override val id: Long = -1,
override var name: String, override var name: String
val givenGenres: MutableList<Genre> = mutableListOf()
) : BaseModel() { ) : BaseModel() {
val albums = mutableListOf<Album>() val albums = mutableListOf<Album>()
val genres = mutableListOf<Genre>() val genres = mutableListOf<Genre>()
@ -75,14 +74,6 @@ data class Artist(
} }
return songs return songs
} }
val genreSongs: MutableList<Song>
get() {
val songs = mutableListOf<Song>()
genres.forEach {
songs.addAll(it.songs)
}
return songs
}
} }
// Genre // Genre
@ -109,14 +100,6 @@ data class Genre(
} }
return num return num
} }
val songs: MutableList<Song>
get() {
val songs = mutableListOf<Song>()
artists.forEach {
songs.addAll(it.songs)
}
return songs
}
} }
// Header [Used for search, nothing else] // Header [Used for search, nothing else]

View file

@ -1,7 +1,7 @@
package org.oxycblt.auxio.music.processing package org.oxycblt.auxio.music.processing
import android.content.ContentResolver import android.content.ContentResolver
import android.database.Cursor import android.provider.MediaStore
import android.provider.MediaStore.Audio.Albums import android.provider.MediaStore.Audio.Albums
import android.provider.MediaStore.Audio.Artists import android.provider.MediaStore.Audio.Artists
import android.provider.MediaStore.Audio.Genres import android.provider.MediaStore.Audio.Genres
@ -32,11 +32,6 @@ class MusicLoader(
var albums = mutableListOf<Album>() var albums = mutableListOf<Album>()
var songs = mutableListOf<Song>() var songs = mutableListOf<Song>()
private var genreCursor: Cursor? = null
private var artistCursor: Cursor? = null
private var albumCursor: Cursor? = null
private var songCursor: Cursor? = null
val response: MusicLoaderResponse val response: MusicLoaderResponse
init { init {
@ -67,7 +62,7 @@ class MusicLoader(
Log.d(this::class.simpleName, "Starting genre search...") Log.d(this::class.simpleName, "Starting genre search...")
// First, get a cursor for every genre in the android system // First, get a cursor for every genre in the android system
genreCursor = resolver.query( val genreCursor = resolver.query(
Genres.EXTERNAL_CONTENT_URI, Genres.EXTERNAL_CONTENT_URI,
arrayOf( arrayOf(
Genres._ID, // 0 Genres._ID, // 0
@ -86,7 +81,7 @@ class MusicLoader(
val id = cursor.getLong(idIndex) val id = cursor.getLong(idIndex)
var name = cursor.getString(nameIndex) ?: genrePlaceholder var name = cursor.getString(nameIndex) ?: genrePlaceholder
// If a genre is still in an old int-based format [Android formats it as "(INT)"], // If a genre is still in an old int-based format [Android formats it as "(INT)"],mu
// convert that to the corresponding ID3 genre. // convert that to the corresponding ID3 genre.
if (name.contains(Regex("[0123456789)]"))) { if (name.contains(Regex("[0123456789)]"))) {
name = name.toNamedGenre() ?: genrePlaceholder name = name.toNamedGenre() ?: genrePlaceholder
@ -111,42 +106,59 @@ class MusicLoader(
private fun loadArtists() { private fun loadArtists() {
Log.d(this::class.simpleName, "Starting artist search...") Log.d(this::class.simpleName, "Starting artist search...")
// Iterate through the artists for each loaded genre, and then add the genre // Load all the artists
// with the artist. val artistCursor = resolver.query(
// This is only done because using GENRE_NAME for songs is broken and has been for years. Artists.EXTERNAL_CONTENT_URI,
arrayOf(
Artists._ID, // 0
Artists.ARTIST // 1
),
null, null,
Artists.DEFAULT_SORT_ORDER
)
artistCursor?.use { cursor ->
val idIndex = cursor.getColumnIndexOrThrow(Artists._ID)
val nameIndex = cursor.getColumnIndexOrThrow(Artists.ARTIST)
while (cursor.moveToNext()) {
val id = cursor.getLong(idIndex)
var name = cursor.getString(nameIndex)
if (name == null || name == MediaStore.UNKNOWN_STRING) {
name = artistPlaceholder
}
Log.d(this::class.simpleName, id.toString())
artists.add(
Artist(
id, name
)
)
}
cursor.close()
}
// Then try to associate any genres with their respective artists.
for (genre in genres) { for (genre in genres) {
artistCursor = resolver.query( val artistGenreCursor = resolver.query(
Genres.Members.getContentUri("external", genre.id), Genres.Members.getContentUri("external", genre.id),
arrayOf( arrayOf(
Artists._ID, // 0 Genres.Members.ARTIST_ID
Artists.ARTIST // 1
), ),
null, null, null, null, null
Artists.DEFAULT_SORT_ORDER
) )
artistCursor?.use { cursor -> artistGenreCursor?.let { cursor ->
val idIndex = cursor.getColumnIndexOrThrow(Artists._ID) val idIndex = cursor.getColumnIndexOrThrow(Genres.Members.ARTIST_ID)
val nameIndex = cursor.getColumnIndexOrThrow(Artists.ARTIST)
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
val id = cursor.getLong(idIndex) val id = cursor.getLong(idIndex)
val name = cursor.getString(nameIndex) ?: artistPlaceholder
// If an artist has already been added [Which is very likely due to how genres artists.filter { it.id == id }.forEach {
// are processed], add the genre to the existing artist instead of creating a it.genres.add(genre)
// new one.
val existingArtist = artists.find { it.name == name }
if (existingArtist != null) {
existingArtist.givenGenres.add(genre)
} else {
artists.add(
Artist(
id, name,
mutableListOf(genre)
)
)
} }
} }
@ -154,9 +166,8 @@ class MusicLoader(
} }
} }
// Remove dupes [Just in case]
artists = artists.distinctBy { artists = artists.distinctBy {
it.name to it.givenGenres it.name to it.genres
}.toMutableList() }.toMutableList()
Log.d( Log.d(
@ -168,12 +179,12 @@ class MusicLoader(
private fun loadAlbums() { private fun loadAlbums() {
Log.d(this::class.simpleName, "Starting album search...") Log.d(this::class.simpleName, "Starting album search...")
albumCursor = resolver.query( val albumCursor = resolver.query(
Albums.EXTERNAL_CONTENT_URI, Albums.EXTERNAL_CONTENT_URI,
arrayOf( arrayOf(
Albums._ID, // 0 Albums._ID, // 0
Albums.ALBUM, // 1 Albums.ALBUM, // 1
Albums.ARTIST, // 2 Albums.ARTIST_ID, // 2
Albums.FIRST_YEAR, // 3 Albums.FIRST_YEAR, // 3
), ),
@ -184,20 +195,20 @@ class MusicLoader(
albumCursor?.use { cursor -> albumCursor?.use { cursor ->
val idIndex = cursor.getColumnIndexOrThrow(Albums._ID) val idIndex = cursor.getColumnIndexOrThrow(Albums._ID)
val nameIndex = cursor.getColumnIndexOrThrow(Albums.ALBUM) val nameIndex = cursor.getColumnIndexOrThrow(Albums.ALBUM)
val artistIndex = cursor.getColumnIndexOrThrow(Albums.ARTIST) val artistIdIndex = cursor.getColumnIndexOrThrow(Albums.ARTIST_ID)
val yearIndex = cursor.getColumnIndexOrThrow(Albums.FIRST_YEAR) val yearIndex = cursor.getColumnIndexOrThrow(Albums.FIRST_YEAR)
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
val id = cursor.getLong(idIndex) val id = cursor.getLong(idIndex)
val name = cursor.getString(nameIndex) ?: albumPlaceholder val name = cursor.getString(nameIndex) ?: albumPlaceholder
val artist = cursor.getString(artistIndex) ?: artistPlaceholder val artistId = cursor.getLong(artistIdIndex)
val year = cursor.getInt(yearIndex) val year = cursor.getInt(yearIndex)
val coverUri = id.toAlbumArtURI() val coverUri = id.toAlbumArtURI()
albums.add( albums.add(
Album( Album(
id, name, artist, id, name, artistId,
coverUri, year coverUri, year
) )
) )
@ -206,9 +217,8 @@ class MusicLoader(
cursor.close() cursor.close()
} }
// Remove dupes
albums = albums.distinctBy { albums = albums.distinctBy {
it.name to it.artistName to it.year to it.numSongs it.name to it.artistId to it.year to it.numSongs
}.toMutableList() }.toMutableList()
Log.d( Log.d(
@ -220,7 +230,7 @@ class MusicLoader(
private fun loadSongs() { private fun loadSongs() {
Log.d(this::class.simpleName, "Starting song search...") Log.d(this::class.simpleName, "Starting song search...")
songCursor = resolver.query( val songCursor = resolver.query(
Media.EXTERNAL_CONTENT_URI, Media.EXTERNAL_CONTENT_URI,
arrayOf( arrayOf(
Media._ID, // 0 Media._ID, // 0
@ -260,7 +270,6 @@ class MusicLoader(
cursor.close() cursor.close()
} }
// Remove dupes
songs = songs.distinctBy { songs = songs.distinctBy {
it.name to it.albumId to it.track to it.duration it.name to it.albumId to it.track to it.duration
}.toMutableList() }.toMutableList()

View file

@ -51,8 +51,7 @@ class MusicSorter(
if (unknownSongs.size > 0) { if (unknownSongs.size > 0) {
val unknownAlbum = Album( val unknownAlbum = Album(
name = albumPlaceholder, name = albumPlaceholder
artistName = artistPlaceholder
) )
for (song in unknownSongs) { for (song in unknownSongs) {
@ -76,7 +75,7 @@ class MusicSorter(
for (artist in artists) { for (artist in artists) {
// Find all albums that match the current artist name // Find all albums that match the current artist name
val artistAlbums = albums.filter { it.artistName == artist.name } val artistAlbums = albums.filter { it.artistId == artist.id }
// Then add them to the artist, along with refreshing the amount of albums // Then add them to the artist, along with refreshing the amount of albums
for (album in artistAlbums) { for (album in artistAlbums) {
@ -85,8 +84,9 @@ class MusicSorter(
} }
// Then group the artist's genres and sort them by "Prominence" // Then group the artist's genres and sort them by "Prominence"
// A.K.A Who has the most map entries // A.K.A Who has the most bugged duplicate genres
val groupedGenres = artist.givenGenres.groupBy { it.name } val groupedGenres = artist.genres.groupBy { it.name }
artist.genres.clear()
groupedGenres.keys.sortedByDescending { key -> groupedGenres.keys.sortedByDescending { key ->
groupedGenres[key]?.size groupedGenres[key]?.size
@ -129,7 +129,7 @@ class MusicSorter(
for (genre in genres) { for (genre in genres) {
// Find all artists that match the current genre // Find all artists that match the current genre
val genreArtists = artists.filter { artist -> val genreArtists = artists.filter { artist ->
artist.givenGenres.any { artist.genres.any {
it.name == genre.name it.name == genre.name
} }
} }

View file

@ -6,7 +6,7 @@ package org.oxycblt.auxio.playback
// IN_ARTIST -> Play from the songs of the artist // IN_ARTIST -> Play from the songs of the artist
// IN_ALBUM -> Play from the songs of the album // IN_ALBUM -> Play from the songs of the album
enum class PlaybackMode { enum class PlaybackMode {
IN_GENRE, IN_ARTIST, IN_ALBUM, ALL_SONGS; IN_ARTIST, IN_ALBUM, ALL_SONGS;
// Make a slice of all the values that this ShowMode covers. // Make a slice of all the values that this ShowMode covers.
// ex. SHOW_ARTISTS would return SHOW_ARTISTS, SHOW_ALBUMS, and SHOW_SONGS // ex. SHOW_ARTISTS would return SHOW_ARTISTS, SHOW_ALBUMS, and SHOW_SONGS

View file

@ -7,6 +7,7 @@ import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
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.BaseModel
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
@ -21,8 +22,8 @@ class PlaybackViewModel : ViewModel() {
private val mCurrentSong = MutableLiveData<Song>() private val mCurrentSong = MutableLiveData<Song>()
val currentSong: LiveData<Song> get() = mCurrentSong val currentSong: LiveData<Song> get() = mCurrentSong
private val mCurrentGenre = MutableLiveData<Genre>() private val mCurrentParent = MutableLiveData<BaseModel>()
val currentGenre: LiveData<Genre> get() = mCurrentGenre val currentParent: LiveData<BaseModel> get() = mCurrentParent
private val mQueue = MutableLiveData(mutableListOf<Song>()) private val mQueue = MutableLiveData(mutableListOf<Song>())
val queue: LiveData<MutableList<Song>> get() = mQueue val queue: LiveData<MutableList<Song>> get() = mQueue
@ -62,19 +63,6 @@ class PlaybackViewModel : ViewModel() {
PlaybackMode.ALL_SONGS -> musicStore.songs.toMutableList() PlaybackMode.ALL_SONGS -> musicStore.songs.toMutableList()
PlaybackMode.IN_ARTIST -> song.album.artist.songs PlaybackMode.IN_ARTIST -> song.album.artist.songs
PlaybackMode.IN_ALBUM -> song.album.songs PlaybackMode.IN_ALBUM -> song.album.songs
// Warning: Calling update() with a mode of IN_GENRE Will cause Auxio to play
// from the artist's most prominent genre instead of the song's genre.
// FIXME: This could be fixed by moving genre loading to songs
PlaybackMode.IN_GENRE -> {
Log.d(
this::class.simpleName,
"update() was called with IN_GENRES, using " +
"most prominent genre instead of the song's genre."
)
song.album.artist.genres[0].songs
}
} }
mCurrentMode.value = mode mCurrentMode.value = mode
@ -90,6 +78,7 @@ class PlaybackViewModel : ViewModel() {
mQueue.value = songs mQueue.value = songs
mCurrentIndex.value = 0 mCurrentIndex.value = 0
mCurrentParent.value = album
mCurrentMode.value = PlaybackMode.IN_ALBUM mCurrentMode.value = PlaybackMode.IN_ALBUM
} }
@ -102,6 +91,7 @@ class PlaybackViewModel : ViewModel() {
mQueue.value = songs mQueue.value = songs
mCurrentIndex.value = 0 mCurrentIndex.value = 0
mCurrentParent.value = artist
mCurrentMode.value = PlaybackMode.IN_ARTIST mCurrentMode.value = PlaybackMode.IN_ARTIST
} }
@ -112,10 +102,8 @@ class PlaybackViewModel : ViewModel() {
updatePlayback(songs[0]) updatePlayback(songs[0])
mCurrentGenre.value = genre
mQueue.value = songs mQueue.value = songs
mCurrentIndex.value = 0 mCurrentIndex.value = 0
mCurrentMode.value = PlaybackMode.IN_GENRE
} }
private fun updatePlayback(song: Song) { private fun updatePlayback(song: Song) {

View file

@ -161,6 +161,7 @@
android:overScrollMode="never" android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_song_header" app:layout_constraintTop_toBottomOf="@+id/album_song_header"
tools:itemCount="4" tools:itemCount="4"
tools:listitem="@layout/item_album_song" /> tools:listitem="@layout/item_album_song" />

View file

@ -158,6 +158,7 @@
android:overScrollMode="never" android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_album_header" app:layout_constraintTop_toBottomOf="@+id/artist_album_header"
tools:itemCount="4" tools:itemCount="4"
tools:listitem="@layout/item_album" /> tools:listitem="@layout/item_album" />

View file

@ -99,23 +99,6 @@
app:layout_constraintTop_toBottomOf="@+id/genre_counts" app:layout_constraintTop_toBottomOf="@+id/genre_counts"
tools:text="80 Songs" /> tools:text="80 Songs" />
<ImageButton
android:id="@+id/genre_play"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="@dimen/margin_tiny"
android:layout_marginEnd="@dimen/margin_medium"
android:background="@drawable/ui_circular_button"
android:backgroundTint="?android:attr/colorPrimary"
android:contentDescription="@string/description_play"
android:src="@drawable/ic_play"
android:onClick="@{() -> playbackModel.play(genre, false)}"
app:layout_constraintBottom_toBottomOf="@+id/genre_song_count"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/genre_counts" />
<TextView <TextView
android:id="@+id/genre_artist_header" android:id="@+id/genre_artist_header"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -159,6 +142,7 @@
android:overScrollMode="never" android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/genre_artist_header" app:layout_constraintTop_toBottomOf="@+id/genre_artist_header"
tools:itemCount="4" tools:itemCount="4"
tools:listitem="@layout/item_artist" /> tools:listitem="@layout/item_artist" />

View file

@ -41,7 +41,6 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_margin="@dimen/margin_mid_large" android:layout_margin="@dimen/margin_mid_large"
android:elevation="4dp"
android:contentDescription="@{@string/description_album_cover(song.name)}" android:contentDescription="@{@string/description_album_cover(song.name)}"
app:coverArt="@{song}" app:coverArt="@{song}"
app:layout_constraintBottom_toTopOf="@+id/playback_song" app:layout_constraintBottom_toTopOf="@+id/playback_song"