Merge branch 'feature/129-favourite-sorting' into 'develop'

Sort Favourites by time

Closes #129

See merge request funkwhale/funkwhale-android!299
This commit is contained in:
Ryan Harg 2023-01-13 13:11:06 +00:00
commit ca63e0d60c
7 changed files with 70 additions and 47 deletions

View File

@ -8,11 +8,13 @@ import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.widget.PopupMenu
import androidx.recyclerview.widget.RecyclerView
import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.databinding.RowTrackBinding
import audio.funkwhale.ffa.fragments.FFAAdapter
import audio.funkwhale.ffa.model.Favorite
import audio.funkwhale.ffa.model.Track
import audio.funkwhale.ffa.utils.Command
import audio.funkwhale.ffa.utils.CommandBus
@ -27,7 +29,7 @@ class FavoritesAdapter(
private val context: Context?,
private val favoriteListener: FavoriteListener,
val fromQueue: Boolean = false,
) : FFAAdapter<Track, FavoritesAdapter.ViewHolder>() {
) : FFAAdapter<Favorite, FavoritesAdapter.ViewHolder>() {
init {
this.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
@ -47,7 +49,7 @@ class FavoritesAdapter(
override fun applyFilter() {
data.clear()
getUnfilteredData().map {
if (it.matchesFilter(filter)) {
if (it.track.matchesFilter(filter)) {
data.add(it)
}
}
@ -65,45 +67,43 @@ class FavoritesAdapter(
@SuppressLint("NewApi")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val favorite = data[position]
val track = favorite.track
CoverArt.withContext(layoutInflater.context, maybeNormalizeUrl(favorite.cover()))
CoverArt.withContext(layoutInflater.context, maybeNormalizeUrl(track.cover()))
.fit()
.placeholder(R.drawable.cover)
.transform(RoundedCornersTransformation(16, 0))
.into(holder.cover)
holder.title.text = favorite.title
holder.artist.text = favorite.artist.name
holder.title.text = track.title
holder.artist.text = track.artist.name
context?.let {
holder.itemView.background = context.getDrawable(R.drawable.ripple)
holder.itemView.background = AppCompatResources.getDrawable(context, R.drawable.ripple)
}
if (favorite.id == currentTrack?.id) {
if (track.id == currentTrack?.id) {
context?.let {
holder.itemView.background = context.getDrawable(R.drawable.current)
holder.itemView.background = AppCompatResources.getDrawable(context, R.drawable.current)
}
}
context?.let {
when (favorite.favorite) {
true -> holder.favorite.setColorFilter(context.getColor(R.color.colorFavorite))
false -> holder.favorite.setColorFilter(context.getColor(R.color.colorSelected))
}
holder.favorite.setColorFilter(context.getColor(R.color.colorFavorite))
when (favorite.cached || favorite.downloaded) {
when (track.cached || track.downloaded) {
true -> holder.title.setCompoundDrawablesWithIntrinsicBounds(R.drawable.downloaded, 0, 0, 0)
false -> holder.title.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0)
}
if (favorite.cached && !favorite.downloaded) {
if (track.cached && !track.downloaded) {
holder.title.compoundDrawables.forEach {
it?.colorFilter =
PorterDuffColorFilter(context.getColor(R.color.cached), PorterDuff.Mode.SRC_IN)
}
}
if (favorite.downloaded) {
if (track.downloaded) {
holder.title.compoundDrawables.forEach {
it?.colorFilter =
PorterDuffColorFilter(context.getColor(R.color.downloaded), PorterDuff.Mode.SRC_IN)
@ -111,8 +111,7 @@ class FavoritesAdapter(
}
holder.favorite.setOnClickListener {
favoriteListener.onToggleFavorite(favorite.id, !favorite.favorite)
favoriteListener.onToggleFavorite(track.id, !track.favorite)
data.remove(favorite)
notifyItemRemoved(holder.bindingAdapterPosition)
}
@ -125,10 +124,10 @@ class FavoritesAdapter(
setOnMenuItemClickListener {
when (it.itemId) {
R.id.track_add_to_queue -> CommandBus.send(Command.AddToQueue(listOf(favorite)))
R.id.track_play_next -> CommandBus.send(Command.PlayNext(favorite))
R.id.track_pin -> CommandBus.send(Command.PinTrack(favorite))
R.id.queue_remove -> CommandBus.send(Command.RemoveFromQueue(favorite))
R.id.track_add_to_queue -> CommandBus.send(Command.AddToQueue(listOf(track)))
R.id.track_play_next -> CommandBus.send(Command.PlayNext(track))
R.id.track_pin -> CommandBus.send(Command.PinTrack(track))
R.id.queue_remove -> CommandBus.send(Command.RemoveFromQueue(track))
}
true
@ -169,10 +168,13 @@ class FavoritesAdapter(
when (fromQueue) {
true -> CommandBus.send(Command.PlayTrack(layoutPosition))
false -> {
data.subList(layoutPosition, data.size).plus(data.subList(0, layoutPosition)).apply {
CommandBus.send(Command.ReplaceQueue(this))
context.toast("All tracks were added to your queue")
}
data
.subList(layoutPosition, data.size).plus(data.subList(0, layoutPosition))
.map { it.track }
.apply {
CommandBus.send(Command.ReplaceQueue(this))
context.toast("All tracks were added to your queue")
}
}
}
}

View File

@ -46,7 +46,7 @@ abstract class FFAAdapter<D, VH : RecyclerView.ViewHolder> : RecyclerView.Adapte
abstract override fun getItemId(position: Int): Long
}
abstract class FFAFragment<D : Any, A : FFAAdapter<D, *>>() : Fragment() {
abstract class FFAFragment<D : Any, A : FFAAdapter<D, *>> : Fragment() {
companion object {
const val OFFSCREEN_PAGES = 20
}

View File

@ -11,6 +11,7 @@ import androidx.recyclerview.widget.RecyclerView
import audio.funkwhale.ffa.adapters.FavoriteListener
import audio.funkwhale.ffa.adapters.FavoritesAdapter
import audio.funkwhale.ffa.databinding.FragmentFavoritesBinding
import audio.funkwhale.ffa.model.Favorite
import audio.funkwhale.ffa.model.Track
import audio.funkwhale.ffa.repositories.FavoritesRepository
import audio.funkwhale.ffa.repositories.TracksRepository
@ -31,7 +32,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.java.KoinJavaComponent.inject
class FavoritesFragment : FFAFragment<Track, FavoritesAdapter>() {
class FavoritesFragment : FFAFragment<Favorite, FavoritesAdapter>() {
private val exoDownloadManager: DownloadManager by inject(DownloadManager::class.java)
@ -63,7 +64,7 @@ class FavoritesFragment : FFAFragment<Track, FavoritesAdapter>() {
adapter.notifyDataSetChanged()
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { }
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
adapter.filter = s.toString()
@ -93,7 +94,7 @@ class FavoritesFragment : FFAFragment<Track, FavoritesAdapter>() {
}
binding.play.setOnClickListener {
CommandBus.send(Command.ReplaceQueue(adapter.data.shuffled()))
CommandBus.send(Command.ReplaceQueue(adapter.data.map { it.track }.shuffled()))
}
}
@ -122,7 +123,7 @@ class FavoritesFragment : FFAFragment<Track, FavoritesAdapter>() {
withContext(Main) {
val data = adapter.data.map {
it.downloaded = downloaded.contains(it.id)
it.track.downloaded = downloaded.contains(it.id)
it
}.toMutableList()
@ -138,7 +139,7 @@ class FavoritesFragment : FFAFragment<Track, FavoritesAdapter>() {
adapter.data.withIndex().associate { it.value to it.index }.filter { it.key.id == info.id }
.toList().getOrNull(0)?.let { match ->
withContext(Main) {
adapter.data[match.second].downloaded = true
adapter.data[match.second].track.downloaded = true
adapter.notifyItemChanged(match.second)
}
}

View File

@ -5,6 +5,7 @@ sealed class CacheItem<D : Any>(val data: List<D>)
class ArtistsCache(data: List<Artist>) : CacheItem<Artist>(data)
class AlbumsCache(data: List<Album>) : CacheItem<Album>(data)
class TracksCache(data: List<Track>) : CacheItem<Track>(data)
class FavoritesCache(data: List<Favorite>) : CacheItem<Favorite>(data)
class PlaylistsCache(data: List<Playlist>) : CacheItem<Playlist>(data)
class PlaylistTracksCache(data: List<PlaylistTrack>) : CacheItem<PlaylistTrack>(data)
class RadiosCache(data: List<Radio>) : CacheItem<Radio>(data)

View File

@ -0,0 +1,10 @@
package audio.funkwhale.ffa.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class Favorite(
val id: Int = 0,
val track: Track
) : Parcelable

View File

@ -0,0 +1,9 @@
package audio.funkwhale.ffa.model
data class FavoritesResponse(
override val count: Int,
override val next: String?,
val results: List<Favorite>
) : FFAResponse<Favorite>() {
override fun getData() = results
}

View File

@ -2,11 +2,11 @@ package audio.funkwhale.ffa.repositories
import android.content.Context
import audio.funkwhale.ffa.model.FFAResponse
import audio.funkwhale.ffa.model.Favorite
import audio.funkwhale.ffa.model.FavoritesResponse
import audio.funkwhale.ffa.model.FavoritedCache
import audio.funkwhale.ffa.model.FavoritedResponse
import audio.funkwhale.ffa.model.Track
import audio.funkwhale.ffa.model.TracksCache
import audio.funkwhale.ffa.model.TracksResponse
import audio.funkwhale.ffa.model.FavoritesCache
import audio.funkwhale.ffa.utils.FFACache
import audio.funkwhale.ffa.utils.OAuth
import audio.funkwhale.ffa.utils.Settings
@ -28,7 +28,7 @@ import kotlinx.coroutines.runBlocking
import org.koin.core.qualifier.named
import org.koin.java.KoinJavaComponent.inject
class FavoritesRepository(override val context: Context?) : Repository<Track, TracksCache>() {
class FavoritesRepository(override val context: Context?) : Repository<Favorite, FavoritesCache>() {
private val exoDownloadManager: DownloadManager by inject(DownloadManager::class.java)
private val exoCache: Cache by inject(Cache::class.java, named("exoCache"))
@ -36,34 +36,34 @@ class FavoritesRepository(override val context: Context?) : Repository<Track, Tr
override val cacheId = "favorites.v2"
override val upstream = HttpUpstream<Track, FFAResponse<Track>>(
override val upstream = HttpUpstream<Favorite, FFAResponse<Favorite>>(
context!!,
HttpUpstream.Behavior.AtOnce,
"/api/v1/tracks/?favorites=true&playable=true&ordering=title",
object : TypeToken<TracksResponse>() {}.type,
"/api/v1/favorites/tracks/?scope=all&ordering=-creation_date",
object : TypeToken<FavoritesResponse>() {}.type,
oAuth
)
override fun cache(data: List<Track>) = TracksCache(data)
override fun cache(data: List<Favorite>) = FavoritesCache(data)
override fun uncache(json: String) =
gsonDeserializerOf(TracksCache::class.java).deserialize(json.reader())
gsonDeserializerOf(FavoritesCache::class.java).deserialize(json.reader())
private val favoritedRepository = FavoritedRepository(context!!)
override fun onDataFetched(data: List<Track>): List<Track> = runBlocking {
override fun onDataFetched(data: List<Favorite>): List<Favorite> = runBlocking {
val downloaded = TracksRepository.getDownloadedIds(exoDownloadManager) ?: listOf()
data.map { track ->
track.favorite = true
track.downloaded = downloaded.contains(track.id)
data.map { favorite ->
favorite.track.favorite = true
favorite.track.downloaded = downloaded.contains(favorite.track.id)
track.bestUpload()?.let { upload ->
favorite.track.bestUpload()?.let { upload ->
maybeNormalizeUrl(upload.listen_url)?.let { url ->
track.cached = exoCache.isCached(url, 0, upload.duration * 1000L)
favorite.track.cached = exoCache.isCached(url, 0, upload.duration * 1000L)
}
}
track
favorite
}
}