Add sorting

Add Alphabetical/Numeric sorting to ArtistDetailFragment.
This commit is contained in:
OxygenCobalt 2020-09-23 20:32:42 -06:00
parent b80a3596b8
commit 414e85153f
21 changed files with 198 additions and 31 deletions

View file

@ -1,4 +0,0 @@
package org.oxycblt.auxio
// RecyclerView click listener
class ClickListener<T>(val onClick: (T) -> Unit)

View file

@ -9,7 +9,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import org.oxycblt.auxio.ClickListener
import org.oxycblt.auxio.reycler.ClickListener
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentAlbumDetailBinding
import org.oxycblt.auxio.detail.adapters.DetailSongAdapter
@ -75,4 +75,10 @@ class AlbumDetailFragment : Fragment() {
return binding.root
}
override fun onDestroy() {
super.onDestroy()
detailModel.currentAlbum = null
}
}

View file

@ -9,12 +9,14 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import org.oxycblt.auxio.ClickListener
import org.oxycblt.auxio.reycler.ClickListener
import org.oxycblt.auxio.databinding.FragmentArtistDetailBinding
import org.oxycblt.auxio.detail.adapters.DetailAlbumAdapter
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.models.Album
import org.oxycblt.auxio.reycler.SortMode
import org.oxycblt.auxio.theme.applyDivider
import java.util.Comparator
class ArtistDetailFragment : Fragment() {
@ -37,20 +39,37 @@ class ArtistDetailFragment : Fragment() {
}!!
}
val artistAdapter = DetailAlbumAdapter(
detailModel.currentArtist!!.albums,
val albumAdapter = DetailAlbumAdapter(
ClickListener {
navToAlbum(it)
}
)
binding.lifecycleOwner = this
binding.detailModel = detailModel
binding.artist = detailModel.currentArtist!!
binding.albumRecycler.adapter = artistAdapter
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

@ -1,10 +1,12 @@
package org.oxycblt.auxio.detail
import android.util.Log
import androidx.lifecycle.LiveData
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.reycler.SortMode
class DetailViewModel : ViewModel() {
var isAlreadyNavigating = false
@ -12,6 +14,9 @@ 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
var currentArtist: Artist? = null
var currentAlbum: Album? = null
@ -22,4 +27,15 @@ 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
}
}
}

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.ClickListener
import org.oxycblt.auxio.reycler.ClickListener
import org.oxycblt.auxio.databinding.ItemAlbumBinding
import org.oxycblt.auxio.music.models.Album
import org.oxycblt.auxio.reycler.DiffCallback
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>(DiffCallback()){
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

@ -3,7 +3,7 @@ package org.oxycblt.auxio.detail.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.ClickListener
import org.oxycblt.auxio.reycler.ClickListener
import org.oxycblt.auxio.databinding.ItemAlbumSongBinding
import org.oxycblt.auxio.music.models.Song

View file

@ -8,7 +8,7 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import org.oxycblt.auxio.ClickListener
import org.oxycblt.auxio.reycler.ClickListener
import org.oxycblt.auxio.MainFragmentDirections
import org.oxycblt.auxio.databinding.FragmentLibraryBinding
import org.oxycblt.auxio.library.adapters.ArtistAdapter

View file

@ -3,7 +3,7 @@ package org.oxycblt.auxio.library.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.ClickListener
import org.oxycblt.auxio.reycler.ClickListener
import org.oxycblt.auxio.databinding.ItemAlbumBinding
import org.oxycblt.auxio.music.models.Album

View file

@ -3,7 +3,7 @@ package org.oxycblt.auxio.library.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.ClickListener
import org.oxycblt.auxio.reycler.ClickListener
import org.oxycblt.auxio.databinding.ItemArtistBinding
import org.oxycblt.auxio.music.models.Artist

View file

@ -86,6 +86,7 @@ fun TextView.bindArtistCounts(artist: Artist) {
text = context.getString(R.string.format_double_counts, albums, songs)
}
// Get the artist genre.
// TODO: Stub, add option to list all genres instead of just the most prominent
@BindingAdapter("artistGenre")

View file

@ -21,6 +21,8 @@ import org.oxycblt.auxio.music.processing.MusicLoaderResponse
import org.oxycblt.auxio.music.processing.MusicSorter
// ViewModel for music storage. May also be a god object.
// TODO: Move the lists from the music models to a map, would
// make some systems a lot less hacky and maybe decrease memory usage.
class MusicViewModel(private val app: Application) : ViewModel() {
// Coroutine

View file

@ -61,6 +61,8 @@ class ArtistImageFetcher(private val context: Context) : Fetcher<List<Uri>> {
var y = 0
val increment = MOSAIC_BITMAP_SIZE / 2
// For each stream, create a bitmap scaled to 1/4th of the mosaics combined size
// and place it on a corner of the canvas.
for (stream in streams) {
val bitmap = Bitmap.createScaledBitmap(
BitmapFactory.decodeStream(stream),

View file

@ -26,7 +26,6 @@ class MusicLoader(
private val artistPlaceholder: String,
private val albumPlaceholder: String,
) {
var genres = mutableListOf<Genre>()
var artists = mutableListOf<Artist>()
var albums = mutableListOf<Album>()
@ -111,14 +110,15 @@ class MusicLoader(
private fun loadArtists() {
Log.d(this::class.simpleName, "Starting artist search...")
// To associate artists with their genres, a new cursor is
// created with all the artists of that type.
// Iterate through the artists for each loaded genre, and then add the genre
// with the artist.
// This is only done because using GENRE_NAME for songs is broken and has been for years.
for (genre in genres) {
artistCursor = resolver.query(
Genres.Members.getContentUri("external", genre.id),
arrayOf(
Artists._ID, // 0
Artists.ARTIST // 1
Artists.ARTIST, // 1
),
null, null,
Artists.DEFAULT_SORT_ORDER
@ -227,7 +227,7 @@ class MusicLoader(
Media.TITLE, // 2
Media.ALBUM_ID, // 3
Media.TRACK, // 4
Media.DURATION // 5
Media.DURATION, // 5
),
Media.IS_MUSIC + "=1", null,
Media.DEFAULT_SORT_ORDER

View file

@ -0,0 +1,47 @@
package org.oxycblt.auxio.reycler
import androidx.recyclerview.widget.DiffUtil
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.models.Album
import org.oxycblt.auxio.music.models.Artist
// RecyclerView click listener
class ClickListener<T>(val onClick: (T) -> Unit)
// Diff callback
class DiffCallback : 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
NONE(R.drawable.ic_sort_alpha_down),
ALPHA_UP(R.drawable.ic_sort_alpha_up),
ALPHA_DOWN(R.drawable.ic_sort_alpha_down),
NUMERIC_UP(R.drawable.ic_sort_numeric_up),
NUMERIC_DOWN(R.drawable.ic_sort_numeric_down);
companion object {
// Sort comparators are different for each music model, so they are
// static maps instead.
val albumSortComparators = mapOf<SortMode, Comparator<Album>>(
NUMERIC_DOWN to compareBy { it.year },
NUMERIC_UP to compareByDescending { it.year },
// Alphabetic sorting needs to be case-insensitive
ALPHA_DOWN to compareByDescending(
String.CASE_INSENSITIVE_ORDER
) { it.name },
ALPHA_UP to compareBy(
String.CASE_INSENSITIVE_ORDER
) { it.name },
)
}
}

View file

@ -3,7 +3,7 @@ package org.oxycblt.auxio.songs
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.ClickListener
import org.oxycblt.auxio.reycler.ClickListener
import org.oxycblt.auxio.databinding.ItemSongBinding
import org.oxycblt.auxio.music.models.Song

View file

@ -7,7 +7,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import org.oxycblt.auxio.ClickListener
import org.oxycblt.auxio.reycler.ClickListener
import org.oxycblt.auxio.databinding.FragmentSongsBinding
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.theme.applyDivider

View file

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?android:attr/colorPrimary">
<path
android:fillColor="#FF000000"
android:pathData="m3.6944,17.9917h-2.7188L5.1182,5.9917L8.3877,5.9917L12.5244,17.9917h-2.7188L6.7998,8.7339L6.7061,8.7339ZM3.5244,13.2749h6.4219v1.9805L3.5244,15.2554Z"
android:strokeWidth="0.4125"/>
<path
android:fillColor="#FF000000"
android:pathData="m13.8369,17.9917v-1.5059l5.9883,-8.4023h-6L13.8252,5.9917h9.1875L23.0127,7.4975L17.0185,15.8999h6.0059v2.0918z"
android:strokeWidth="0.4125"/>
<path
android:pathData="m15.5,20.5355c-2.3333,0 -4.6667,0 -7,0 1.1667,1.152 2.3333,2.304 3.5,3.456 1.1667,-1.152 2.3333,-2.304 3.5,-3.456z"
android:strokeWidth="0.139935"
android:fillColor="#000000"/>
</vector>

View file

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?android:attr/colorPrimary">
<path
android:fillColor="#FF000000"
android:pathData="m3.6944,17.9917h-2.7188L5.1182,5.9917L8.3877,5.9917L12.5244,17.9917h-2.7188L6.7998,8.7339L6.7061,8.7339ZM3.5244,13.2749h6.4219v1.9805L3.5244,15.2554Z"
android:strokeWidth="0.4125"/>
<path
android:fillColor="#FF000000"
android:pathData="m13.8369,17.9917v-1.5059l5.9883,-8.4023h-6L13.8252,5.9917h9.1875L23.0127,7.4975L17.0185,15.8999h6.0059v2.0918z"
android:strokeWidth="0.4125"/>
<path
android:pathData="m15.5,3.4478c-2.3333,0 -4.6667,0 -7,0 1.1667,-1.152 2.3333,-2.304 3.5,-3.456 1.1667,1.152 2.3333,2.304 3.5,3.456z"
android:strokeWidth="0.139935"
android:fillColor="#000000"/>
</vector>

View file

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?android:attr/colorPrimary">
<path
android:pathData="m15.5,20.544c-2.3333,0 -4.6667,0 -7,0 1.1667,1.152 2.3333,2.304 3.5,3.456 1.1667,-1.152 2.3333,-2.304 3.5,-3.456z"
android:strokeWidth="0.139935"
android:fillColor="#000000"/>
<path
android:fillColor="#FF000000"
android:pathData="M6.5424,18.0001Q5.034,17.9945 3.9466,17.2836 2.865,16.5728 2.2804,15.2244 1.7016,13.876 1.7074,11.9804q0,-1.89 0.5788,-3.2214 0.5846,-1.3314 1.6662,-2.0254 1.0874,-0.6996 2.59,-0.6996 1.5025,0 2.5841,0.6996 1.0874,0.6996 1.6721,2.031 0.5846,1.3258 0.5788,3.2158 0,1.9013 -0.5846,3.2496 -0.5788,1.3484 -1.6604,2.0592 -1.0816,0.7109 -2.59,0.7109zM6.5424,15.9748q1.029,0 1.6428,-0.9986 0.6139,-0.9986 0.608,-2.9957 0,-1.3145 -0.2806,-2.189 -0.2748,-0.8745 -0.7834,-1.3145 -0.5028,-0.4401 -1.1868,-0.4401 -1.0231,0 -1.637,0.9873 -0.6139,0.9873 -0.6197,2.9563 0,1.3314 0.2748,2.2228 0.2806,0.8857 0.7893,1.3314 0.5086,0.4401 1.1927,0.4401z"
android:strokeWidth="0.404319"/>
<path
android:fillColor="#FF000000"
android:pathData="m17.5102,6.0002q0.9237,0 1.7773,0.299 0.8594,0.2934 1.5318,0.9534 0.6782,0.6601 1.0699,1.7489 0.3976,1.0832 0.4034,2.6685 -0.0058,1.4725 -0.3508,2.629 -0.3391,1.1509 -0.9763,1.9577 -0.6373,0.8068 -1.5376,1.2299 -0.8945,0.4175 -2.0053,0.4175 -1.1985,0 -2.1164,-0.4457 -0.9179,-0.4513 -1.4791,-1.2243 -0.5554,-0.7786 -0.6723,-1.7489h2.4964q0.1462,0.6319 0.6139,0.9817 0.4677,0.3441 1.1576,0.3441 1.1693,0 1.7773,-0.9817 0.608,-0.9873 0.6139,-2.7024h-0.0818q-0.2689,0.5078 -0.725,0.8745 -0.456,0.3611 -1.0465,0.5585 -0.5846,0.1975 -1.2453,0.1975 -1.0582,0 -1.8942,-0.4795 -0.8302,-0.4795 -1.3096,-1.3202 -0.4794,-0.8463 -0.4736,-1.9295 -0.0058,-1.1735 0.5554,-2.0818 0.5613,-0.914 1.5668,-1.433 1.0114,-0.519 2.3503,-0.5134zM17.5278,7.9184q-0.5905,0 -1.0524,0.2708 -0.456,0.2708 -0.725,0.7334 -0.2631,0.4626 -0.2572,1.0381 -0.0058,0.5755 0.2514,1.0324 0.2631,0.457 0.7191,0.7278 0.456,0.2652 1.0407,0.2652 0.4385,0 0.8068,-0.158 0.3742,-0.158 0.6489,-0.4344 0.2806,-0.2821 0.4385,-0.6488 0.1579,-0.3724 0.1637,-0.7955 -0.0058,-0.5585 -0.2689,-1.0211 -0.2631,-0.4626 -0.725,-0.7334 -0.4619,-0.2764 -1.0407,-0.2764z"
android:strokeWidth="0.404319"/>
</vector>

View file

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?android:attr/colorPrimary">
<path
android:pathData="m15.5,3.4563c-2.3333,0 -4.6667,0 -7,0 1.1667,-1.152 2.3333,-2.304 3.5,-3.456 1.1667,1.152 2.3333,2.304 3.5,3.456z"
android:strokeWidth="0.139935"
android:fillColor="#000000"/>
<path
android:fillColor="#FF000000"
android:pathData="M6.5424,18.0001Q5.034,17.9945 3.9466,17.2836 2.865,16.5728 2.2804,15.2244 1.7016,13.876 1.7074,11.9804q0,-1.89 0.5788,-3.2214 0.5846,-1.3314 1.6662,-2.0254 1.0874,-0.6996 2.59,-0.6996 1.5025,0 2.5841,0.6996 1.0874,0.6996 1.6721,2.031 0.5846,1.3258 0.5788,3.2158 0,1.9013 -0.5846,3.2496 -0.5788,1.3484 -1.6604,2.0592 -1.0816,0.7109 -2.59,0.7109zM6.5424,15.9748q1.029,0 1.6428,-0.9986 0.6139,-0.9986 0.608,-2.9957 0,-1.3145 -0.2806,-2.189 -0.2748,-0.8745 -0.7834,-1.3145 -0.5028,-0.4401 -1.1868,-0.4401 -1.0231,0 -1.637,0.9873 -0.6139,0.9873 -0.6197,2.9563 0,1.3314 0.2748,2.2228 0.2806,0.8857 0.7893,1.3314 0.5086,0.4401 1.1927,0.4401z"
android:strokeWidth="0.404319"/>
<path
android:fillColor="#FF000000"
android:pathData="m17.5102,6.0002q0.9237,0 1.7773,0.299 0.8594,0.2934 1.5318,0.9534 0.6782,0.6601 1.0699,1.7489 0.3976,1.0832 0.4034,2.6685 -0.0058,1.4725 -0.3508,2.629 -0.3391,1.1509 -0.9763,1.9577 -0.6373,0.8068 -1.5376,1.2299 -0.8945,0.4175 -2.0053,0.4175 -1.1985,0 -2.1164,-0.4457 -0.9179,-0.4513 -1.4791,-1.2243 -0.5554,-0.7786 -0.6723,-1.7489h2.4964q0.1462,0.6319 0.6139,0.9817 0.4677,0.3441 1.1576,0.3441 1.1693,0 1.7773,-0.9817 0.608,-0.9873 0.6139,-2.7024h-0.0818q-0.2689,0.5078 -0.725,0.8745 -0.456,0.3611 -1.0465,0.5585 -0.5846,0.1975 -1.2453,0.1975 -1.0582,0 -1.8942,-0.4795 -0.8302,-0.4795 -1.3096,-1.3202 -0.4794,-0.8463 -0.4736,-1.9295 -0.0058,-1.1735 0.5554,-2.0818 0.5613,-0.914 1.5668,-1.433 1.0114,-0.519 2.3503,-0.5134zM17.5278,7.9184q-0.5905,0 -1.0524,0.2708 -0.456,0.2708 -0.725,0.7334 -0.2631,0.4626 -0.2572,1.0381 -0.0058,0.5755 0.2514,1.0324 0.2631,0.457 0.7191,0.7278 0.456,0.2652 1.0407,0.2652 0.4385,0 0.8068,-0.158 0.3742,-0.158 0.6489,-0.4344 0.2806,-0.2821 0.4385,-0.6488 0.1579,-0.3724 0.1637,-0.7955 -0.0058,-0.5585 -0.2689,-1.0211 -0.2631,-0.4626 -0.725,-0.7334 -0.4619,-0.2764 -1.0407,-0.2764z"
android:strokeWidth="0.404319"/>
</vector>

View file

@ -4,10 +4,13 @@
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="artist"
type="org.oxycblt.auxio.music.models.Artist" />
<variable
name="detailModel"
type="org.oxycblt.auxio.detail.DetailViewModel" />
</data>
<LinearLayout
@ -92,7 +95,7 @@
<TextView
android:id="@+id/header_title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="0dp"
android:layout_marginTop="@dimen/margin_medium"
android:background="@drawable/header_dividers"
android:fontFamily="@font/inter_bold"
@ -106,7 +109,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_counts" />
<!--
<ImageButton
android:id="@+id/sort_button"
android:layout_width="wrap_content"
@ -118,10 +120,11 @@
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"