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() { class MainFragment : Fragment() {
private val shownFragments = listOf(0, 1) private val shownFragments = listOf(0, 1)
private lateinit var libraryFragment: LibraryFragment private val libraryFragment: LibraryFragment by lazy { LibraryFragment() }
private lateinit var songsFragment: SongsFragment private val songsFragment: SongsFragment by lazy { SongsFragment() }
private val tabIcons = listOf( private val tabIcons = listOf(
R.drawable.ic_library, R.drawable.ic_library,
@ -81,21 +81,10 @@ class MainFragment : Fragment() {
private fun fragmentAt(position: Int): Fragment { private fun fragmentAt(position: Int): Fragment {
return when (position) { return when (position) {
0 -> { 0 -> libraryFragment
if (!::libraryFragment.isInitialized) { 1 -> songsFragment
libraryFragment = LibraryFragment()
}
libraryFragment else -> libraryFragment
}
else -> {
if (!::songsFragment.isInitialized) {
songsFragment = SongsFragment()
}
songsFragment
}
} }
} }

View file

@ -14,6 +14,7 @@ import org.oxycblt.auxio.databinding.FragmentAlbumDetailBinding
import org.oxycblt.auxio.detail.adapters.DetailSongAdapter import org.oxycblt.auxio.detail.adapters.DetailSongAdapter
import org.oxycblt.auxio.music.MusicViewModel import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.recycler.ClickListener import org.oxycblt.auxio.recycler.ClickListener
import org.oxycblt.auxio.recycler.SortMode
import org.oxycblt.auxio.theme.applyDivider import org.oxycblt.auxio.theme.applyDivider
class AlbumDetailFragment : Fragment() { class AlbumDetailFragment : Fragment() {
@ -39,7 +40,6 @@ class AlbumDetailFragment : Fragment() {
} }
val songAdapter = DetailSongAdapter( val songAdapter = DetailSongAdapter(
detailModel.currentAlbum!!.songs,
ClickListener { ClickListener {
Log.d(this::class.simpleName, it.name) Log.d(this::class.simpleName, it.name)
} }
@ -73,6 +73,23 @@ class AlbumDetailFragment : Fragment() {
binding.artistName.setBackgroundResource(R.drawable.ripple) 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.") Log.d(this::class.simpleName, "Fragment created.")
return binding.root 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.MusicViewModel
import org.oxycblt.auxio.music.models.Album import org.oxycblt.auxio.music.models.Album
import org.oxycblt.auxio.recycler.ClickListener import org.oxycblt.auxio.recycler.ClickListener
import org.oxycblt.auxio.recycler.SortMode
import org.oxycblt.auxio.theme.applyDivider import org.oxycblt.auxio.theme.applyDivider
class ArtistDetailFragment : Fragment() { class ArtistDetailFragment : Fragment() {
@ -38,19 +39,36 @@ class ArtistDetailFragment : Fragment() {
} }
val albumAdapter = DetailAlbumAdapter( val albumAdapter = DetailAlbumAdapter(
detailModel.currentArtist!!.albums,
ClickListener { ClickListener {
navToAlbum(it) navToAlbum(it)
} }
) )
binding.lifecycleOwner = this binding.lifecycleOwner = this
binding.detailModel = detailModel
binding.artist = detailModel.currentArtist!! binding.artist = detailModel.currentArtist!!
binding.albumRecycler.adapter = albumAdapter binding.albumRecycler.adapter = albumAdapter
binding.albumRecycler.applyDivider() binding.albumRecycler.applyDivider()
binding.albumRecycler.setHasFixedSize(true) 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.") Log.d(this::class.simpleName, "Fragment created.")
return binding.root return binding.root

View file

@ -5,6 +5,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
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.Artist
import org.oxycblt.auxio.recycler.SortMode
class DetailViewModel : ViewModel() { class DetailViewModel : ViewModel() {
var isAlreadyNavigating = false var isAlreadyNavigating = false
@ -12,6 +13,12 @@ class DetailViewModel : ViewModel() {
private val mNavToParentArtist = MutableLiveData<Boolean>() private val mNavToParentArtist = MutableLiveData<Boolean>()
val navToParentArtist: LiveData<Boolean> get() = mNavToParentArtist 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 currentArtist: Artist? = null
var currentAlbum: Album? = null var currentAlbum: Album? = null
@ -22,4 +29,24 @@ class DetailViewModel : ViewModel() {
fun doneWithNavToParent() { fun doneWithNavToParent() {
mNavToParentArtist.value = false 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.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.ItemAlbumBinding import org.oxycblt.auxio.databinding.ItemAlbumBinding
import org.oxycblt.auxio.music.models.Album import org.oxycblt.auxio.music.models.Album
import org.oxycblt.auxio.recycler.AlbumDiffCallback
import org.oxycblt.auxio.recycler.ClickListener import org.oxycblt.auxio.recycler.ClickListener
class DetailAlbumAdapter( class DetailAlbumAdapter(
private val data: List<Album>,
private val listener: ClickListener<Album> private val listener: ClickListener<Album>
) : RecyclerView.Adapter<DetailAlbumAdapter.ViewHolder>() { ) : ListAdapter<Album, DetailAlbumAdapter.ViewHolder>(AlbumDiffCallback()) {
override fun getItemCount(): Int = data.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder( return ViewHolder(
@ -21,7 +20,7 @@ class DetailAlbumAdapter(
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(data[position]) holder.bind(getItem(position))
} }
// Generic ViewHolder for an album // Generic ViewHolder for an album

View file

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

View file

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

View file

@ -8,18 +8,7 @@ import org.oxycblt.auxio.music.models.Song
// RecyclerView click listener // RecyclerView click listener
class ClickListener<T>(val onClick: (T) -> Unit) class ClickListener<T>(val onClick: (T) -> Unit)
// Diff callback for albums // Song 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
}
}
// Diff callback for songs
class SongDiffCallback : DiffUtil.ItemCallback<Song>() { class SongDiffCallback : DiffUtil.ItemCallback<Song>() {
override fun areContentsTheSame(oldItem: Song, newItem: Song): Boolean { override fun areContentsTheSame(oldItem: Song, newItem: Song): Boolean {
return oldItem.id == newItem.id 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 // Sorting modes
enum class SortMode(val iconRes: Int) { enum class SortMode(val iconRes: Int) {
// Icons for each mode are assigned to the enums themselves // 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); NUMERIC_DOWN(R.drawable.ic_sort_numeric_down);
companion object { companion object {
// Sort comparators are different for each music model, so they are // Sort comparators are different for each music model, so they are static maps instead.
// 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>>( val albumSortComparators = mapOf<SortMode, Comparator<Album>>(
NUMERIC_DOWN to compareByDescending { it.year }, NUMERIC_DOWN to compareByDescending { it.year },
NUMERIC_UP to compareBy { it.year }, NUMERIC_UP to compareBy { it.year },
@ -54,10 +58,5 @@ enum class SortMode(val iconRes: Int) {
String.CASE_INSENSITIVE_ORDER String.CASE_INSENSITIVE_ORDER
) { it.name }, ) { 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" android:textSize="16sp"
app:layout_constraintTop_toBottomOf="@+id/album_year" /> 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 <androidx.recyclerview.widget.RecyclerView
android:id="@+id/song_recycler" android:id="@+id/song_recycler"
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -8,6 +8,10 @@
<variable <variable
name="artist" name="artist"
type="org.oxycblt.auxio.music.models.Artist" /> type="org.oxycblt.auxio.music.models.Artist" />
<variable
name="detailModel"
type="org.oxycblt.auxio.detail.DetailViewModel" />
</data> </data>
<LinearLayout <LinearLayout
@ -106,6 +110,23 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_counts" /> 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 <androidx.recyclerview.widget.RecyclerView
android:id="@+id/album_recycler" android:id="@+id/album_recycler"
android:layout_width="match_parent" android:layout_width="match_parent"