funkwhale-app-android/app/src/main/java/audio/funkwhale/ffa/adapters/SearchAdapter.kt

382 lines
12 KiB
Kotlin
Raw Normal View History

package audio.funkwhale.ffa.adapters
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.graphics.Typeface
import android.os.Build
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
2023-01-10 13:56:20 +01:00
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.RecyclerView
import audio.funkwhale.ffa.R
2021-07-16 10:03:52 +02:00
import audio.funkwhale.ffa.databinding.RowSearchHeaderBinding
import audio.funkwhale.ffa.databinding.RowTrackBinding
import audio.funkwhale.ffa.model.Album
import audio.funkwhale.ffa.model.Artist
2021-09-09 09:56:15 +02:00
import audio.funkwhale.ffa.model.Track
2021-07-16 10:03:52 +02:00
import audio.funkwhale.ffa.utils.Command
import audio.funkwhale.ffa.utils.CommandBus
import audio.funkwhale.ffa.utils.CoverArt
2021-07-16 10:03:52 +02:00
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
import audio.funkwhale.ffa.utils.onApi
import audio.funkwhale.ffa.utils.toast
2023-01-10 13:56:20 +01:00
import audio.funkwhale.ffa.viewmodel.SearchViewModel
import com.squareup.picasso.Picasso
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
2021-07-16 10:03:52 +02:00
class SearchAdapter(
2023-01-10 13:56:20 +01:00
viewModel: SearchViewModel,
private val fragment: Fragment,
private val listener: OnSearchResultClickListener,
private val favoriteListener: FavoriteListener
2021-07-16 10:03:52 +02:00
) : RecyclerView.Adapter<SearchAdapter.ViewHolder>() {
interface OnSearchResultClickListener {
fun onArtistClick(holder: View?, artist: Artist)
fun onAlbumClick(holder: View?, album: Album)
}
enum class ResultType {
Header,
Artist,
Album,
Track
}
2021-07-16 10:03:52 +02:00
private lateinit var searchHeaderBinding: RowSearchHeaderBinding
private lateinit var rowTrackBinding: RowTrackBinding
val sectionCount = 3
2023-01-10 13:56:20 +01:00
var artists = listOf<Artist>()
var albums = listOf<Album>()
var tracks = listOf<Track>()
var currentTrack: Track? = null
2023-01-10 13:56:20 +01:00
init {
viewModel.artistResults.observe(fragment.viewLifecycleOwner) {
artists = it
this.notifyDataSetChanged()
}
viewModel.albumResults.observe(fragment.viewLifecycleOwner) {
albums = it
this.notifyDataSetChanged()
}
viewModel.trackResults.observe(fragment.viewLifecycleOwner) {
tracks = it
this.notifyDataSetChanged()
}
}
2021-07-16 10:03:52 +02:00
override fun getItemCount() = sectionCount + artists.size + albums.size + tracks.size
override fun getItemId(position: Int): Long {
return when (getItemViewType(position)) {
ResultType.Header.ordinal -> {
if (position == 0) return -1
if (position == (artists.size + 1)) return -2
return -3
}
ResultType.Artist.ordinal -> artists[position].id.toLong()
2023-01-10 13:56:20 +01:00
ResultType.Album.ordinal -> albums[position - artists.size - 2].id.toLong()
2021-09-09 09:56:15 +02:00
ResultType.Track.ordinal ->
tracks[position - artists.size - albums.size - sectionCount].id.toLong()
else -> 0
}
}
2021-09-09 09:56:15 +02:00
override fun getItemViewType(position: Int): Int = when {
position == 0 ||
position == (artists.size + 1) ||
position == (artists.size + albums.size + 2) -> ResultType.Header.ordinal
position <= artists.size -> ResultType.Artist.ordinal
position <= artists.size + albums.size + 2 -> ResultType.Album.ordinal
else -> ResultType.Track.ordinal
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
2021-07-16 10:03:52 +02:00
return when (viewType) {
ResultType.Header.ordinal -> {
2023-01-10 13:56:20 +01:00
searchHeaderBinding = RowSearchHeaderBinding.inflate(fragment.layoutInflater, parent, false)
SearchHeaderViewHolder(searchHeaderBinding, fragment.requireContext())
2021-07-16 10:03:52 +02:00
}
else -> {
2023-01-10 13:56:20 +01:00
rowTrackBinding = RowTrackBinding.inflate(fragment.layoutInflater, parent, false)
RowTrackViewHolder(rowTrackBinding, fragment.requireContext()).also {
2021-07-16 10:03:52 +02:00
rowTrackBinding.root.setOnClickListener(it)
}
}
}
}
@SuppressLint("NewApi")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val resultType = getItemViewType(position)
2021-07-16 10:03:52 +02:00
val searchHeaderViewHolder = holder as? SearchHeaderViewHolder
val rowTrackViewHolder = holder as? RowTrackViewHolder
if (resultType == ResultType.Header.ordinal) {
2023-01-10 13:56:20 +01:00
if (position == 0) {
searchHeaderViewHolder?.title?.text = fragment.requireContext().getString(R.string.artists)
holder.itemView.visibility = View.VISIBLE
holder.itemView.layoutParams = RecyclerView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
if (artists.isEmpty()) {
holder.itemView.visibility = View.GONE
holder.itemView.layoutParams = RecyclerView.LayoutParams(0, 0)
}
2023-01-10 13:56:20 +01:00
}
2023-01-10 13:56:20 +01:00
if (position == (artists.size + 1)) {
searchHeaderViewHolder?.title?.text = fragment.requireContext().getString(R.string.albums)
holder.itemView.visibility = View.VISIBLE
holder.itemView.layoutParams = RecyclerView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
if (albums.isEmpty()) {
holder.itemView.visibility = View.GONE
holder.itemView.layoutParams = RecyclerView.LayoutParams(0, 0)
}
2023-01-10 13:56:20 +01:00
}
2023-01-10 13:56:20 +01:00
if (position == (artists.size + albums.size + 2)) {
searchHeaderViewHolder?.title?.text = fragment.requireContext().getString(R.string.tracks)
holder.itemView.visibility = View.VISIBLE
holder.itemView.layoutParams = RecyclerView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
if (tracks.isEmpty()) {
holder.itemView.visibility = View.GONE
holder.itemView.layoutParams = RecyclerView.LayoutParams(0, 0)
}
}
return
}
val item = when (resultType) {
ResultType.Artist.ordinal -> {
2021-07-16 10:03:52 +02:00
rowTrackViewHolder?.actions?.visibility = View.GONE
rowTrackViewHolder?.favorite?.visibility = View.GONE
artists[position - 1]
}
ResultType.Album.ordinal -> {
2021-07-16 10:03:52 +02:00
rowTrackViewHolder?.actions?.visibility = View.GONE
rowTrackViewHolder?.favorite?.visibility = View.GONE
albums[position - artists.size - 2]
}
ResultType.Track.ordinal -> {
tracks[position - artists.size - albums.size - sectionCount]
}
else -> tracks[position]
}
2023-01-10 13:56:20 +01:00
CoverArt.withContext(fragment.layoutInflater.context, maybeNormalizeUrl(item.cover()))
.fit()
.transform(RoundedCornersTransformation(16, 0))
2021-07-16 10:03:52 +02:00
.into(rowTrackViewHolder?.cover)
2021-07-23 09:06:12 +02:00
rowTrackViewHolder?.title?.text = item.title()
2021-07-16 10:03:52 +02:00
rowTrackViewHolder?.artist?.text = item.subtitle()
Build.VERSION_CODES.P.onApi(
{
2021-07-16 10:03:52 +02:00
searchHeaderViewHolder?.title?.setTypeface(
searchHeaderViewHolder.title.typeface,
Typeface.DEFAULT.weight
)
rowTrackViewHolder?.artist?.setTypeface(
rowTrackViewHolder.artist.typeface,
Typeface.DEFAULT.weight
)
},
{
2021-07-16 10:03:52 +02:00
searchHeaderViewHolder?.title?.typeface =
Typeface.create(searchHeaderViewHolder?.title?.typeface, Typeface.NORMAL)
rowTrackViewHolder?.artist?.typeface =
Typeface.create(rowTrackViewHolder?.artist?.typeface, Typeface.NORMAL)
2021-09-09 09:56:15 +02:00
}
)
2021-07-16 10:03:52 +02:00
searchHeaderViewHolder?.title?.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0)
when (resultType) {
2021-09-09 09:56:15 +02:00
ResultType.Artist.ordinal -> {
rowTrackViewHolder?.title?.setCompoundDrawablesWithIntrinsicBounds(
0, 0, 0, 0
)
}
ResultType.Album.ordinal -> {
rowTrackViewHolder?.title?.setCompoundDrawablesWithIntrinsicBounds(
0, 0, 0, 0
)
}
ResultType.Track.ordinal -> {
(item as? Track)?.let { track ->
2023-01-10 13:56:20 +01:00
if (track == currentTrack || track.current) {
searchHeaderViewHolder?.title?.setTypeface(
searchHeaderViewHolder.title.typeface,
Typeface.BOLD
)
rowTrackViewHolder?.artist?.setTypeface(
rowTrackViewHolder.artist.typeface,
Typeface.BOLD
)
}
2023-01-10 13:56:20 +01:00
when (track.favorite) {
true -> rowTrackViewHolder?.favorite?.setColorFilter(
fragment.requireContext().getColor(R.color.colorFavorite)
)
false -> rowTrackViewHolder?.favorite?.setColorFilter(
fragment.requireContext().getColor(R.color.colorSelected)
)
}
2023-01-10 13:56:20 +01:00
rowTrackViewHolder?.favorite?.setOnClickListener {
favoriteListener.let {
favoriteListener.onToggleFavorite(track.id, !track.favorite)
2023-01-10 13:56:20 +01:00
tracks[position - artists.size - albums.size - sectionCount].favorite =
!track.favorite
2023-01-10 13:56:20 +01:00
notifyItemChanged(position)
2021-09-09 09:56:15 +02:00
}
2023-01-10 13:56:20 +01:00
}
2023-01-10 13:56:20 +01:00
when (track.cached || track.downloaded) {
true -> rowTrackViewHolder?.title?.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.downloaded, 0, 0, 0
)
false -> rowTrackViewHolder?.title?.setCompoundDrawablesWithIntrinsicBounds(
0, 0, 0, 0
)
}
2023-01-10 13:56:20 +01:00
if (track.cached && !track.downloaded) {
rowTrackViewHolder?.title?.compoundDrawables?.forEach {
it?.colorFilter =
PorterDuffColorFilter(
fragment.requireContext().getColor(R.color.cached),
PorterDuff.Mode.SRC_IN
)
2021-09-09 09:56:15 +02:00
}
2023-01-10 13:56:20 +01:00
}
2023-01-10 13:56:20 +01:00
if (track.downloaded) {
rowTrackViewHolder?.title?.compoundDrawables?.forEach {
it?.colorFilter =
PorterDuffColorFilter(
fragment.requireContext().getColor(R.color.downloaded),
PorterDuff.Mode.SRC_IN
)
2021-09-09 09:56:15 +02:00
}
2023-01-10 13:56:20 +01:00
}
2023-01-10 13:56:20 +01:00
rowTrackViewHolder?.actions?.setOnClickListener {
PopupMenu(
fragment.requireContext(),
rowTrackViewHolder.actions,
Gravity.START,
R.attr.actionOverflowMenuStyle,
0
).apply {
inflate(R.menu.row_track)
setOnMenuItemClickListener {
when (it.itemId) {
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.track_add_to_playlist -> CommandBus.send(
Command.AddToPlaylist(listOf(track))
)
R.id.queue_remove -> CommandBus.send(Command.RemoveFromQueue(track))
}
2021-09-09 09:56:15 +02:00
2023-01-10 13:56:20 +01:00
true
}
2023-01-10 13:56:20 +01:00
show()
}
}
}
2021-09-09 09:56:15 +02:00
}
}
}
fun getPositionOf(type: ResultType, position: Int): Int {
return when (type) {
ResultType.Artist -> position + 1
ResultType.Album -> position + artists.size + 2
2021-07-16 10:03:52 +02:00
ResultType.Track -> artists.size + albums.size + sectionCount + position
else -> 0
}
}
2023-01-10 13:56:20 +01:00
inner class SearchHeaderViewHolder(val binding: RowSearchHeaderBinding, context: Context) :
2021-07-16 10:03:52 +02:00
ViewHolder(binding.root, context) {
val title = binding.title
}
2023-01-10 13:56:20 +01:00
inner class RowTrackViewHolder(val binding: RowTrackBinding, context: Context) :
2021-07-16 10:03:52 +02:00
ViewHolder(binding.root, context), View.OnClickListener {
2021-07-23 09:06:12 +02:00
val title = binding.title
2021-07-16 10:03:52 +02:00
val cover = binding.cover
val artist = binding.artist
val favorite = binding.favorite
val actions = binding.actions
override fun onClick(view: View?) {
when (getItemViewType(layoutPosition)) {
ResultType.Artist.ordinal -> {
val position = layoutPosition - 1
listener.onArtistClick(view, artists[position])
}
ResultType.Album.ordinal -> {
val position = layoutPosition - artists.size - 2
listener.onAlbumClick(view, albums[position])
}
ResultType.Track.ordinal -> {
2021-07-16 10:03:52 +02:00
val position = layoutPosition - artists.size - albums.size - sectionCount
tracks.subList(position, tracks.size).plus(tracks.subList(0, position)).apply {
CommandBus.send(Command.ReplaceQueue(this))
context.toast("All tracks were added to your queue")
}
}
else -> {
2021-07-16 10:03:52 +02:00
// empty
}
}
}
}
2021-07-16 10:03:52 +02:00
abstract inner class ViewHolder(view: View, val context: Context?) : RecyclerView.ViewHolder(view)
}