Fix bugs w/music loading
Fix bugs with how genres/album names are displayed.
This commit is contained in:
parent
ec45a020a5
commit
602cc2153f
14 changed files with 87 additions and 71 deletions
|
@ -12,7 +12,6 @@ import androidx.navigation.fragment.navArgs
|
||||||
import org.oxycblt.auxio.ClickListener
|
import org.oxycblt.auxio.ClickListener
|
||||||
import org.oxycblt.auxio.databinding.FragmentArtistDetailBinding
|
import org.oxycblt.auxio.databinding.FragmentArtistDetailBinding
|
||||||
import org.oxycblt.auxio.detail.adapters.DetailAlbumAdapter
|
import org.oxycblt.auxio.detail.adapters.DetailAlbumAdapter
|
||||||
import org.oxycblt.auxio.library.DetailViewModel
|
|
||||||
import org.oxycblt.auxio.music.MusicViewModel
|
import org.oxycblt.auxio.music.MusicViewModel
|
||||||
import org.oxycblt.auxio.music.models.Album
|
import org.oxycblt.auxio.music.models.Album
|
||||||
import org.oxycblt.auxio.theme.applyDivider
|
import org.oxycblt.auxio.theme.applyDivider
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package org.oxycblt.auxio.library
|
package org.oxycblt.auxio.detail
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ class DetailSongAdapter(
|
||||||
holder.bind(data[position])
|
holder.bind(data[position])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generic ViewHolder for an album
|
// Generic ViewHolder for a song
|
||||||
inner class ViewHolder(
|
inner class ViewHolder(
|
||||||
private val binding: ItemAlbumSongBinding
|
private val binding: ItemAlbumSongBinding
|
||||||
) : RecyclerView.ViewHolder(binding.root) {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
|
@ -37,13 +37,13 @@ private val ID3_GENRES = arrayOf(
|
||||||
const val PAREN_FILTER = "()"
|
const val PAREN_FILTER = "()"
|
||||||
|
|
||||||
// Convert legacy ID3 genres to a named genre
|
// Convert legacy ID3 genres to a named genre
|
||||||
fun String.toNamedGenre(): String {
|
fun String.toNamedGenre(): String? {
|
||||||
// Strip the genres of any parentheses, and convert it to an int
|
// Strip the genres of any parentheses, and convert it to an int
|
||||||
val intGenre = this.filterNot {
|
val intGenre = this.filterNot {
|
||||||
PAREN_FILTER.indexOf(it) > -1
|
PAREN_FILTER.indexOf(it) > -1
|
||||||
}.toInt()
|
}.toInt()
|
||||||
|
|
||||||
return ID3_GENRES.getOrNull(intGenre) ?: ""
|
return ID3_GENRES.getOrNull(intGenre)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert a song to its URI
|
// Convert a song to its URI
|
||||||
|
|
|
@ -69,20 +69,32 @@ class MusicViewModel(private val app: Application) : ViewModel() {
|
||||||
ioScope.launch {
|
ioScope.launch {
|
||||||
val start = System.currentTimeMillis()
|
val start = System.currentTimeMillis()
|
||||||
|
|
||||||
val loader = MusicLoader(app.contentResolver)
|
// Get the placeholder strings, which are used by MusicLoader & MusicSorter for
|
||||||
|
// any music that doesn't have metadata.
|
||||||
|
val genrePlaceholder = app.getString(R.string.placeholder_genre)
|
||||||
|
val artistPlaceholder = app.getString(R.string.placeholder_artist)
|
||||||
|
val albumPlaceholder = app.getString(R.string.placeholder_album)
|
||||||
|
|
||||||
|
val loader = MusicLoader(
|
||||||
|
app.contentResolver,
|
||||||
|
|
||||||
|
genrePlaceholder,
|
||||||
|
artistPlaceholder,
|
||||||
|
albumPlaceholder
|
||||||
|
)
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
if (loader.response == MusicLoaderResponse.DONE) {
|
if (loader.response == MusicLoaderResponse.DONE) {
|
||||||
// If the loading succeeds, then process the songs and set them.
|
// If the loading succeeds, then sort the songs and update the value
|
||||||
val sorter = MusicSorter(
|
val sorter = MusicSorter(
|
||||||
loader.genres,
|
loader.genres,
|
||||||
loader.artists,
|
loader.artists,
|
||||||
loader.albums,
|
loader.albums,
|
||||||
loader.songs,
|
loader.songs,
|
||||||
|
|
||||||
app.getString(R.string.placeholder_unknown_genre),
|
genrePlaceholder,
|
||||||
app.getString(R.string.placeholder_unknown_artist),
|
artistPlaceholder,
|
||||||
app.getString(R.string.placeholder_unknown_album)
|
albumPlaceholder
|
||||||
)
|
)
|
||||||
|
|
||||||
mSongs.value = sorter.songs.toList()
|
mSongs.value = sorter.songs.toList()
|
||||||
|
|
|
@ -4,9 +4,9 @@ import android.net.Uri
|
||||||
|
|
||||||
// Abstraction for Song
|
// Abstraction for Song
|
||||||
data class Album(
|
data class Album(
|
||||||
val id: Long = 0L,
|
val id: Long = -1,
|
||||||
var name: String = "",
|
var name: String,
|
||||||
val artistName: String = "", // only used for sorting. Use artist.name instead.
|
val artistName: String,
|
||||||
val coverUri: Uri = Uri.EMPTY,
|
val coverUri: Uri = Uri.EMPTY,
|
||||||
val year: Int = 0
|
val year: Int = 0
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -2,9 +2,9 @@ package org.oxycblt.auxio.music.models
|
||||||
|
|
||||||
// Abstraction for albums
|
// Abstraction for albums
|
||||||
data class Artist(
|
data class Artist(
|
||||||
val id: Long = 0,
|
val id: Long = -1,
|
||||||
var name: String = "",
|
var name: String,
|
||||||
val genres: MutableList<Genre> = mutableListOf(Genre())
|
val genres: MutableList<Genre> = mutableListOf()
|
||||||
) {
|
) {
|
||||||
val albums = mutableListOf<Album>()
|
val albums = mutableListOf<Album>()
|
||||||
var genre = ""
|
var genre = ""
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package org.oxycblt.auxio.music.models
|
package org.oxycblt.auxio.music.models
|
||||||
|
|
||||||
data class Genre(
|
data class Genre(
|
||||||
val id: Long = 0,
|
val id: Long = -1,
|
||||||
var name: String = "",
|
var name: String,
|
||||||
) {
|
) {
|
||||||
val artists = mutableListOf<Artist>()
|
val artists = mutableListOf<Artist>()
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,12 @@ import android.text.format.DateUtils
|
||||||
data class Song(
|
data class Song(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
var name: String,
|
var name: String,
|
||||||
val albumName: String, // Only used for sorting. Use album.title for everything else.
|
val albumId: Long,
|
||||||
val track: Int,
|
val track: Int,
|
||||||
val duration: Long
|
val duration: Long
|
||||||
) {
|
) {
|
||||||
lateinit var album: Album
|
lateinit var album: Album
|
||||||
|
|
||||||
val seconds = duration / 1000
|
val seconds = duration / 1000
|
||||||
val formattedDuration = DateUtils.formatElapsedTime(seconds)
|
val formattedDuration: String = DateUtils.formatElapsedTime(seconds)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,13 @@ enum class MusicLoaderResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Class that loads music from the FileSystem.
|
// Class that loads music from the FileSystem.
|
||||||
class MusicLoader(private val resolver: ContentResolver) {
|
class MusicLoader(
|
||||||
|
private val resolver: ContentResolver,
|
||||||
|
|
||||||
|
private val genrePlaceholder: String,
|
||||||
|
private val artistPlaceholder: String,
|
||||||
|
private val albumPlaceholder: String,
|
||||||
|
) {
|
||||||
|
|
||||||
var genres = mutableListOf<Genre>()
|
var genres = mutableListOf<Genre>()
|
||||||
var artists = mutableListOf<Artist>()
|
var artists = mutableListOf<Artist>()
|
||||||
|
@ -78,12 +84,12 @@ class MusicLoader(private val resolver: ContentResolver) {
|
||||||
|
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
val id = cursor.getLong(idIndex)
|
val id = cursor.getLong(idIndex)
|
||||||
var name = cursor.getString(nameIndex) ?: ""
|
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)"],
|
||||||
// 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()
|
name = name.toNamedGenre() ?: genrePlaceholder
|
||||||
}
|
}
|
||||||
|
|
||||||
genres.add(
|
genres.add(
|
||||||
|
@ -124,7 +130,7 @@ class MusicLoader(private val resolver: ContentResolver) {
|
||||||
|
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
val id = cursor.getLong(idIndex)
|
val id = cursor.getLong(idIndex)
|
||||||
val name = cursor.getString(nameIndex) ?: ""
|
val name = cursor.getString(nameIndex) ?: artistPlaceholder
|
||||||
|
|
||||||
// If an artist has already been added [Which is very likely due to how genres
|
// If an artist has already been added [Which is very likely due to how genres
|
||||||
// are processed], add the genre to the existing artist instead of creating a
|
// are processed], add the genre to the existing artist instead of creating a
|
||||||
|
@ -182,12 +188,16 @@ class MusicLoader(private val resolver: ContentResolver) {
|
||||||
|
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
val id = cursor.getLong(idIndex)
|
val id = cursor.getLong(idIndex)
|
||||||
val name = cursor.getString(nameIndex) ?: ""
|
var name = cursor.getString(nameIndex) ?: albumPlaceholder
|
||||||
val artist = cursor.getString(artistIndex) ?: ""
|
val artist = cursor.getString(artistIndex)
|
||||||
val year = cursor.getInt(yearIndex)
|
val year = cursor.getInt(yearIndex)
|
||||||
|
|
||||||
val coverUri = id.toAlbumArtURI()
|
val coverUri = id.toAlbumArtURI()
|
||||||
|
|
||||||
|
// Sometimes the android system will return 0 for an album name, update
|
||||||
|
// it properly if that happens.
|
||||||
|
name = if (name == "0") albumPlaceholder else name
|
||||||
|
|
||||||
albums.add(
|
albums.add(
|
||||||
Album(
|
Album(
|
||||||
id, name, artist,
|
id, name, artist,
|
||||||
|
@ -219,9 +229,9 @@ class MusicLoader(private val resolver: ContentResolver) {
|
||||||
Media._ID, // 0
|
Media._ID, // 0
|
||||||
Media.DISPLAY_NAME, // 1
|
Media.DISPLAY_NAME, // 1
|
||||||
Media.TITLE, // 2
|
Media.TITLE, // 2
|
||||||
Media.ALBUM, // 4
|
Media.ALBUM_ID, // 3
|
||||||
Media.TRACK, // 6
|
Media.TRACK, // 4
|
||||||
Media.DURATION // 7
|
Media.DURATION // 5
|
||||||
),
|
),
|
||||||
Media.IS_MUSIC + "=1", null,
|
Media.IS_MUSIC + "=1", null,
|
||||||
Media.DEFAULT_SORT_ORDER
|
Media.DEFAULT_SORT_ORDER
|
||||||
|
@ -231,20 +241,20 @@ class MusicLoader(private val resolver: ContentResolver) {
|
||||||
val idIndex = cursor.getColumnIndexOrThrow(Media._ID)
|
val idIndex = cursor.getColumnIndexOrThrow(Media._ID)
|
||||||
val fileIndex = cursor.getColumnIndexOrThrow(Media.DISPLAY_NAME)
|
val fileIndex = cursor.getColumnIndexOrThrow(Media.DISPLAY_NAME)
|
||||||
val titleIndex = cursor.getColumnIndexOrThrow(Media.TITLE)
|
val titleIndex = cursor.getColumnIndexOrThrow(Media.TITLE)
|
||||||
val albumIndex = cursor.getColumnIndexOrThrow(Media.ALBUM)
|
val albumIndex = cursor.getColumnIndexOrThrow(Media.ALBUM_ID)
|
||||||
val trackIndex = cursor.getColumnIndexOrThrow(Media.TRACK)
|
val trackIndex = cursor.getColumnIndexOrThrow(Media.TRACK)
|
||||||
val durationIndex = cursor.getColumnIndexOrThrow(Media.DURATION)
|
val durationIndex = cursor.getColumnIndexOrThrow(Media.DURATION)
|
||||||
|
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
val id = cursor.getLong(idIndex)
|
val id = cursor.getLong(idIndex)
|
||||||
val title = cursor.getString(titleIndex) ?: cursor.getString(fileIndex)
|
val title = cursor.getString(titleIndex) ?: cursor.getString(fileIndex)
|
||||||
val album = cursor.getString(albumIndex) ?: ""
|
val albumId = cursor.getLong(albumIndex)
|
||||||
val track = cursor.getInt(trackIndex)
|
val track = cursor.getInt(trackIndex)
|
||||||
val duration = cursor.getLong(durationIndex)
|
val duration = cursor.getLong(durationIndex)
|
||||||
|
|
||||||
songs.add(
|
songs.add(
|
||||||
Song(
|
Song(
|
||||||
id, title, album,
|
id, title, albumId,
|
||||||
track, duration
|
track, duration
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -255,7 +265,7 @@ class MusicLoader(private val resolver: ContentResolver) {
|
||||||
|
|
||||||
// Remove dupes
|
// Remove dupes
|
||||||
songs = songs.distinctBy {
|
songs = songs.distinctBy {
|
||||||
it.name to it.albumName to it.track to it.duration
|
it.name to it.albumId to it.track to it.duration
|
||||||
}.toMutableList()
|
}.toMutableList()
|
||||||
|
|
||||||
Log.d(
|
Log.d(
|
||||||
|
|
|
@ -21,7 +21,6 @@ class MusicSorter(
|
||||||
sortAlbumsIntoArtists()
|
sortAlbumsIntoArtists()
|
||||||
sortArtistsIntoGenres()
|
sortArtistsIntoGenres()
|
||||||
|
|
||||||
addPlaceholders()
|
|
||||||
finalizeMusic()
|
finalizeMusic()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,8 +30,9 @@ class MusicSorter(
|
||||||
val unknownSongs = songs.toMutableList()
|
val unknownSongs = songs.toMutableList()
|
||||||
|
|
||||||
for (album in albums) {
|
for (album in albums) {
|
||||||
// Find all songs that match the current album title
|
// Find all songs that match the current album ID to prevent any bugs w/comparing names.
|
||||||
val albumSongs = songs.filter { it.albumName == album.name }
|
// This cant be done with artists/genres sadly.
|
||||||
|
val albumSongs = songs.filter { it.albumId == album.id }
|
||||||
|
|
||||||
// Then add them to the album
|
// Then add them to the album
|
||||||
for (song in albumSongs) {
|
for (song in albumSongs) {
|
||||||
|
@ -40,8 +40,6 @@ class MusicSorter(
|
||||||
album.songs.add(song)
|
album.songs.add(song)
|
||||||
}
|
}
|
||||||
|
|
||||||
album.finalize()
|
|
||||||
|
|
||||||
unknownSongs.removeAll(albumSongs)
|
unknownSongs.removeAll(albumSongs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,15 +47,16 @@ class MusicSorter(
|
||||||
if (unknownSongs.size > 0) {
|
if (unknownSongs.size > 0) {
|
||||||
|
|
||||||
// Reuse an existing unknown album if one is found
|
// Reuse an existing unknown album if one is found
|
||||||
val unknownAlbum = albums.find { it.name == "" } ?: Album()
|
val unknownAlbum = Album(
|
||||||
|
name = albumPlaceholder,
|
||||||
|
artistName = artistPlaceholder
|
||||||
|
)
|
||||||
|
|
||||||
for (song in unknownSongs) {
|
for (song in unknownSongs) {
|
||||||
song.album = unknownAlbum
|
song.album = unknownAlbum
|
||||||
unknownAlbum.songs.add(song)
|
unknownAlbum.songs.add(song)
|
||||||
}
|
}
|
||||||
|
|
||||||
unknownAlbum.finalize()
|
|
||||||
|
|
||||||
albums.add(unknownAlbum)
|
albums.add(unknownAlbum)
|
||||||
|
|
||||||
Log.d(
|
Log.d(
|
||||||
|
@ -76,14 +75,14 @@ class MusicSorter(
|
||||||
// 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.artistName == artist.name }
|
||||||
|
|
||||||
|
Log.d(this::class.simpleName, artist.id.toString())
|
||||||
|
|
||||||
// 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) {
|
||||||
album.artist = artist
|
album.artist = artist
|
||||||
artist.albums.add(album)
|
artist.albums.add(album)
|
||||||
}
|
}
|
||||||
|
|
||||||
artist.finalize()
|
|
||||||
|
|
||||||
unknownAlbums.removeAll(artistAlbums)
|
unknownAlbums.removeAll(artistAlbums)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,15 +90,15 @@ class MusicSorter(
|
||||||
if (unknownAlbums.size > 0) {
|
if (unknownAlbums.size > 0) {
|
||||||
|
|
||||||
// Reuse an existing unknown artist if one is found
|
// Reuse an existing unknown artist if one is found
|
||||||
val unknownArtist = artists.find { it.name == "" } ?: Artist()
|
val unknownArtist = Artist(
|
||||||
|
name = artistPlaceholder
|
||||||
|
)
|
||||||
|
|
||||||
for (album in unknownAlbums) {
|
for (album in unknownAlbums) {
|
||||||
album.artist = unknownArtist
|
album.artist = unknownArtist
|
||||||
unknownArtist.albums.add(album)
|
unknownArtist.albums.add(album)
|
||||||
}
|
}
|
||||||
|
|
||||||
unknownArtist.finalize()
|
|
||||||
|
|
||||||
artists.add(unknownArtist)
|
artists.add(unknownArtist)
|
||||||
|
|
||||||
Log.d(
|
Log.d(
|
||||||
|
@ -124,40 +123,39 @@ class MusicSorter(
|
||||||
|
|
||||||
// Then add them to the genre, along with refreshing the amount of artists
|
// Then add them to the genre, along with refreshing the amount of artists
|
||||||
genre.artists.addAll(genreArtists)
|
genre.artists.addAll(genreArtists)
|
||||||
genre.finalize()
|
|
||||||
|
|
||||||
unknownArtists.removeAll(genreArtists)
|
unknownArtists.removeAll(genreArtists)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unknownArtists.size > 0) {
|
if (unknownArtists.size > 0) {
|
||||||
// Reuse an existing unknown genre if one is found
|
// Reuse an existing unknown genre if one is found
|
||||||
val unknownGenre = genres.find { it.name == "" } ?: Genre()
|
val unknownGenre = Genre(
|
||||||
|
name = genrePlaceholder
|
||||||
|
)
|
||||||
|
|
||||||
for (artist in unknownArtists) {
|
for (artist in unknownArtists) {
|
||||||
artist.genres.add(unknownGenre)
|
artist.genres.add(unknownGenre)
|
||||||
unknownGenre.artists.add(artist)
|
unknownGenre.artists.add(artist)
|
||||||
}
|
}
|
||||||
|
|
||||||
unknownGenre.finalize()
|
|
||||||
|
|
||||||
genres.add(unknownGenre)
|
genres.add(unknownGenre)
|
||||||
|
|
||||||
Log.d(
|
Log.d(
|
||||||
this::class.simpleName,
|
this::class.simpleName,
|
||||||
"${unknownArtists.size} albums were placed into an unknown genre."
|
"${unknownArtists.size} artists were placed into an unknown genre."
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Correct any empty names [""] with the proper placeholders [Unknown Album]
|
// Finalize music
|
||||||
private fun addPlaceholders() {
|
|
||||||
genres.forEach { if (it.name == "") it.name = genrePlaceholder }
|
|
||||||
artists.forEach { if (it.name == "") it.name = artistPlaceholder }
|
|
||||||
albums.forEach { if (it.name == "") it.name = albumPlaceholder }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort all music
|
|
||||||
private fun finalizeMusic() {
|
private fun finalizeMusic() {
|
||||||
|
// Correct any empty names [""] with the proper placeholders [Unknown Album]
|
||||||
|
genres.forEach { it.finalize() }
|
||||||
|
|
||||||
|
artists.forEach { it.finalize() }
|
||||||
|
|
||||||
|
albums.forEach { it.finalize() }
|
||||||
|
|
||||||
|
// Then finally sort the music
|
||||||
genres.sortWith(
|
genres.sortWith(
|
||||||
compareBy(String.CASE_INSENSITIVE_ORDER, { it.name })
|
compareBy(String.CASE_INSENSITIVE_ORDER, { it.name })
|
||||||
)
|
)
|
||||||
|
|
|
@ -90,7 +90,7 @@
|
||||||
tools:text="2020" />
|
tools:text="2020" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/header_songs"
|
android:id="@+id/header"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:text="@string/label_songs"
|
android:text="@string/label_songs"
|
||||||
|
@ -115,10 +115,8 @@
|
||||||
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_constraintTop_toBottomOf="@+id/header_songs"
|
app:layout_constraintTop_toBottomOf="@+id/header"
|
||||||
app:layout_constraintVertical_bias="0.0"
|
|
||||||
tools:itemCount="4"
|
tools:itemCount="4"
|
||||||
tools:layout_editor_absoluteX="0dp"
|
|
||||||
tools:listitem="@layout/item_album_song" />
|
tools:listitem="@layout/item_album_song" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
|
@ -90,7 +90,7 @@
|
||||||
tools:text="2 Albums, 20 Songs" />
|
tools:text="2 Albums, 20 Songs" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/header_albums"
|
android:id="@+id/header"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:text="@string/label_albums"
|
android:text="@string/label_albums"
|
||||||
|
@ -103,8 +103,7 @@
|
||||||
android:paddingTop="@dimen/padding_small"
|
android:paddingTop="@dimen/padding_small"
|
||||||
android:paddingBottom="@dimen/padding_small"
|
android:paddingBottom="@dimen/padding_small"
|
||||||
android:background="@drawable/header_dividers"
|
android:background="@drawable/header_dividers"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/artist_counts"
|
app:layout_constraintTop_toBottomOf="@+id/artist_counts" />
|
||||||
tools:layout_editor_absoluteX="60dp" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/album_recycler"
|
android:id="@+id/album_recycler"
|
||||||
|
@ -115,7 +114,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_constraintTop_toBottomOf="@+id/header_albums"
|
app:layout_constraintTop_toBottomOf="@+id/header"
|
||||||
tools:itemCount="4"
|
tools:itemCount="4"
|
||||||
tools:listitem="@layout/item_album" />
|
tools:listitem="@layout/item_album" />
|
||||||
|
|
||||||
|
|
|
@ -14,9 +14,9 @@
|
||||||
<string name="label_albums">Albums</string>
|
<string name="label_albums">Albums</string>
|
||||||
<string name="label_songs">Songs</string>
|
<string name="label_songs">Songs</string>
|
||||||
|
|
||||||
<string name="placeholder_unknown_genre">Unknown Genre</string>
|
<string name="placeholder_genre">Unknown Genre</string>
|
||||||
<string name="placeholder_unknown_artist">Unknown Artist</string>
|
<string name="placeholder_artist">Unknown Artist</string>
|
||||||
<string name="placeholder_unknown_album">Unknown Album</string>
|
<string name="placeholder_album">Unknown Album</string>
|
||||||
|
|
||||||
<string name="format_info">%1$s / %2$s</string>
|
<string name="format_info">%1$s / %2$s</string>
|
||||||
<string name="format_double_counts">%1$s, %2$s</string>
|
<string name="format_double_counts">%1$s, %2$s</string>
|
||||||
|
|
Loading…
Reference in a new issue