Add Artist item

Add the Artist item and switch LibraryFragment over to it.
This commit is contained in:
OxygenCobalt 2020-09-04 17:11:16 -06:00
parent 139cf3c089
commit 7a2c779fe2
15 changed files with 172 additions and 29 deletions

View file

@ -10,7 +10,7 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentLibraryBinding import org.oxycblt.auxio.databinding.FragmentLibraryBinding
import org.oxycblt.auxio.recycler.adapters.AlbumAdapter import org.oxycblt.auxio.recycler.adapters.ArtistAdapter
import org.oxycblt.auxio.recycler.applyDivider import org.oxycblt.auxio.recycler.applyDivider
import org.oxycblt.auxio.recycler.viewholders.ClickListener import org.oxycblt.auxio.recycler.viewholders.ClickListener
@ -29,10 +29,10 @@ class LibraryFragment : Fragment() {
inflater, R.layout.fragment_library, container, false inflater, R.layout.fragment_library, container, false
) )
binding.libraryRecycler.adapter = AlbumAdapter( binding.libraryRecycler.adapter = ArtistAdapter(
libraryModel.albums.value!!, libraryModel.artists.value!!,
ClickListener { album -> ClickListener { artist ->
Log.d(this::class.simpleName, album.title) Log.d(this::class.simpleName, artist.name)
} }
) )
binding.libraryRecycler.applyDivider() binding.libraryRecycler.applyDivider()

View file

@ -21,6 +21,8 @@ import org.oxycblt.auxio.music.processing.MusicLoaderResponse
class LoadingFragment : Fragment(R.layout.fragment_loading) { class LoadingFragment : Fragment(R.layout.fragment_loading) {
// TODO: Phase out LoadingFragment
private val loadingModel: LoadingViewModel by lazy { private val loadingModel: LoadingViewModel by lazy {
ViewModelProvider( ViewModelProvider(
this, this,
@ -108,6 +110,7 @@ class LoadingFragment : Fragment(R.layout.fragment_loading) {
// Don't run this if the value is null, Which is what the value changes to after // Don't run this if the value is null, Which is what the value changes to after
// this is run. // this is run.
repoResponse?.let { response -> repoResponse?.let { response ->
binding.loadingBar.visibility = View.GONE
if (response == MusicLoaderResponse.DONE) { if (response == MusicLoaderResponse.DONE) {
val inflater = TransitionInflater.from(requireContext()) val inflater = TransitionInflater.from(requireContext())
@ -119,7 +122,6 @@ class LoadingFragment : Fragment(R.layout.fragment_loading) {
} else { } else {
// If the response wasn't a success, then show the specific error message // If the response wasn't a success, then show the specific error message
// depending on which error response was given, along with a retry button // depending on which error response was given, along with a retry button
binding.loadingBar.visibility = View.GONE
binding.errorText.visibility = View.VISIBLE binding.errorText.visibility = View.VISIBLE
binding.statusIcon.visibility = View.VISIBLE binding.statusIcon.visibility = View.VISIBLE
binding.retryButton.visibility = View.VISIBLE binding.retryButton.visibility = View.VISIBLE

View file

@ -9,6 +9,7 @@ import androidx.databinding.BindingAdapter
import coil.load import coil.load
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.models.Album import org.oxycblt.auxio.music.models.Album
import org.oxycblt.auxio.music.models.Artist
import org.oxycblt.auxio.music.models.Song import org.oxycblt.auxio.music.models.Song
private val ID3_GENRES = arrayOf( private val ID3_GENRES = arrayOf(
@ -83,12 +84,32 @@ fun ImageView.getCoverArt(any: Any) {
} }
} }
fun Int.toSongCount(): Int {
return if (this < 2) {
R.string.label_single_song
} else {
R.string.format_multi_song_count
}
}
fun Int.toAlbumCount(): Int {
return if (this < 2) {
R.string.label_single_album
} else {
R.string.format_album_count
}
}
// Format the amount of songs in an album // Format the amount of songs in an album
@BindingAdapter("songCount") @BindingAdapter("songCount")
fun TextView.getAlbumSongs(album: Album) { fun TextView.getAlbumSongs(album: Album) {
text = if (album.numSongs < 2) { text = context.getString(album.numSongs.toSongCount(), album.numSongs)
context.getString(R.string.label_single_song) }
} else {
context.getString(R.string.format_multi_song_count, album.numSongs.toString()) @BindingAdapter("albumSongCount")
} fun TextView.getSongAlbumCount(artist: Artist) {
val albums = context.getString(artist.numAlbums.toAlbumCount(), artist.numAlbums)
val songs = context.getString(artist.numSongs.toSongCount(), artist.numSongs)
text = context.getString(R.string.format_combined_song_album, albums, songs)
} }

View file

@ -8,4 +8,5 @@ data class Artist(
) { ) {
val albums = mutableListOf<Album>() val albums = mutableListOf<Album>()
var numAlbums = 0 var numAlbums = 0
var numSongs = 0
} }

View file

@ -80,6 +80,9 @@ class MusicSorter(
} }
artist.numAlbums = artist.albums.size artist.numAlbums = artist.albums.size
artist.albums.forEach { album ->
artist.numSongs += album.numSongs
}
unknownAlbums.removeAll(artistAlbums) unknownAlbums.removeAll(artistAlbums)
} }
@ -95,7 +98,10 @@ class MusicSorter(
unknownArtist.albums.add(album) unknownArtist.albums.add(album)
} }
unknownArtist.numAlbums = albums.size unknownArtist.numAlbums = unknownArtist.albums.size
unknownArtist.albums.forEach { album ->
unknownArtist.numSongs += album.numSongs
}
artists.add(unknownArtist) artists.add(unknownArtist)

View file

@ -29,10 +29,6 @@ class AlbumAdapter(
override fun onBindViewHolder(holder: AlbumViewHolder, position: Int) { override fun onBindViewHolder(holder: AlbumViewHolder, position: Int) {
val album = data[position] val album = data[position]
holder.itemView.setOnClickListener {
listener.onClick(album)
}
holder.bind(album) holder.bind(album)
} }
} }

View file

@ -0,0 +1,34 @@
package org.oxycblt.auxio.recycler.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.ArtistItemBinding
import org.oxycblt.auxio.music.models.Artist
import org.oxycblt.auxio.recycler.viewholders.ArtistViewHolder
import org.oxycblt.auxio.recycler.viewholders.ClickListener
class ArtistAdapter(
private val data: List<Artist>,
private val listener: ClickListener<Artist>
) : RecyclerView.Adapter<ArtistViewHolder>() {
override fun getItemCount(): Int = data.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArtistViewHolder {
val binding = ArtistItemBinding.inflate(LayoutInflater.from(parent.context))
// Force the item to *actually* be the screen width so ellipsizing can work.
binding.root.layoutParams = RecyclerView.LayoutParams(
RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT
)
return ArtistViewHolder(binding)
}
override fun onBindViewHolder(holder: ArtistViewHolder, position: Int) {
val album = data[position]
holder.bind(album)
}
}

View file

@ -0,0 +1,20 @@
package org.oxycblt.auxio.recycler.viewholders
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.ArtistItemBinding
import org.oxycblt.auxio.music.models.Artist
// Generic ViewHolder for an album
class ArtistViewHolder(
private val binding: ArtistItemBinding
) : RecyclerView.ViewHolder(binding.root) {
// Bind the view w/new data
fun bind(artist: Artist) {
binding.artist = artist
binding.artistName.requestLayout()
binding.executePendingBindings()
}
}

View file

@ -13,7 +13,6 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:background="@drawable/ripple" android:background="@drawable/ripple"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
@ -23,12 +22,12 @@
android:id="@+id/cover" android:id="@+id/cover"
android:layout_width="@dimen/cover_size_normal" android:layout_width="@dimen/cover_size_normal"
android:layout_height="@dimen/cover_size_normal" android:layout_height="@dimen/cover_size_normal"
android:contentDescription="@{@string/description_cover_art + album.title}"
app:coverArt="@{album}" app:coverArt="@{album}"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/backgrounds/scenic" /> tools:src="@tools:sample/backgrounds/scenic"
tools:ignore="ContentDescription" />
<TextView <TextView
android:id="@+id/album_name" android:id="@+id/album_name"

View file

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="artist"
type="org.oxycblt.auxio.music.models.Artist" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/ripple"
android:clickable="true"
android:focusable="true"
android:padding="@dimen/padding_medium">
<ImageView
android:id="@+id/artist_image"
android:layout_width="@dimen/cover_size_normal"
android:layout_height="@dimen/cover_size_normal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/backgrounds/scenic"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/artist_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_medium"
android:text="@{artist.name}"
android:textAppearance="?android:attr/textAppearanceListItem"
android:textColor="?android:attr/textColorPrimary"
android:ellipsize="end"
android:maxLines="1"
app:layout_constraintBottom_toTopOf="@+id/album_song_count"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/artist_image"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Artist Name" />
<TextView
android:id="@+id/album_song_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_medium"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
app:albumSongCount="@{artist}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/artist_image"
app:layout_constraintTop_toBottomOf="@+id/artist_name"
tools:text="2 Albums, 20 Songs" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View file

@ -6,6 +6,7 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:orientation="vertical"> android:orientation="vertical">
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
@ -15,7 +16,8 @@
android:background="?android:attr/windowBackground" android:background="?android:attr/windowBackground"
android:elevation="@dimen/elevation_normal" android:elevation="@dimen/elevation_normal"
app:titleTextAppearance="@style/TextAppearance.Toolbar.Bold" app:titleTextAppearance="@style/TextAppearance.Toolbar.Bold"
app:title="@string/title_library_fragment" /> app:title="@string/title_library_fragment"
tools:titleTextColor="@color/blue"/>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/library_recycler" android:id="@+id/library_recycler"

View file

@ -39,14 +39,14 @@
android:indeterminateTint="?attr/colorPrimary" android:indeterminateTint="?attr/colorPrimary"
android:indeterminateTintMode="src_in" android:indeterminateTintMode="src_in"
android:src="@drawable/ic_error" android:src="@drawable/ic_error"
android:contentDescription="@string/description_error_icon"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/error_text" app:layout_constraintBottom_toTopOf="@+id/error_text"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/loading_bar" app:layout_constraintTop_toBottomOf="@+id/loading_bar"
app:srcCompat="@drawable/ic_error" app:srcCompat="@drawable/ic_error"
tools:tint="@color/blue" /> tools:tint="@color/blue"
tools:ignore="ContentDescription" />
<TextView <TextView
android:id="@+id/error_text" android:id="@+id/error_text"

View file

@ -6,6 +6,7 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:orientation="vertical"> android:orientation="vertical">
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
@ -16,7 +17,8 @@
android:elevation="@dimen/elevation_normal" android:elevation="@dimen/elevation_normal"
app:titleTextAppearance="@style/TextAppearance.Toolbar.Bold" app:titleTextAppearance="@style/TextAppearance.Toolbar.Bold"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:title="@string/title_all_songs" /> app:title="@string/title_all_songs"
tools:titleTextColor="@color/blue"/>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/song_recycler" android:id="@+id/song_recycler"

View file

@ -13,7 +13,6 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:background="@drawable/ripple" android:background="@drawable/ripple"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
@ -23,12 +22,12 @@
android:id="@+id/cover" android:id="@+id/cover"
android:layout_width="@dimen/cover_size_compact" android:layout_width="@dimen/cover_size_compact"
android:layout_height="@dimen/cover_size_compact" android:layout_height="@dimen/cover_size_compact"
android:contentDescription="@{@string/description_cover_art + song.album.title}"
app:coverArt="@{song}" app:coverArt="@{song}"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:src="@tools:sample/backgrounds/scenic" /> tools:src="@tools:sample/backgrounds/scenic"
tools:ignore="ContentDescription" />
<TextView <TextView
android:id="@+id/song_name" android:id="@+id/song_name"

View file

@ -12,6 +12,7 @@
<string name="label_retry">Retry</string> <string name="label_retry">Retry</string>
<string name="label_grant">Grant</string> <string name="label_grant">Grant</string>
<string name="label_single_song">1 Song</string> <string name="label_single_song">1 Song</string>
<string name="label_single_album">1 Album</string>
<string name="placeholder_unknown_genre">Unknown Genre</string> <string name="placeholder_unknown_genre">Unknown Genre</string>
<string name="placeholder_unknown_artist">Unknown Artist</string> <string name="placeholder_unknown_artist">Unknown Artist</string>
@ -19,7 +20,6 @@
<string name="format_multi_song_count">%s Songs</string> <string name="format_multi_song_count">%s Songs</string>
<string name="format_song_info">%s / %s</string> <string name="format_song_info">%s / %s</string>
<string name="format_album_count">%s Albums</string>
<string name="description_cover_art">Cover art for </string> <string name="format_combined_song_album">%s, %s</string>
<string name="description_error_icon">Error Icon</string>
</resources> </resources>