Add sorting to AlbumDetailFragment

Add sorting to AlbumDetailFragment. Ignore the other commit.
This commit is contained in:
OxygenCobalt 2020-09-24 16:15:34 -06:00
parent 35e186d8a9
commit 35814c5b40
10 changed files with 137 additions and 55 deletions

View file

@ -21,8 +21,8 @@ import org.oxycblt.auxio.theme.toColor
class MainFragment : Fragment() {
private val shownFragments = listOf(0, 1)
private lateinit var libraryFragment: LibraryFragment
private lateinit var songsFragment: SongsFragment
private val libraryFragment: LibraryFragment by lazy { LibraryFragment() }
private val songsFragment: SongsFragment by lazy { SongsFragment() }
private val tabIcons = listOf(
R.drawable.ic_library,
@ -81,21 +81,10 @@ class MainFragment : Fragment() {
private fun fragmentAt(position: Int): Fragment {
return when (position) {
0 -> {
if (!::libraryFragment.isInitialized) {
libraryFragment = LibraryFragment()
}
0 -> libraryFragment
1 -> songsFragment
libraryFragment
}
else -> {
if (!::songsFragment.isInitialized) {
songsFragment = SongsFragment()
}
songsFragment
}
else -> libraryFragment
}
}

View file

@ -14,6 +14,7 @@ import org.oxycblt.auxio.databinding.FragmentAlbumDetailBinding
import org.oxycblt.auxio.detail.adapters.DetailSongAdapter
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.recycler.ClickListener
import org.oxycblt.auxio.recycler.SortMode
import org.oxycblt.auxio.theme.applyDivider
class AlbumDetailFragment : Fragment() {
@ -39,7 +40,6 @@ class AlbumDetailFragment : Fragment() {
}
val songAdapter = DetailSongAdapter(
detailModel.currentAlbum!!.songs,
ClickListener {
Log.d(this::class.simpleName, it.name)
}
@ -73,6 +73,23 @@ class AlbumDetailFragment : Fragment() {
binding.artistName.setBackgroundResource(R.drawable.ripple)
}
detailModel.albumSortMode.observe(viewLifecycleOwner) { mode ->
// Update the current sort icon
binding.sortButton.setImageResource(mode.iconRes)
// Then update the sort mode of the album adapter.
songAdapter.submitList(
detailModel.currentAlbum!!.songs.sortedWith(
SortMode.songSortComparators.getOrDefault(
mode,
// If any invalid value is given, just default to the normal sort order.
compareByDescending { it.track }
)
)
)
}
Log.d(this::class.simpleName, "Fragment created.")
return binding.root

View file

@ -14,6 +14,7 @@ import org.oxycblt.auxio.detail.adapters.DetailAlbumAdapter
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.models.Album
import org.oxycblt.auxio.recycler.ClickListener
import org.oxycblt.auxio.recycler.SortMode
import org.oxycblt.auxio.theme.applyDivider
class ArtistDetailFragment : Fragment() {
@ -38,19 +39,36 @@ class ArtistDetailFragment : Fragment() {
}
val albumAdapter = DetailAlbumAdapter(
detailModel.currentArtist!!.albums,
ClickListener {
navToAlbum(it)
}
)
binding.lifecycleOwner = this
binding.detailModel = detailModel
binding.artist = detailModel.currentArtist!!
binding.albumRecycler.adapter = albumAdapter
binding.albumRecycler.applyDivider()
binding.albumRecycler.setHasFixedSize(true)
detailModel.artistSortMode.observe(viewLifecycleOwner) { mode ->
// Update the current sort icon
binding.sortButton.setImageResource(mode.iconRes)
// Then update the sort mode of the album adapter.
albumAdapter.submitList(
detailModel.currentArtist!!.albums.sortedWith(
SortMode.albumSortComparators.getOrDefault(
mode,
// If any invalid value is given, just default to the normal sort order.
compareByDescending { it.year }
)
)
)
}
Log.d(this::class.simpleName, "Fragment created.")
return binding.root

View file

@ -5,6 +5,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import org.oxycblt.auxio.music.models.Album
import org.oxycblt.auxio.music.models.Artist
import org.oxycblt.auxio.recycler.SortMode
class DetailViewModel : ViewModel() {
var isAlreadyNavigating = false
@ -12,6 +13,12 @@ class DetailViewModel : ViewModel() {
private val mNavToParentArtist = MutableLiveData<Boolean>()
val navToParentArtist: LiveData<Boolean> get() = mNavToParentArtist
private val mArtistSortMode = MutableLiveData(SortMode.NUMERIC_DOWN)
val artistSortMode: LiveData<SortMode> get() = mArtistSortMode
private val mAlbumSortMode = MutableLiveData(SortMode.NUMERIC_DOWN)
val albumSortMode: LiveData<SortMode> get() = mAlbumSortMode
var currentArtist: Artist? = null
var currentAlbum: Album? = null
@ -22,4 +29,24 @@ class DetailViewModel : ViewModel() {
fun doneWithNavToParent() {
mNavToParentArtist.value = false
}
fun incrementArtistSortMode() {
mArtistSortMode.value = when (mArtistSortMode.value) {
SortMode.NUMERIC_DOWN -> SortMode.NUMERIC_UP
SortMode.NUMERIC_UP -> SortMode.ALPHA_DOWN
SortMode.ALPHA_DOWN -> SortMode.ALPHA_UP
SortMode.ALPHA_UP -> SortMode.NUMERIC_DOWN
else -> SortMode.NUMERIC_DOWN
}
}
fun incrementAlbumSortMode() {
mAlbumSortMode.value = when (mAlbumSortMode.value) {
SortMode.NUMERIC_DOWN -> SortMode.NUMERIC_UP
SortMode.NUMERIC_UP -> SortMode.NUMERIC_DOWN
else -> SortMode.NUMERIC_DOWN
}
}
}

View file

@ -2,17 +2,16 @@ package org.oxycblt.auxio.detail.adapters
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.music.models.Album
import org.oxycblt.auxio.recycler.AlbumDiffCallback
import org.oxycblt.auxio.recycler.ClickListener
class DetailAlbumAdapter(
private val data: List<Album>,
private val listener: ClickListener<Album>
) : RecyclerView.Adapter<DetailAlbumAdapter.ViewHolder>() {
override fun getItemCount(): Int = data.size
) : ListAdapter<Album, DetailAlbumAdapter.ViewHolder>(AlbumDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
@ -21,7 +20,7 @@ class DetailAlbumAdapter(
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(data[position])
holder.bind(getItem(position))
}
// Generic ViewHolder for an album

View file

@ -2,17 +2,16 @@ package org.oxycblt.auxio.detail.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.ItemAlbumSongBinding
import org.oxycblt.auxio.music.models.Song
import org.oxycblt.auxio.recycler.ClickListener
import org.oxycblt.auxio.recycler.SongDiffCallback
class DetailSongAdapter(
private val data: List<Song>,
private val listener: ClickListener<Song>
) : RecyclerView.Adapter<DetailSongAdapter.ViewHolder>() {
override fun getItemCount(): Int = data.size
) : ListAdapter<Song, DetailSongAdapter.ViewHolder>(SongDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
@ -21,7 +20,7 @@ class DetailSongAdapter(
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(data[position])
holder.bind(getItem(position))
}
// Generic ViewHolder for a song

View file

@ -31,10 +31,8 @@ class MusicSorter(
for (album in albums) {
// Find all songs that match the current album ID to prevent any bugs w/comparing names.
// Also sort them by their track number.
val albumSongs = songs.filter {
it.albumId == album.id
}.sortedBy { it.track }
// This cant be done with artists/genres sadly.
val albumSongs = songs.filter { it.albumId == album.id }
// Then add them to the album
for (song in albumSongs) {
@ -74,10 +72,8 @@ class MusicSorter(
val unknownAlbums = albums.toMutableList()
for (artist in artists) {
// Find all albums that match the current artist name, and then sort them by year
val artistAlbums = albums.filter {
it.artistName == artist.name
}.sortedByDescending { it.year }
// Find all albums that match the current artist name
val artistAlbums = albums.filter { it.artistName == artist.name }
// Then add them to the artist, along with refreshing the amount of albums
for (album in artistAlbums) {

View file

@ -8,18 +8,7 @@ import org.oxycblt.auxio.music.models.Song
// RecyclerView click listener
class ClickListener<T>(val onClick: (T) -> Unit)
// Diff callback for albums
class AlbumDiffCallback : DiffUtil.ItemCallback<Album>() {
override fun areContentsTheSame(oldItem: Album, newItem: Album): Boolean {
return oldItem.id == newItem.id
}
override fun areItemsTheSame(oldItem: Album, newItem: Album): Boolean {
return oldItem == newItem
}
}
// Diff callback for songs
// Song Diff callback
class SongDiffCallback : DiffUtil.ItemCallback<Song>() {
override fun areContentsTheSame(oldItem: Song, newItem: Song): Boolean {
return oldItem.id == newItem.id
@ -30,6 +19,17 @@ class SongDiffCallback : DiffUtil.ItemCallback<Song>() {
}
}
// Album Diff callback
class AlbumDiffCallback : DiffUtil.ItemCallback<Album>() {
override fun areContentsTheSame(oldItem: Album, newItem: Album): Boolean {
return oldItem.id == newItem.id
}
override fun areItemsTheSame(oldItem: Album, newItem: Album): Boolean {
return oldItem == newItem
}
}
// Sorting modes
enum class SortMode(val iconRes: Int) {
// Icons for each mode are assigned to the enums themselves
@ -40,8 +40,12 @@ enum class SortMode(val iconRes: Int) {
NUMERIC_DOWN(R.drawable.ic_sort_numeric_down);
companion object {
// Sort comparators are different for each music model, so they are
// static maps instead.
// Sort comparators are different for each music model, so they are static maps instead.
val songSortComparators = mapOf<SortMode, Comparator<Song>>(
NUMERIC_DOWN to compareBy { it.track },
NUMERIC_UP to compareByDescending { it.track }
)
val albumSortComparators = mapOf<SortMode, Comparator<Album>>(
NUMERIC_DOWN to compareByDescending { it.year },
NUMERIC_UP to compareBy { it.year },
@ -54,10 +58,5 @@ enum class SortMode(val iconRes: Int) {
String.CASE_INSENSITIVE_ORDER
) { it.name },
)
val songSortComparators = mapOf<SortMode, Comparator<Song>>(
NUMERIC_DOWN to compareBy { it.track },
NUMERIC_UP to compareByDescending { it.track }
)
}
}

View file

@ -112,6 +112,23 @@
android:textSize="16sp"
app:layout_constraintTop_toBottomOf="@+id/album_year" />
<ImageButton
android:id="@+id/sort_button"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginTop="@dimen/margin_medium"
android:background="@drawable/header_dividers"
android:contentDescription="@string/description_sort_button"
android:paddingStart="@dimen/padding_medium"
android:paddingTop="@dimen/padding_small"
android:paddingEnd="@dimen/margin_medium"
android:paddingBottom="@dimen/padding_small"
android:onClick="@{() -> detailModel.incrementAlbumSortMode()}"
tools:src="@drawable/ic_sort_numeric_down"
app:layout_constraintBottom_toTopOf="@+id/song_recycler"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_year" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/song_recycler"
android:layout_width="match_parent"

View file

@ -8,6 +8,10 @@
<variable
name="artist"
type="org.oxycblt.auxio.music.models.Artist" />
<variable
name="detailModel"
type="org.oxycblt.auxio.detail.DetailViewModel" />
</data>
<LinearLayout
@ -106,6 +110,23 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_counts" />
<ImageButton
android:id="@+id/sort_button"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginTop="@dimen/margin_medium"
android:background="@drawable/header_dividers"
android:contentDescription="@string/description_sort_button"
android:paddingStart="@dimen/padding_medium"
android:paddingTop="@dimen/padding_small"
android:paddingEnd="@dimen/margin_medium"
android:paddingBottom="@dimen/padding_small"
android:onClick="@{() -> detailModel.incrementArtistSortMode()}"
tools:src="@drawable/ic_sort_numeric_down"
app:layout_constraintBottom_toTopOf="@+id/album_recycler"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_counts" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/album_recycler"
android:layout_width="match_parent"