Update ViewHolder structure

Move all shared viewholders to /recycler/, and change structure of shared viewholders to prohibit direct instantiation.
This commit is contained in:
OxygenCobalt 2020-10-03 19:29:03 -06:00
parent 45f411fdd7
commit 2126b4f78f
15 changed files with 186 additions and 166 deletions

View file

@ -67,6 +67,8 @@ class AlbumDetailFragment : Fragment() {
// If the album was shown directly from LibraryFragment, Then enable the ability to
// navigate upwards to the parent artist
if (args.enableParentNav) {
detailModel.doneWithNavToParent()
detailModel.navToParent.observe(viewLifecycleOwner) {
if (it) {
findNavController().navigate(

View file

@ -5,9 +5,9 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
import org.oxycblt.auxio.databinding.ItemArtistAlbumBinding
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.recycler.BaseViewHolder
import org.oxycblt.auxio.recycler.ClickListener
import org.oxycblt.auxio.recycler.DiffCallback
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
class DetailAlbumAdapter(
private val listener: ClickListener<Album>
@ -23,7 +23,7 @@ class DetailAlbumAdapter(
holder.bind(getItem(position))
}
// Generic ViewHolder for an album
// Generic ViewHolder for a detail album
inner class ViewHolder(
private val binding: ItemArtistAlbumBinding
) : BaseViewHolder<Album>(binding, listener) {

View file

@ -5,9 +5,9 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
import org.oxycblt.auxio.databinding.ItemGenreArtistBinding
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.recycler.BaseViewHolder
import org.oxycblt.auxio.recycler.ClickListener
import org.oxycblt.auxio.recycler.DiffCallback
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
class DetailArtistAdapter(
private val listener: ClickListener<Artist>

View file

@ -5,9 +5,9 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
import org.oxycblt.auxio.databinding.ItemAlbumSongBinding
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.recycler.BaseViewHolder
import org.oxycblt.auxio.recycler.ClickListener
import org.oxycblt.auxio.recycler.DiffCallback
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
class DetailSongAdapter(
private val listener: ClickListener<Song>

View file

@ -23,7 +23,7 @@ class LibraryViewModel : ViewModel() {
val searchHasFocus: Boolean get() = mSearchHasFocus
// TODO: Move these to prefs when they're added
private val mShowMode = MutableLiveData(SHOW_ARTISTS)
private val mShowMode = MutableLiveData(SHOW_ALBUMS)
val showMode: LiveData<Int> get() = mShowMode
private val mSortMode = MutableLiveData(SortMode.ALPHA_DOWN)

View file

@ -1,21 +1,21 @@
package org.oxycblt.auxio.library.recycler
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.ItemAlbumBinding
import org.oxycblt.auxio.databinding.ItemArtistBinding
import org.oxycblt.auxio.databinding.ItemGenreBinding
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.recycler.ClickListener
import org.oxycblt.auxio.recycler.viewholders.AlbumViewHolder
import org.oxycblt.auxio.recycler.viewholders.ArtistViewHolder
import org.oxycblt.auxio.recycler.viewholders.GenreViewHolder
import org.oxycblt.auxio.theme.SHOW_ALBUMS
import org.oxycblt.auxio.theme.SHOW_ARTISTS
import org.oxycblt.auxio.theme.SHOW_GENRES
// A Great Value androidx ListAdapter that can display three types of ViewHolders.
// A ListAdapter that can contain three different types of ViewHolders depending
// the showmode given. It cannot display multiple types of viewholders *at once*.
class LibraryAdapter(
private val showMode: Int,
val listener: ClickListener<BaseModel>
@ -39,36 +39,21 @@ class LibraryAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
// Return a different View Holder depending on the show type
return when (showMode) {
SHOW_GENRES -> GenreViewHolder(
listener,
ItemGenreBinding.inflate(LayoutInflater.from(parent.context))
)
SHOW_ARTISTS -> ArtistViewHolder(
listener,
ItemArtistBinding.inflate(LayoutInflater.from(parent.context))
)
SHOW_ALBUMS -> AlbumViewHolder(
listener,
ItemAlbumBinding.inflate(LayoutInflater.from(parent.context))
)
else -> ArtistViewHolder(
listener,
ItemArtistBinding.inflate(LayoutInflater.from(parent.context))
)
SHOW_GENRES -> GenreViewHolder.from(parent.context, listener)
SHOW_ARTISTS -> ArtistViewHolder.from(parent.context, listener)
SHOW_ALBUMS -> AlbumViewHolder.from(parent.context, listener)
else -> ArtistViewHolder.from(parent.context, listener)
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (showMode) {
SHOW_GENRES -> holder as GenreViewHolder
SHOW_ARTISTS -> holder as ArtistViewHolder
SHOW_ALBUMS -> holder as AlbumViewHolder
SHOW_GENRES -> (holder as GenreViewHolder).bind(data[position] as Genre)
SHOW_ARTISTS -> (holder as ArtistViewHolder).bind(data[position] as Artist)
SHOW_ALBUMS -> (holder as AlbumViewHolder).bind(data[position] as Album)
else -> return
}.bind(data[position])
}
}
// Update the data, as its an internal value.

View file

@ -1,14 +1,8 @@
package org.oxycblt.auxio.library.recycler
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.ItemAlbumBinding
import org.oxycblt.auxio.databinding.ItemArtistBinding
import org.oxycblt.auxio.databinding.ItemGenreBinding
import org.oxycblt.auxio.databinding.ItemHeaderBinding
import org.oxycblt.auxio.databinding.ItemSongBinding
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel
@ -17,62 +11,34 @@ import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.recycler.ClickListener
import org.oxycblt.auxio.recycler.DiffCallback
import org.oxycblt.auxio.recycler.viewholders.AlbumViewHolder
import org.oxycblt.auxio.recycler.viewholders.ArtistViewHolder
import org.oxycblt.auxio.recycler.viewholders.GenreViewHolder
import org.oxycblt.auxio.recycler.viewholders.HeaderViewHolder
import org.oxycblt.auxio.recycler.viewholders.SongViewHolder
class SearchAdapter(
private val listener: ClickListener<BaseModel>
) : ListAdapter<BaseModel, RecyclerView.ViewHolder>(DiffCallback<BaseModel>()) {
override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
is Genre -> ITEM_TYPE_GENRE
is Artist -> ITEM_TYPE_ARTIST
is Album -> ITEM_TYPE_ALBUM
is Song -> ITEM_TYPE_SONG
is Header -> ITEM_TYPE_HEADER
is Genre -> GenreViewHolder.ITEM_TYPE
is Artist -> ArtistViewHolder.ITEM_TYPE
is Album -> AlbumViewHolder.ITEM_TYPE
is Song -> SongViewHolder.ITEM_TYPE
is Header -> HeaderViewHolder.ITEM_TYPE
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ITEM_TYPE_GENRE -> GenreViewHolder(
listener,
ItemGenreBinding.inflate(
LayoutInflater.from(parent.context)
)
)
GenreViewHolder.ITEM_TYPE -> GenreViewHolder.from(parent.context, listener)
ArtistViewHolder.ITEM_TYPE -> ArtistViewHolder.from(parent.context, listener)
AlbumViewHolder.ITEM_TYPE -> AlbumViewHolder.from(parent.context, listener)
SongViewHolder.ITEM_TYPE -> SongViewHolder.from(parent.context, listener)
HeaderViewHolder.ITEM_TYPE -> HeaderViewHolder.from(parent.context)
ITEM_TYPE_ARTIST -> ArtistViewHolder(
listener,
ItemArtistBinding.inflate(
LayoutInflater.from(parent.context)
)
)
ITEM_TYPE_ALBUM -> AlbumViewHolder(
listener,
ItemAlbumBinding.inflate(
LayoutInflater.from(parent.context)
)
)
ITEM_TYPE_SONG -> SongViewHolder(
listener,
ItemSongBinding.inflate(
LayoutInflater.from(parent.context)
)
)
ITEM_TYPE_HEADER -> HeaderViewHolder(
ItemHeaderBinding.inflate(
LayoutInflater.from(parent.context)
)
)
else -> ArtistViewHolder(
listener,
ItemArtistBinding.inflate(
LayoutInflater.from(parent.context)
)
)
else -> HeaderViewHolder.from(parent.context)
}
}
@ -85,6 +51,6 @@ class SearchAdapter(
is HeaderViewHolder -> holder
else -> return
}.onBind(getItem(position))
}.bind(getItem(position))
}
}

View file

@ -1,75 +0,0 @@
package org.oxycblt.auxio.library.recycler
import org.oxycblt.auxio.databinding.ItemAlbumBinding
import org.oxycblt.auxio.databinding.ItemArtistBinding
import org.oxycblt.auxio.databinding.ItemGenreBinding
import org.oxycblt.auxio.databinding.ItemHeaderBinding
import org.oxycblt.auxio.databinding.ItemSongBinding
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.recycler.BaseViewHolder
import org.oxycblt.auxio.recycler.ClickListener
const val ITEM_TYPE_GENRE = 10
const val ITEM_TYPE_ARTIST = 11
const val ITEM_TYPE_ALBUM = 12
const val ITEM_TYPE_SONG = 13
const val ITEM_TYPE_HEADER = 14
class GenreViewHolder(
listener: ClickListener<BaseModel>,
private val binding: ItemGenreBinding
) : BaseViewHolder<BaseModel>(binding, listener) {
override fun onBind(model: BaseModel) {
binding.genre = model as Genre
binding.genreName.requestLayout()
}
}
class ArtistViewHolder(
listener: ClickListener<BaseModel>,
private val binding: ItemArtistBinding
) : BaseViewHolder<BaseModel>(binding, listener) {
override fun onBind(model: BaseModel) {
binding.artist = model as Artist
binding.artistName.requestLayout()
}
}
class AlbumViewHolder(
listener: ClickListener<BaseModel>,
private val binding: ItemAlbumBinding
) : BaseViewHolder<BaseModel>(binding, listener) {
override fun onBind(model: BaseModel) {
binding.album = model as Album
binding.albumName.requestLayout()
}
}
class SongViewHolder(
listener: ClickListener<BaseModel>,
private val binding: ItemSongBinding
) : BaseViewHolder<BaseModel>(binding, listener) {
override fun onBind(model: BaseModel) {
binding.song = model as Song
binding.songName.requestLayout()
binding.songInfo.requestLayout()
}
}
class HeaderViewHolder(
private val binding: ItemHeaderBinding
) : BaseViewHolder<BaseModel>(binding, null) {
override fun onBind(model: BaseModel) {
binding.header = model as Header
}
}

View file

@ -131,7 +131,7 @@ fun TextView.bindArtistCounts(artist: Artist) {
// Get a bunch of miscellaneous album information [Year, Songs, Duration] and combine them
@BindingAdapter("albumDetails")
fun TextView.bindAlbumDetails(album: Album) {
fun TextView.bindAllAlbumDetails(album: Album) {
text = context.getString(
R.string.format_double_info,
album.year.toYear(context),
@ -143,6 +143,18 @@ fun TextView.bindAlbumDetails(album: Album) {
)
}
@BindingAdapter("albumInfo")
fun TextView.bindAlbumInfo(album: Album) {
text = context.getString(
R.string.format_info,
album.artist.name,
context.resources.getQuantityString(
R.plurals.format_song_count,
album.numSongs, album.numSongs
)
)
}
@BindingAdapter("albumYear")
fun TextView.bindAlbumDate(album: Album) {
text = album.year.toYear(context)

View file

@ -4,7 +4,7 @@ import androidx.recyclerview.widget.DiffUtil
import org.oxycblt.auxio.music.BaseModel
// RecyclerView click listener
class ClickListener<T>(val onClick: (T) -> Unit)
class ClickListener<T : BaseModel>(val onClick: (T) -> Unit)
// Base Diff callback
class DiffCallback<T : BaseModel> : DiffUtil.ItemCallback<T>() {

View file

@ -1,10 +1,12 @@
package org.oxycblt.auxio.recycler
package org.oxycblt.auxio.recycler.viewholders
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.recycler.ClickListener
// ViewHolder abstraction that automates some of the things that are common for all ViewHolders.
// TODO: Implement some kind of abstraction for BaseViewHolder.from()
abstract class BaseViewHolder<T : BaseModel>(
private val baseBinding: ViewDataBinding,
protected val listener: ClickListener<T>?

View file

@ -0,0 +1,127 @@
package org.oxycblt.auxio.recycler.viewholders
import android.content.Context
import android.view.LayoutInflater
import org.oxycblt.auxio.databinding.ItemAlbumBinding
import org.oxycblt.auxio.databinding.ItemArtistBinding
import org.oxycblt.auxio.databinding.ItemGenreBinding
import org.oxycblt.auxio.databinding.ItemHeaderBinding
import org.oxycblt.auxio.databinding.ItemSongBinding
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Header
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.recycler.ClickListener
// Basic ViewHolders for each music model.
// FIXME: Mode these type signaturs to something sensible.
class GenreViewHolder private constructor(
listener: ClickListener<BaseModel>,
private val binding: ItemGenreBinding
) : BaseViewHolder<BaseModel>(binding, listener) {
override fun onBind(model: BaseModel) {
binding.genre = model as Genre
binding.genreName.requestLayout()
}
companion object {
const val ITEM_TYPE = 10
fun from(context: Context, listener: ClickListener<BaseModel>): GenreViewHolder {
return GenreViewHolder(
ClickListener { listener.onClick(it) },
ItemGenreBinding.inflate(LayoutInflater.from(context))
)
}
}
}
class ArtistViewHolder private constructor(
listener: ClickListener<BaseModel>,
private val binding: ItemArtistBinding
) : BaseViewHolder<BaseModel>(binding, listener) {
override fun onBind(model: BaseModel) {
binding.artist = model as Artist
binding.artistName.requestLayout()
}
companion object {
const val ITEM_TYPE = 11
fun from(context: Context, listener: ClickListener<BaseModel>): ArtistViewHolder {
return ArtistViewHolder(
ClickListener { listener.onClick(it) },
ItemArtistBinding.inflate(LayoutInflater.from(context))
)
}
}
}
class AlbumViewHolder private constructor(
listener: ClickListener<BaseModel>,
private val binding: ItemAlbumBinding
) : BaseViewHolder<BaseModel>(binding, listener) {
override fun onBind(model: BaseModel) {
binding.album = model as Album
binding.albumName.requestLayout()
}
companion object {
const val ITEM_TYPE = 12
fun from(context: Context, listener: ClickListener<BaseModel>): AlbumViewHolder {
return AlbumViewHolder(
listener,
ItemAlbumBinding.inflate(LayoutInflater.from(context))
)
}
}
}
class SongViewHolder private constructor(
listener: ClickListener<BaseModel>,
private val binding: ItemSongBinding
) : BaseViewHolder<BaseModel>(binding, listener) {
override fun onBind(model: BaseModel) {
binding.song = model as Song
binding.songName.requestLayout()
binding.songInfo.requestLayout()
}
companion object {
const val ITEM_TYPE = 13
fun from(context: Context, listener: ClickListener<BaseModel>): SongViewHolder {
return SongViewHolder(
listener,
ItemSongBinding.inflate(LayoutInflater.from(context))
)
}
}
}
class HeaderViewHolder(
private val binding: ItemHeaderBinding
) : BaseViewHolder<BaseModel>(binding, null) {
override fun onBind(model: BaseModel) {
binding.header = model as Header
}
companion object {
const val ITEM_TYPE = 14
fun from(context: Context): HeaderViewHolder {
return HeaderViewHolder(
ItemHeaderBinding.inflate(LayoutInflater.from(context))
)
}
}
}

View file

@ -5,8 +5,8 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.ItemSongBinding
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.recycler.BaseViewHolder
import org.oxycblt.auxio.recycler.ClickListener
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
class SongAdapter(
private val data: List<Song>,

View file

@ -1,6 +1,7 @@
package org.oxycblt.auxio.theme
// Preference Constants
// TODO: Move these to dedicated enum class.
const val SHOW_ARTISTS = 0
const val SHOW_ALBUMS = 1
const val SHOW_GENRES = 2

View file

@ -39,7 +39,7 @@
android:textColor="?android:attr/textColorPrimary"
android:ellipsize="end"
android:maxLines="1"
app:layout_constraintBottom_toTopOf="@+id/album_song_count"
app:layout_constraintBottom_toTopOf="@+id/album_info"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/album_cover"
app:layout_constraintTop_toTopOf="parent"
@ -47,14 +47,14 @@
tools:text="Album Name" />
<TextView
android:id="@+id/album_song_count"
android:id="@+id/album_info"
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"
android:maxLines="1"
app:albumSongCount="@{album}"
app:albumInfo="@{album}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/album_cover"
app:layout_constraintTop_toBottomOf="@+id/album_name"