Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • bugfix/46-adjust-now-playing-top-margin
  • deploy-in-docker
  • develop
  • enhancement/speed-up-pipelines
  • fix-ci-for-tags
  • gradle-v8
  • housekeeping/mavenCentral
  • housekeeping/remove-warnings
  • renovate/io.mockk-mockk-1.x
  • technical/skip-metadata-file-for-branches
  • technical/update-compile-sdk-version-docker
  • technical/update-jvmTarget-version
  • technical/upgrade-appcompat-1.5.x
  • technical/upgrade-exoplayer
  • 0.0.1
  • 0.1
  • 0.1.1
  • 0.1.2
  • 0.1.3
  • 0.1.4
  • 0.1.5
  • 0.1.5-1
  • 0.2.0
  • 0.2.1
  • 0.2.1-1
  • 0.3.0
  • fdroid-dummy-1
  • fdroid-dummy-2
  • fdroid-dummy-3
  • fdroid-dummy-4
  • fdroid-dummy-5
31 results

Target

Select target project
  • funkwhale/funkwhale-android
  • creak/funkwhale-android
  • Keunes/funkwhale-android
  • Mouath/funkwhale-android
4 results
Select Git revision
  • bugfix/46-adjust-now-playing-top-margin
  • bugfix/90-error-playing-user-radio
  • deploy-in-docker
  • develop
  • enhancement/89-update-appstore-metadata
  • enhancement/speed-up-pipelines
  • housekeeping/remove-warnings
  • renovate/configure
  • 0.0.1
  • 0.1
  • 0.1.1
11 results
Show changes
Showing
with 769 additions and 611 deletions
......@@ -8,9 +8,9 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.databinding.RowDownloadBinding
import audio.funkwhale.ffa.playback.PinService
import audio.funkwhale.ffa.model.DownloadInfo
import audio.funkwhale.ffa.model.Track
import audio.funkwhale.ffa.playback.PinService
import com.google.android.exoplayer2.offline.Download
import com.google.android.exoplayer2.offline.DownloadService
......
package audio.funkwhale.ffa.adapters
import audio.funkwhale.ffa.repositories.FavoritesRepository
class FavoriteListener(private val repository: FavoritesRepository) {
fun onToggleFavorite(id: Int, state: Boolean) {
when (state) {
true -> repository.addFavorite(id)
false -> repository.deleteFavorite(id)
}
}
}
......@@ -8,35 +8,37 @@ 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
import audio.funkwhale.ffa.model.Track
import audio.funkwhale.ffa.utils.maybeLoad
import audio.funkwhale.ffa.utils.CoverArt
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
import audio.funkwhale.ffa.utils.toast
import com.squareup.picasso.Picasso
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
import java.util.Collections
class FavoritesAdapter(
private val layoutInflater: LayoutInflater,
private val context: Context?,
private val favoriteListener: OnFavoriteListener,
val fromQueue: Boolean = false
) : FFAAdapter<Track, FavoritesAdapter.ViewHolder>() {
private val favoriteListener: FavoriteListener,
val fromQueue: Boolean = false,
) : FFAAdapter<Favorite, FavoritesAdapter.ViewHolder>() {
interface OnFavoriteListener {
fun onToggleFavorite(id: Int, state: Boolean)
init {
this.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
}
private lateinit var binding: RowTrackBinding
var currentTrack: Track? = null
var filter = ""
override fun getItemCount() = data.size
......@@ -44,6 +46,15 @@ class FavoritesAdapter(
return data[position].id.toLong()
}
override fun applyFilter() {
data.clear()
getUnfilteredData().map {
if (it.track.matchesFilter(filter)) {
data.add(it)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
binding = RowTrackBinding.inflate(layoutInflater, parent, false)
......@@ -56,46 +67,42 @@ class FavoritesAdapter(
@SuppressLint("NewApi")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val favorite = data[position]
val track = favorite.track
Picasso.get()
.maybeLoad(maybeNormalizeUrl(favorite.album?.cover()))
CoverArt.requestCreator(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)
......@@ -103,8 +110,7 @@ class FavoritesAdapter(
}
holder.favorite.setOnClickListener {
favoriteListener.onToggleFavorite(favorite.id, !favorite.favorite)
favoriteListener.onToggleFavorite(track.id, !track.favorite)
data.remove(favorite)
notifyItemRemoved(holder.bindingAdapterPosition)
}
......@@ -117,10 +123,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
......@@ -161,9 +167,11 @@ class FavoritesAdapter(
when (fromQueue) {
true -> CommandBus.send(Command.PlayTrack(layoutPosition))
false -> {
data.subList(layoutPosition, data.size).plus(data.subList(0, layoutPosition)).apply {
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")
}
}
......
......@@ -16,28 +16,23 @@ 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.utils.Command
import audio.funkwhale.ffa.utils.CommandBus
import audio.funkwhale.ffa.model.PlaylistTrack
import audio.funkwhale.ffa.model.Track
import audio.funkwhale.ffa.utils.maybeLoad
import audio.funkwhale.ffa.utils.Command
import audio.funkwhale.ffa.utils.CommandBus
import audio.funkwhale.ffa.utils.CoverArt
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
import audio.funkwhale.ffa.utils.toast
import com.squareup.picasso.Picasso
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
import java.util.Collections
class PlaylistTracksAdapter(
private val layoutInflater: LayoutInflater,
private val context: Context?,
private val favoriteListener: OnFavoriteListener? = null,
private val playlistListener: OnPlaylistListener? = null
private val favoriteListener: FavoriteListener,
private val playlistListener: OnPlaylistListener
) : FFAAdapter<PlaylistTrack, PlaylistTracksAdapter.ViewHolder>() {
interface OnFavoriteListener {
fun onToggleFavorite(id: Int, state: Boolean)
}
private lateinit var binding: RowTrackBinding
interface OnPlaylistListener {
......@@ -74,39 +69,37 @@ class PlaylistTracksAdapter(
@SuppressLint("NewApi")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val track = data[position]
val playlistTrack = data[position]
Picasso.get()
.maybeLoad(maybeNormalizeUrl(track.track.album?.cover()))
CoverArt.requestCreator(maybeNormalizeUrl(playlistTrack.track.cover()))
.fit()
.placeholder(R.drawable.cover)
.transform(RoundedCornersTransformation(16, 0))
.into(holder.cover)
holder.title.text = track.track.title
holder.artist.text = track.track.artist.name
holder.title.text = playlistTrack.track.title
holder.artist.text = playlistTrack.track.artist.name
context?.let {
holder.itemView.background = ContextCompat.getDrawable(context, R.drawable.ripple)
}
if (track.track == currentTrack || track.track.current) {
if (playlistTrack.track == currentTrack || playlistTrack.track.current) {
context?.let {
holder.itemView.background = ContextCompat.getDrawable(context, R.drawable.current)
}
}
context?.let {
when (track.track.favorite) {
when (playlistTrack.track.favorite) {
true -> holder.favorite.setColorFilter(context.getColor(R.color.colorFavorite))
false -> holder.favorite.setColorFilter(context.getColor(R.color.colorSelected))
}
holder.favorite.setOnClickListener {
favoriteListener?.let {
favoriteListener.onToggleFavorite(track.track.id, !track.track.favorite)
favoriteListener.let {
favoriteListener.onToggleFavorite(playlistTrack.track.id, !playlistTrack.track.favorite)
track.track.favorite = !track.track.favorite
playlistTrack.track.favorite = !playlistTrack.track.favorite
notifyItemChanged(position)
}
}
......@@ -121,11 +114,11 @@ class PlaylistTracksAdapter(
setOnMenuItemClickListener {
when (it.itemId) {
R.id.track_add_to_queue -> CommandBus.send(Command.AddToQueue(listOf(track.track)))
R.id.track_play_next -> CommandBus.send(Command.PlayNext(track.track))
R.id.queue_remove -> CommandBus.send(Command.RemoveFromQueue(track.track))
R.id.track_remove_from_playlist -> playlistListener?.onRemoveTrackFromPlaylist(
track.track,
R.id.track_add_to_queue -> CommandBus.send(Command.AddToQueue(listOf(playlistTrack.track)))
R.id.track_play_next -> CommandBus.send(Command.PlayNext(playlistTrack.track))
R.id.queue_remove -> CommandBus.send(Command.RemoveFromQueue(playlistTrack.track))
R.id.track_remove_from_playlist -> playlistListener.onRemoveTrackFromPlaylist(
playlistTrack.track,
position
)
}
......@@ -223,7 +216,7 @@ class PlaylistTracksAdapter(
viewHolder.itemView.background = ColorDrawable(Color.TRANSPARENT)
if (from != -1 && to != -1 && from != to) {
playlistListener?.onMoveTrack(from, to)
playlistListener.onMoveTrack(from, to)
from = -1
to = -1
......
......@@ -10,8 +10,8 @@ import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.databinding.RowPlaylistBinding
import audio.funkwhale.ffa.fragments.FFAAdapter
import audio.funkwhale.ffa.model.Playlist
import audio.funkwhale.ffa.utils.CoverArt
import audio.funkwhale.ffa.utils.toDurationString
import com.squareup.picasso.Picasso
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
class PlaylistsAdapter(
......@@ -20,6 +20,10 @@ class PlaylistsAdapter(
private val listener: OnPlaylistClickListener
) : FFAAdapter<Playlist, PlaylistsAdapter.ViewHolder>() {
init {
this.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
}
interface OnPlaylistClickListener {
fun onClick(holder: View?, playlist: Playlist)
}
......@@ -75,8 +79,7 @@ class PlaylistsAdapter(
else -> RoundedCornersTransformation.CornerType.TOP_LEFT
}
Picasso.get()
.load(url)
CoverArt.requestCreator(url)
.transform(RoundedCornersTransformation(32, 0, corner))
.into(imageView)
}
......
......@@ -9,15 +9,14 @@ import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.databinding.RowRadioBinding
import audio.funkwhale.ffa.databinding.RowRadioHeaderBinding
import audio.funkwhale.ffa.fragments.FFAAdapter
import audio.funkwhale.ffa.model.Radio
import audio.funkwhale.ffa.utils.AppContext
import audio.funkwhale.ffa.utils.Event
import audio.funkwhale.ffa.utils.EventBus
import audio.funkwhale.ffa.model.Radio
import audio.funkwhale.ffa.views.LoadingImageView
import com.preference.PowerPreference
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
class RadiosAdapter(
......@@ -27,6 +26,10 @@ class RadiosAdapter(
private val listener: OnRadioClickListener
) : FFAAdapter<Radio, RadiosAdapter.ViewHolder>() {
init {
this.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
}
interface OnRadioClickListener {
fun onClick(holder: RowRadioViewHolder, radio: Radio)
}
......@@ -42,8 +45,10 @@ class RadiosAdapter(
private val instanceRadios: List<Radio> by lazy {
context?.let {
return@lazy when (val username =
PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("actor_username")) {
return@lazy when (
val username =
PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("actor_username")
) {
"" -> listOf(
Radio(
0,
......@@ -56,7 +61,7 @@ class RadiosAdapter(
else -> listOf(
Radio(
0,
"actor_content",
"actor-content",
context.getString(R.string.radio_your_content_title),
context.getString(R.string.radio_your_content_description),
username
......@@ -133,7 +138,8 @@ class RadiosAdapter(
context?.let {
when (position) {
0 -> holder.label.text = context.getString(R.string.radio_instance_radios)
instanceRadios.size + 1 -> holder.label.text =
instanceRadios.size + 1 ->
holder.label.text =
context.getString(R.string.radio_user_radios)
}
}
......@@ -183,9 +189,8 @@ class RadiosAdapter(
art.setColorFilter(context.getColor(R.color.controlForeground))
scope.launch(Main) {
EventBus.get().collect { message ->
when (message) {
is Event.RadioStarted -> {
EventBus.get().collect { event ->
if (event is Event.RadioStarted) {
art.colorFilter = originalColorFilter
LoadingImageView.stop(context, originalDrawable, art, imageAnimator)
}
......@@ -193,7 +198,6 @@ class RadiosAdapter(
}
}
}
}
override fun onClick(view: View?) {
listener.onClick(this, getRadioAt(layoutPosition))
......
......@@ -7,31 +7,32 @@ import android.graphics.PorterDuffColorFilter
import android.graphics.Typeface
import android.os.Build
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.RecyclerView
import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.databinding.RowSearchHeaderBinding
import audio.funkwhale.ffa.databinding.RowTrackBinding
import audio.funkwhale.ffa.model.Album
import audio.funkwhale.ffa.model.Artist
import audio.funkwhale.ffa.model.Track
import audio.funkwhale.ffa.utils.Command
import audio.funkwhale.ffa.utils.CommandBus
import audio.funkwhale.ffa.model.Track
import audio.funkwhale.ffa.utils.maybeLoad
import audio.funkwhale.ffa.utils.CoverArt
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
import audio.funkwhale.ffa.utils.onApi
import audio.funkwhale.ffa.utils.toast
import audio.funkwhale.ffa.viewmodel.SearchViewModel
import com.squareup.picasso.Picasso
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
class SearchAdapter(
private val layoutInflater: LayoutInflater,
private val context: Context?,
private val listener: OnSearchResultClickListener? = null,
private val favoriteListener: OnFavoriteListener? = null
viewModel: SearchViewModel,
private val fragment: Fragment,
private val listener: OnSearchResultClickListener,
private val favoriteListener: FavoriteListener
) : RecyclerView.Adapter<SearchAdapter.ViewHolder>() {
interface OnSearchResultClickListener {
......@@ -39,10 +40,6 @@ class SearchAdapter(
fun onAlbumClick(holder: View?, album: Album)
}
interface OnFavoriteListener {
fun onToggleFavorite(id: Int, state: Boolean)
}
enum class ResultType {
Header,
Artist,
......@@ -55,12 +52,27 @@ class SearchAdapter(
val sectionCount = 3
var artists: MutableList<Artist> = mutableListOf()
var albums: MutableList<Album> = mutableListOf()
var tracks: MutableList<Track> = mutableListOf()
var artists = listOf<Artist>()
var albums = listOf<Album>()
var tracks = listOf<Track>()
var currentTrack: Track? = null
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()
}
}
override fun getItemCount() = sectionCount + artists.size + albums.size + tracks.size
override fun getItemId(position: Int): Long {
......@@ -72,32 +84,31 @@ class SearchAdapter(
}
ResultType.Artist.ordinal -> artists[position].id.toLong()
ResultType.Artist.ordinal -> albums[position - artists.size - 2].id.toLong()
ResultType.Track.ordinal -> tracks[position - artists.size - albums.size - sectionCount].id.toLong()
ResultType.Album.ordinal -> albums[position - artists.size - 2].id.toLong()
ResultType.Track.ordinal ->
tracks[position - artists.size - albums.size - sectionCount].id.toLong()
else -> 0
}
}
override fun getItemViewType(position: Int): Int {
if (position == 0) return ResultType.Header.ordinal // Artists header
if (position == (artists.size + 1)) return ResultType.Header.ordinal // Albums header
if (position == (artists.size + albums.size + 2)) return ResultType.Header.ordinal // Tracks header
if (position <= artists.size) return ResultType.Artist.ordinal
if (position <= artists.size + albums.size + 2) return ResultType.Album.ordinal
return ResultType.Track.ordinal
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 {
return when (viewType) {
ResultType.Header.ordinal -> {
searchHeaderBinding = RowSearchHeaderBinding.inflate(layoutInflater, parent, false)
SearchHeaderViewHolder(searchHeaderBinding, context)
searchHeaderBinding = RowSearchHeaderBinding.inflate(fragment.layoutInflater, parent, false)
SearchHeaderViewHolder(searchHeaderBinding, fragment.requireContext())
}
else -> {
rowTrackBinding = RowTrackBinding.inflate(layoutInflater, parent, false)
RowTrackViewHolder(rowTrackBinding, context).also {
rowTrackBinding = RowTrackBinding.inflate(fragment.layoutInflater, parent, false)
RowTrackViewHolder(rowTrackBinding, fragment.requireContext()).also {
rowTrackBinding.root.setOnClickListener(it)
}
}
......@@ -111,9 +122,8 @@ class SearchAdapter(
val rowTrackViewHolder = holder as? RowTrackViewHolder
if (resultType == ResultType.Header.ordinal) {
context?.let { context ->
if (position == 0) {
searchHeaderViewHolder?.title?.text = context.getString(R.string.artists)
searchHeaderViewHolder?.title?.text = fragment.requireContext().getString(R.string.artists)
holder.itemView.visibility = View.VISIBLE
holder.itemView.layoutParams = RecyclerView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
......@@ -127,7 +137,7 @@ class SearchAdapter(
}
if (position == (artists.size + 1)) {
searchHeaderViewHolder?.title?.text = context.getString(R.string.albums)
searchHeaderViewHolder?.title?.text = fragment.requireContext().getString(R.string.albums)
holder.itemView.visibility = View.VISIBLE
holder.itemView.layoutParams = RecyclerView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
......@@ -141,7 +151,7 @@ class SearchAdapter(
}
if (position == (artists.size + albums.size + 2)) {
searchHeaderViewHolder?.title?.text = context.getString(R.string.tracks)
searchHeaderViewHolder?.title?.text = fragment.requireContext().getString(R.string.tracks)
holder.itemView.visibility = View.VISIBLE
holder.itemView.layoutParams = RecyclerView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
......@@ -153,7 +163,6 @@ class SearchAdapter(
holder.itemView.layoutParams = RecyclerView.LayoutParams(0, 0)
}
}
}
return
}
......@@ -180,8 +189,7 @@ class SearchAdapter(
else -> tracks[position]
}
Picasso.get()
.maybeLoad(maybeNormalizeUrl(item.cover()))
CoverArt.requestCreator(maybeNormalizeUrl(item.cover()))
.fit()
.transform(RoundedCornersTransformation(16, 0))
.into(rowTrackViewHolder?.cover)
......@@ -205,7 +213,8 @@ class SearchAdapter(
Typeface.create(searchHeaderViewHolder?.title?.typeface, Typeface.NORMAL)
rowTrackViewHolder?.artist?.typeface =
Typeface.create(rowTrackViewHolder?.artist?.typeface, Typeface.NORMAL)
})
}
)
searchHeaderViewHolder?.title?.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0)
......@@ -222,7 +231,6 @@ class SearchAdapter(
}
ResultType.Track.ordinal -> {
(item as? Track)?.let { track ->
context?.let { context ->
if (track == currentTrack || track.current) {
searchHeaderViewHolder?.title?.setTypeface(
searchHeaderViewHolder.title.typeface,
......@@ -235,12 +243,16 @@ class SearchAdapter(
}
when (track.favorite) {
true -> rowTrackViewHolder?.favorite?.setColorFilter(context.getColor(R.color.colorFavorite))
false -> rowTrackViewHolder?.favorite?.setColorFilter(context.getColor(R.color.colorSelected))
true -> rowTrackViewHolder?.favorite?.setColorFilter(
fragment.requireContext().getColor(R.color.colorFavorite)
)
false -> rowTrackViewHolder?.favorite?.setColorFilter(
fragment.requireContext().getColor(R.color.colorSelected)
)
}
rowTrackViewHolder?.favorite?.setOnClickListener {
favoriteListener?.let {
favoriteListener.let {
favoriteListener.onToggleFavorite(track.id, !track.favorite)
tracks[position - artists.size - albums.size - sectionCount].favorite =
......@@ -262,20 +274,26 @@ class SearchAdapter(
if (track.cached && !track.downloaded) {
rowTrackViewHolder?.title?.compoundDrawables?.forEach {
it?.colorFilter =
PorterDuffColorFilter(context.getColor(R.color.cached), PorterDuff.Mode.SRC_IN)
PorterDuffColorFilter(
fragment.requireContext().getColor(R.color.cached),
PorterDuff.Mode.SRC_IN
)
}
}
if (track.downloaded) {
rowTrackViewHolder?.title?.compoundDrawables?.forEach {
it?.colorFilter =
PorterDuffColorFilter(context.getColor(R.color.downloaded), PorterDuff.Mode.SRC_IN)
PorterDuffColorFilter(
fragment.requireContext().getColor(R.color.downloaded),
PorterDuff.Mode.SRC_IN
)
}
}
rowTrackViewHolder?.actions?.setOnClickListener {
PopupMenu(
context,
fragment.requireContext(),
rowTrackViewHolder.actions,
Gravity.START,
R.attr.actionOverflowMenuStyle,
......@@ -288,7 +306,9 @@ class SearchAdapter(
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.track_add_to_playlist -> CommandBus.send(
Command.AddToPlaylist(listOf(track))
)
R.id.queue_remove -> CommandBus.send(Command.RemoveFromQueue(track))
}
......@@ -302,7 +322,6 @@ class SearchAdapter(
}
}
}
}
fun getPositionOf(type: ResultType, position: Int): Int {
return when (type) {
......@@ -313,12 +332,12 @@ class SearchAdapter(
}
}
inner class SearchHeaderViewHolder(val binding: RowSearchHeaderBinding, context: Context?) :
inner class SearchHeaderViewHolder(val binding: RowSearchHeaderBinding, context: Context) :
ViewHolder(binding.root, context) {
val title = binding.title
}
inner class RowTrackViewHolder(val binding: RowTrackBinding, context: Context?) :
inner class RowTrackViewHolder(val binding: RowTrackBinding, context: Context) :
ViewHolder(binding.root, context), View.OnClickListener {
val title = binding.title
val cover = binding.cover
......@@ -332,13 +351,13 @@ class SearchAdapter(
ResultType.Artist.ordinal -> {
val position = layoutPosition - 1
listener?.onArtistClick(view, artists[position])
listener.onArtistClick(view, artists[position])
}
ResultType.Album.ordinal -> {
val position = layoutPosition - artists.size - 2
listener?.onAlbumClick(view, albums[position])
listener.onAlbumClick(view, albums[position])
}
ResultType.Track.ordinal -> {
......
......@@ -18,25 +18,24 @@ 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.Track
import audio.funkwhale.ffa.utils.Command
import audio.funkwhale.ffa.utils.CommandBus
import audio.funkwhale.ffa.model.Track
import audio.funkwhale.ffa.utils.maybeLoad
import audio.funkwhale.ffa.utils.CoverArt
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
import audio.funkwhale.ffa.utils.toast
import com.squareup.picasso.Picasso
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
import java.util.Collections
class TracksAdapter(
private val layoutInflater: LayoutInflater,
private val context: Context?,
private val favoriteListener: OnFavoriteListener? = null,
private val favoriteListener: FavoriteListener,
val fromQueue: Boolean = false
) : FFAAdapter<Track, TracksAdapter.ViewHolder>() {
interface OnFavoriteListener {
fun onToggleFavorite(id: Int, state: Boolean)
init {
this.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
}
private lateinit var binding: RowTrackBinding
......@@ -71,8 +70,7 @@ class TracksAdapter(
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val track = data[position]
Picasso.get()
.maybeLoad(maybeNormalizeUrl(track.album?.cover()))
CoverArt.requestCreator(maybeNormalizeUrl(track.cover()))
.fit()
.transform(RoundedCornersTransformation(8, 0))
.into(holder.cover)
......@@ -97,7 +95,7 @@ class TracksAdapter(
}
holder.favorite.setOnClickListener {
favoriteListener?.let {
favoriteListener.let {
favoriteListener.onToggleFavorite(track.id, !track.favorite)
data[position].favorite = !track.favorite
......@@ -193,7 +191,6 @@ class TracksAdapter(
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")
}
}
......
......@@ -14,7 +14,8 @@ import audio.funkwhale.ffa.databinding.DialogAddToPlaylistBinding
import audio.funkwhale.ffa.model.Playlist
import audio.funkwhale.ffa.model.Track
import audio.funkwhale.ffa.repositories.ManagementPlaylistsRepository
import audio.funkwhale.ffa.utils.*
import audio.funkwhale.ffa.utils.FFACache
import audio.funkwhale.ffa.utils.untilNetwork
import com.google.gson.Gson
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
......@@ -80,7 +81,9 @@ object AddToPlaylistDialog {
}
val adapter =
PlaylistsAdapter(layoutInflater, activity, object : PlaylistsAdapter.OnPlaylistClickListener {
PlaylistsAdapter(
layoutInflater, activity,
object : PlaylistsAdapter.OnPlaylistClickListener {
override fun onClick(holder: View?, playlist: Playlist) {
repository.add(playlist.id, tracks)
......@@ -92,7 +95,8 @@ object AddToPlaylistDialog {
dialog.dismiss()
}
})
}
)
binding.playlists.layoutManager = LinearLayoutManager(activity)
binding.playlists.adapter = adapter
......@@ -102,7 +106,7 @@ object AddToPlaylistDialog {
fetch().untilNetwork(lifecycleScope) { data, isCache, _, hasMore ->
if (isCache) {
adapter.data = data.toMutableList()
adapter.setUnfilteredData(data.toMutableList())
adapter.notifyDataSetChanged()
return@untilNetwork
......@@ -120,7 +124,7 @@ object AddToPlaylistDialog {
FFACache.set(
context,
cacheId,
Gson().toJson(cache(adapter.data)).toByteArray()
Gson().toJson(cache(adapter.data)).toString()
)
} catch (e: ConcurrentModificationException) {
}
......
package audio.funkwhale.ffa.fragments
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
import androidx.transition.Fade
import androidx.transition.Slide
import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.activities.MainActivity
import audio.funkwhale.ffa.adapters.AlbumsAdapter
import audio.funkwhale.ffa.databinding.FragmentAlbumsBinding
import audio.funkwhale.ffa.model.Album
import audio.funkwhale.ffa.model.Artist
import audio.funkwhale.ffa.repositories.AlbumsRepository
import audio.funkwhale.ffa.repositories.ArtistTracksRepository
import audio.funkwhale.ffa.repositories.Repository
import audio.funkwhale.ffa.utils.*
import com.squareup.picasso.Picasso
import audio.funkwhale.ffa.utils.Command
import audio.funkwhale.ffa.utils.CommandBus
import audio.funkwhale.ffa.utils.CoverArt
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
import com.preference.PowerPreference
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
......@@ -40,77 +36,22 @@ class AlbumsFragment : FFAFragment<Album, AlbumsAdapter>() {
override val recycler: RecyclerView get() = binding.albums
override val alwaysRefresh = false
private val args by navArgs<AlbumsFragmentArgs>()
private val artistArt: String get() = when {
!args.cover.isNullOrBlank() -> args.cover!!
else -> args.artist.cover() ?: ""
}
private var _binding: FragmentAlbumsBinding? = null
private val binding get() = _binding!!
private lateinit var artistTracksRepository: ArtistTracksRepository
private var artistId = 0
private var artistName = ""
private var artistArt = ""
companion object {
fun new(artist: Artist, _art: String? = null): AlbumsFragment {
val art = _art ?: if (artist.albums?.isNotEmpty() == true) artist.cover() else ""
return AlbumsFragment().apply {
arguments = bundleOf(
"artistId" to artist.id,
"artistName" to artist.name,
"artistArt" to art
)
}
}
fun openTracks(context: Context?, album: Album?, fragment: Fragment? = null) {
if (album == null) {
return
}
(context as? MainActivity)?.let {
fragment?.let { fragment ->
fragment.onViewPager {
exitTransition = Fade().apply {
duration = AppContext.TRANSITION_DURATION
interpolator = AccelerateDecelerateInterpolator()
view?.let {
addTarget(it)
}
}
}
}
}
(context as? AppCompatActivity)?.let { activity ->
val nextFragment = TracksFragment.new(album).apply {
enterTransition = Slide().apply {
duration = AppContext.TRANSITION_DURATION
interpolator = AccelerateDecelerateInterpolator()
}
}
activity.supportFragmentManager
.beginTransaction()
.replace(R.id.container, nextFragment)
.addToBackStack(null)
.commit()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.apply {
artistId = getInt("artistId")
artistName = getString("artistName") ?: ""
artistArt = getString("artistArt") ?: ""
}
adapter = AlbumsAdapter(layoutInflater, context, OnAlbumClickListener())
repository = AlbumsRepository(context, artistId)
artistTracksRepository = ArtistTracksRepository(context, artistId)
repository = AlbumsRepository(context, args.artist.id)
artistTracksRepository = ArtistTracksRepository(context, args.artist.id)
}
override fun onCreateView(
......@@ -120,6 +61,12 @@ class AlbumsFragment : FFAFragment<Album, AlbumsAdapter>() {
): View {
_binding = FragmentAlbumsBinding.inflate(inflater)
swiper = binding.swiper
when (PowerPreference.getDefaultFile().getString("play_order")) {
"in_order" -> binding.play.text = getString(R.string.playback_play)
else -> binding.play.text = getString(R.string.playback_shuffle)
}
return binding.root
}
......@@ -132,8 +79,7 @@ class AlbumsFragment : FFAFragment<Album, AlbumsAdapter>() {
super.onViewCreated(view, savedInstanceState)
binding.cover.let { cover ->
Picasso.get()
.maybeLoad(maybeNormalizeUrl(artistArt))
CoverArt.requestCreator(maybeNormalizeUrl(artistArt))
.noFade()
.fit()
.centerCrop()
......@@ -141,7 +87,30 @@ class AlbumsFragment : FFAFragment<Album, AlbumsAdapter>() {
.into(cover)
}
binding.artist.text = artistName
binding.artist.text = args.artist.name
}
override fun onResume() {
super.onResume()
var coverHeight: Float? = null
binding.scroller.setOnScrollChangeListener { _: View?, _: Int, scrollY: Int, _: Int, _: Int ->
if (coverHeight == null) {
coverHeight = binding.cover.measuredHeight.toFloat()
}
binding.cover.translationY = (scrollY / 2).toFloat()
coverHeight?.let { height ->
binding.cover.alpha = (height - scrollY.toFloat()) / height
}
}
when (PowerPreference.getDefaultFile().getString("play_order")) {
"in_order" -> binding.play.text = getString(R.string.playback_play)
else -> binding.play.text = getString(R.string.playback_shuffle)
}
binding.play.setOnClickListener {
val loader = CircularProgressDrawable(requireContext()).apply {
......@@ -155,13 +124,15 @@ class AlbumsFragment : FFAFragment<Album, AlbumsAdapter>() {
binding.play.isClickable = false
lifecycleScope.launch(IO) {
artistTracksRepository.fetch(Repository.Origin.Network.origin)
val tracks = artistTracksRepository.fetch(Repository.Origin.Network.origin)
.map { it.data }
.toList()
.flatten()
.shuffled()
.also {
CommandBus.send(Command.ReplaceQueue(it))
when (PowerPreference.getDefaultFile().getString("play_order")) {
"in_order" -> CommandBus.send(Command.ReplaceQueue(tracks))
else -> CommandBus.send(Command.ReplaceQueue(tracks.shuffled()))
}
withContext(Main) {
binding.play.icon =
......@@ -171,29 +142,10 @@ class AlbumsFragment : FFAFragment<Album, AlbumsAdapter>() {
}
}
}
}
override fun onResume() {
super.onResume()
var coverHeight: Float? = null
binding.scroller.setOnScrollChangeListener { _: View?, _: Int, scrollY: Int, _: Int, _: Int ->
if (coverHeight == null) {
coverHeight = binding.cover.measuredHeight.toFloat()
}
binding.cover.translationY = (scrollY / 2).toFloat()
coverHeight?.let { height ->
binding.cover.alpha = (height - scrollY.toFloat()) / height
}
}
}
inner class OnAlbumClickListener : AlbumsAdapter.OnAlbumClickListener {
override fun onClick(view: View?, album: Album) {
openTracks(context, album, fragment = this@AlbumsFragment)
findNavController().navigate(AlbumsFragmentDirections.albumsToTracks(album))
}
}
}
......@@ -4,18 +4,13 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.transition.Fade
import androidx.transition.Slide
import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.activities.MainActivity
import audio.funkwhale.ffa.adapters.AlbumsGridAdapter
import audio.funkwhale.ffa.databinding.FragmentAlbumsGridBinding
import audio.funkwhale.ffa.repositories.AlbumsRepository
import audio.funkwhale.ffa.model.Album
import audio.funkwhale.ffa.utils.AppContext
import audio.funkwhale.ffa.repositories.AlbumsRepository
class AlbumsGridFragment : FFAFragment<Album, AlbumsGridAdapter>() {
......@@ -49,29 +44,7 @@ class AlbumsGridFragment : FFAFragment<Album, AlbumsGridAdapter>() {
inner class OnAlbumClickListener : AlbumsGridAdapter.OnAlbumClickListener {
override fun onClick(view: View?, album: Album) {
(context as? MainActivity)?.let { activity ->
exitTransition = Fade().apply {
duration = AppContext.TRANSITION_DURATION
interpolator = AccelerateDecelerateInterpolator()
view?.let {
addTarget(it)
}
}
val fragment = TracksFragment.new(album).apply {
enterTransition = Slide().apply {
duration = AppContext.TRANSITION_DURATION
interpolator = AccelerateDecelerateInterpolator()
}
}
activity.supportFragmentManager
.beginTransaction()
.replace(R.id.container, fragment)
.addToBackStack(null)
.commit()
}
findNavController().navigate(BrowseFragmentDirections.browseToTracks(album))
}
}
}
package audio.funkwhale.ffa.fragments
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.RecyclerView
import androidx.transition.Fade
import androidx.transition.Slide
import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.activities.MainActivity
import audio.funkwhale.ffa.adapters.ArtistsAdapter
import audio.funkwhale.ffa.databinding.FragmentArtistsBinding
import audio.funkwhale.ffa.model.Artist
import audio.funkwhale.ffa.repositories.ArtistsRepository
import audio.funkwhale.ffa.utils.AppContext
import audio.funkwhale.ffa.utils.onViewPager
class ArtistsFragment : FFAFragment<Artist, ArtistsAdapter>() {
private var _binding: FragmentArtistsBinding? = null
private val binding get() = _binding!!
......@@ -50,49 +40,9 @@ class ArtistsFragment : FFAFragment<Artist, ArtistsAdapter>() {
_binding = null
}
companion object {
fun openAlbums(
context: Context?,
artist: Artist,
fragment: Fragment? = null,
art: String? = null
) {
(context as? MainActivity)?.let {
fragment?.let { fragment ->
fragment.onViewPager {
exitTransition = Fade().apply {
duration = AppContext.TRANSITION_DURATION
interpolator = AccelerateDecelerateInterpolator()
view?.let {
addTarget(it)
}
}
}
}
}
(context as? AppCompatActivity)?.let { activity ->
val nextFragment = AlbumsFragment.new(artist, art).apply {
enterTransition = Slide().apply {
duration = AppContext.TRANSITION_DURATION
interpolator = AccelerateDecelerateInterpolator()
}
}
activity.supportFragmentManager
.beginTransaction()
.replace(R.id.container, nextFragment)
.addToBackStack(null)
.commit()
}
}
}
inner class OnArtistClickListener : ArtistsAdapter.OnArtistClickListener {
override fun onClick(holder: View?, artist: Artist) {
openAlbums(context, artist, fragment = this@ArtistsFragment)
findNavController().navigate(BrowseFragmentDirections.browseToAlbums(artist))
}
}
}
......@@ -7,19 +7,13 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment
import audio.funkwhale.ffa.adapters.BrowseTabsAdapter
import audio.funkwhale.ffa.databinding.FragmentBrowseBinding
import com.google.android.material.tabs.TabLayoutMediator
class BrowseFragment : Fragment() {
private var _binding: FragmentBrowseBinding? = null
private val binding get() = _binding!!
private var adapter: BrowseTabsAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter = BrowseTabsAdapter(this, childFragmentManager)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
......@@ -27,11 +21,14 @@ class BrowseFragment : Fragment() {
): View {
_binding = FragmentBrowseBinding.inflate(inflater)
return binding.root.apply {
binding.tabs.setupWithViewPager(binding.pager)
binding.tabs.getTabAt(0)?.select()
val adapter = BrowseTabsAdapter(this@BrowseFragment)
binding.pager.adapter = adapter
binding.pager.offscreenPageLimit = 3
TabLayoutMediator(binding.tabs, binding.pager) { tab, position ->
tab.text = adapter.tabText(position)
}.attach()
}
}
......@@ -39,8 +36,4 @@ class BrowseFragment : Fragment() {
super.onDestroyView()
_binding = null
}
fun selectTabAt(position: Int) {
binding.tabs.getTabAt(position)?.select()
}
}
......@@ -10,17 +10,34 @@ import androidx.recyclerview.widget.SimpleItemAnimator
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import audio.funkwhale.ffa.repositories.HttpUpstream
import audio.funkwhale.ffa.repositories.Repository
import audio.funkwhale.ffa.utils.*
import audio.funkwhale.ffa.utils.Event
import audio.funkwhale.ffa.utils.EventBus
import audio.funkwhale.ffa.utils.FFACache
import audio.funkwhale.ffa.utils.untilNetwork
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
abstract class FFAAdapter<D, VH : RecyclerView.ViewHolder> : RecyclerView.Adapter<VH>() {
var data: MutableList<D> = mutableListOf()
private var unfilteredData: MutableList<D> = mutableListOf()
fun getUnfilteredData(): MutableList<D> {
return unfilteredData
}
fun setUnfilteredData(data: MutableList<D>) {
unfilteredData = data
applyFilter()
}
open fun applyFilter() {
data.clear()
data.addAll(unfilteredData)
}
init {
super.setHasStableIds(true)
......@@ -29,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
}
......@@ -45,6 +62,8 @@ abstract class FFAFragment<D : Any, A : FFAAdapter<D, *>>() : Fragment() {
private var moreLoading = false
private var listener: Job? = null
fun <T> repository() = repository as T
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
......@@ -125,19 +144,20 @@ abstract class FFAFragment<D : Any, A : FFAAdapter<D, *>>() : Fragment() {
if (isCache) {
moreLoading = false
adapter.data = data.toMutableList()
adapter.setUnfilteredData(data.toMutableList())
adapter.notifyDataSetChanged()
return@launch
}
if (first) {
adapter.data.clear()
adapter.getUnfilteredData().clear()
}
onDataFetched(data)
adapter.data.addAll(data)
adapter.getUnfilteredData().addAll(data)
adapter.applyFilter()
withContext(IO) {
try {
......@@ -145,7 +165,7 @@ abstract class FFAFragment<D : Any, A : FFAAdapter<D, *>>() : Fragment() {
FFACache.set(
context,
cacheId,
Gson().toJson(repository.cache(adapter.data)).toByteArray()
Gson().toJson(repository.cache(adapter.getUnfilteredData())).toString()
)
}
} catch (e: ConcurrentModificationException) {
......@@ -156,7 +176,7 @@ abstract class FFAFragment<D : Any, A : FFAAdapter<D, *>>() : Fragment() {
(repository.upstream as? HttpUpstream<*, *>)?.let { upstream ->
if (!isCache && upstream.behavior == HttpUpstream.Behavior.Progressive) {
if (first || needsMoreOffscreenPages()) {
fetch(Repository.Origin.Network.origin, adapter.data.size)
fetch(Repository.Origin.Network.origin, adapter.getUnfilteredData().size)
} else {
moreLoading = false
}
......
package audio.funkwhale.ffa.fragments
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
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
import audio.funkwhale.ffa.utils.*
import audio.funkwhale.ffa.utils.Command
import audio.funkwhale.ffa.utils.CommandBus
import audio.funkwhale.ffa.utils.Event
import audio.funkwhale.ffa.utils.EventBus
import audio.funkwhale.ffa.utils.Request
import audio.funkwhale.ffa.utils.RequestBus
import audio.funkwhale.ffa.utils.Response
import audio.funkwhale.ffa.utils.getMetadata
import audio.funkwhale.ffa.utils.wait
import com.google.android.exoplayer2.offline.Download
import com.google.android.exoplayer2.offline.DownloadManager
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.flow.collect
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)
......@@ -33,8 +44,8 @@ class FavoritesFragment : FFAFragment<Track, FavoritesAdapter>() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter = FavoritesAdapter(layoutInflater, context, FavoriteListener())
repository = FavoritesRepository(context)
adapter = FavoritesAdapter(layoutInflater, context, FavoriteListener(repository()))
watchEventBus()
}
......@@ -45,6 +56,20 @@ class FavoritesFragment : FFAFragment<Track, FavoritesAdapter>() {
): View {
_binding = FragmentFavoritesBinding.inflate(inflater)
swiper = binding.swiper
binding.filterTracks.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable) {
adapter.applyFilter()
adapter.notifyDataSetChanged()
}
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()
}
})
return binding.root
}
......@@ -64,41 +89,46 @@ class FavoritesFragment : FFAFragment<Track, FavoritesAdapter>() {
}
}
refreshFavoritedTracks()
refreshDownloadedTracks()
}
binding.play.setOnClickListener {
CommandBus.send(Command.ReplaceQueue(adapter.data.shuffled()))
CommandBus.send(Command.ReplaceQueue(adapter.data.map { it.track }.shuffled()))
}
}
private fun watchEventBus() {
lifecycleScope.launch(Main) {
EventBus.get().collect { message ->
when (message) {
is Event.DownloadChanged -> refreshDownloadedTrack(message.download)
}
EventBus.get().collect { event ->
if (event is Event.DownloadChanged) refreshDownloadedTrack(event.download)
}
}
lifecycleScope.launch(Main) {
CommandBus.get().collect { command ->
when (command) {
is Command.RefreshTrack -> refreshCurrentTrack(command.track)
if (command is Command.RefreshTrack) refreshCurrentTrack(command.track)
}
}
}
private fun refreshFavoritedTracks() {
lifecycleScope.launch(Main) {
update()
}
}
private suspend fun refreshDownloadedTracks() {
val downloaded = TracksRepository.getDownloadedIds(exoDownloadManager) ?: listOf()
withContext(Main) {
adapter.data = adapter.data.map {
it.downloaded = downloaded.contains(it.id)
val data = adapter.data.map {
it.track.downloaded = downloaded.contains(it.id)
it
}.toMutableList()
adapter.setUnfilteredData(data)
adapter.notifyDataSetChanged()
}
}
......@@ -109,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)
}
}
......@@ -126,15 +156,4 @@ class FavoritesFragment : FFAFragment<Track, FavoritesAdapter>() {
adapter.notifyDataSetChanged()
}
}
inner class FavoriteListener : FavoritesAdapter.OnFavoriteListener {
override fun onToggleFavorite(id: Int, state: Boolean) {
(repository as? FavoritesRepository)?.let { repository ->
when (state) {
true -> repository.addFavorite(id)
false -> repository.deleteFavorite(id)
}
}
}
}
}
......@@ -7,8 +7,10 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import audio.funkwhale.ffa.adapters.FavoriteListener
import audio.funkwhale.ffa.adapters.TracksAdapter
import audio.funkwhale.ffa.databinding.PartialQueueBinding
import audio.funkwhale.ffa.repositories.FavoritesRepository
import audio.funkwhale.ffa.utils.Command
import audio.funkwhale.ffa.utils.CommandBus
import audio.funkwhale.ffa.utils.Event
......@@ -26,11 +28,15 @@ class LandscapeQueueFragment : Fragment() {
private var _binding: PartialQueueBinding? = null
private val binding get() = _binding!!
lateinit var favoritesRepository: FavoritesRepository
private var adapter: TracksAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
favoritesRepository = FavoritesRepository(context)
watchEventBus()
}
......@@ -40,8 +46,14 @@ class LandscapeQueueFragment : Fragment() {
savedInstanceState: Bundle?
): View {
_binding = PartialQueueBinding.inflate(inflater)
return binding.root.apply {
adapter = TracksAdapter(layoutInflater, context, fromQueue = true).also {
adapter = TracksAdapter(
layoutInflater,
context,
fromQueue = true,
favoriteListener = FavoriteListener(favoritesRepository)
).also {
binding.queue.layoutManager = LinearLayoutManager(context)
binding.queue.adapter = it
}
......@@ -80,7 +92,7 @@ class LandscapeQueueFragment : Fragment() {
activity?.lifecycleScope?.launch(Main) {
RequestBus.send(Request.GetQueue).wait<Response.Queue>()?.let { response ->
adapter?.let {
it.data = response.queue.toMutableList()
it.setUnfilteredData(response.queue.toMutableList())
it.notifyDataSetChanged()
if (it.data.isEmpty()) {
......@@ -98,17 +110,13 @@ class LandscapeQueueFragment : Fragment() {
private fun watchEventBus() {
activity?.lifecycleScope?.launch(Main) {
EventBus.get().collect { message ->
when (message) {
is Event.QueueChanged -> refresh()
}
if (message is Event.QueueChanged) refresh()
}
}
activity?.lifecycleScope?.launch(Main) {
CommandBus.get().collect { command ->
when (command) {
is Command.RefreshTrack -> refresh()
}
if (command is Command.RefreshTrack) refresh()
}
}
}
......
package audio.funkwhale.ffa.fragments
import android.os.Bundle
import android.util.Log
import android.view.Gravity
import android.view.View
import android.widget.SeekBar
import android.widget.SeekBar.OnSeekBarChangeListener
import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.liveData
import androidx.lifecycle.map
import androidx.navigation.fragment.findNavController
import audio.funkwhale.ffa.MainNavDirections
import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.databinding.FragmentNowPlayingBinding
import audio.funkwhale.ffa.model.Track
import audio.funkwhale.ffa.repositories.FavoritedRepository
import audio.funkwhale.ffa.repositories.FavoritesRepository
import audio.funkwhale.ffa.repositories.Repository
import audio.funkwhale.ffa.utils.Command
import audio.funkwhale.ffa.utils.CommandBus
import audio.funkwhale.ffa.utils.CoverArt
import audio.funkwhale.ffa.utils.Event
import audio.funkwhale.ffa.utils.EventBus
import audio.funkwhale.ffa.utils.FFACache
import audio.funkwhale.ffa.utils.ProgressBus
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
import audio.funkwhale.ffa.utils.toIntOrElse
import audio.funkwhale.ffa.utils.untilNetwork
import audio.funkwhale.ffa.viewmodel.NowPlayingViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.lang.Float.max
class NowPlayingFragment: Fragment(R.layout.fragment_now_playing) {
private val binding by lazy { FragmentNowPlayingBinding.bind(requireView()) }
private val viewModel by viewModels<NowPlayingViewModel>()
private val favoriteRepository by lazy { FavoritesRepository(requireContext()) }
private val favoritedRepository by lazy { FavoritedRepository(requireContext()) }
private var onDetailsMenuItemClickedCb: () -> Unit = {}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.lifecycleOwner = viewLifecycleOwner
viewModel.currentTrack.distinctUntilChanged().observe(viewLifecycleOwner, ::onTrackChange)
with(binding.controls) {
currentTrackTitle = viewModel.currentTrackTitle
currentTrackArtist = viewModel.currentTrackArtist
isCurrentTrackFavorite = viewModel.isCurrentTrackFavorite
repeatModeResource = viewModel.repeatModeResource
repeatModeAlpha = viewModel.repeatModeAlpha
currentProgressText = viewModel.currentProgressText
currentDurationText = viewModel.currentDurationText
isPlaying = viewModel.isPlaying
progress = viewModel.progress
nowPlayingDetailsPrevious.setOnClickListener {
CommandBus.send(Command.PreviousTrack)
}
nowPlayingDetailsNext.setOnClickListener {
CommandBus.send(Command.NextTrack)
}
nowPlayingDetailsToggle.setOnClickListener {
CommandBus.send(Command.ToggleState)
}
nowPlayingDetailsRepeat.setOnClickListener { toggleRepeatMode() }
nowPlayingDetailsProgress.setOnSeekBarChangeListener(OnSeekBarChanged())
nowPlayingDetailsFavorite.setOnClickListener { onFavorite() }
nowPlayingDetailsAddToPlaylist.setOnClickListener { onAddToPlaylist() }
}
binding.nowPlayingDetailsInfo.setOnClickListener { openInfoMenu() }
with(binding.header) {
lifecycleOwner = viewLifecycleOwner
isBuffering = viewModel.isBuffering
isPlaying = viewModel.isPlaying
progress = viewModel.progress
currentTrackTitle = viewModel.currentTrackTitle
currentTrackArtist = viewModel.currentTrackArtist
nowPlayingNext.setOnClickListener {
CommandBus.send(Command.NextTrack)
}
nowPlayingToggle.setOnClickListener {
CommandBus.send(Command.ToggleState)
}
}
lifecycleScope.launch(Dispatchers.Main) {
CommandBus.get().collect { onCommand(it) }
}
lifecycleScope.launch(Dispatchers.Main) {
ProgressBus.get().collect { onProgress(it) }
}
}
fun onBottomSheetDrag(value: Float) {
binding.nowPlayingRoot.progress = max(value, 0f)
}
fun onDetailsMenuItemClicked(cb: () -> Unit) {
onDetailsMenuItemClickedCb = cb
}
private fun toggleRepeatMode() {
val cachedRepeatMode = FFACache.getLine(requireContext(), "repeat").toIntOrElse(0)
val iteratedRepeatMode = (cachedRepeatMode + 1) % 3
FFACache.set(requireContext(), "repeat", "$iteratedRepeatMode")
CommandBus.send(Command.SetRepeatMode(iteratedRepeatMode))
}
private fun onAddToPlaylist() {
val currentTrack = viewModel.currentTrack.value ?: return
CommandBus.send(Command.AddToPlaylist(listOf(currentTrack)))
}
private fun onCommand(command: Command) = when (command) {
is Command.RefreshTrack -> refreshCurrentTrack(command.track)
is Command.SetRepeatMode -> viewModel.repeatMode.postValue(command.mode)
else -> {}
}
private fun onFavorite() {
val currentTrack = viewModel.currentTrack.value ?: return
if (currentTrack.favorite) favoriteRepository.deleteFavorite(currentTrack.id)
else favoriteRepository.addFavorite(currentTrack.id)
currentTrack.favorite = !currentTrack.favorite
// Trigger UI refresh
viewModel.currentTrack.postValue(viewModel.currentTrack.value)
favoritedRepository.fetch(Repository.Origin.Network.origin)
}
private fun onProgress(state: Triple<Int, Int, Int>) {
val (current, duration, percent) = state
val currentMins = (current / 1000) / 60
val currentSecs = (current / 1000) % 60
val durationMins = duration / 60
val durationSecs = duration % 60
viewModel.progress.postValue(percent)
viewModel.currentProgressText.postValue("%02d:%02d".format(currentMins, currentSecs))
viewModel.currentDurationText.postValue("%02d:%02d".format(durationMins, durationSecs))
}
private fun onTrackChange(track: Track?) {
if (track == null) {
binding.header.nowPlayingCover.setImageResource(R.drawable.cover)
return
}
CoverArt.requestCreator(maybeNormalizeUrl(track.album?.cover()))
.into(binding.header.nowPlayingCover)
}
private fun openInfoMenu() {
val currentTrack = viewModel.currentTrack.value ?: return
PopupMenu(
requireContext(),
binding.nowPlayingDetailsInfo,
Gravity.START,
R.attr.actionOverflowMenuStyle,
0
).apply {
inflate(R.menu.track_info)
setOnMenuItemClickListener {
onDetailsMenuItemClickedCb()
when (it.itemId) {
R.id.track_info_artist -> findNavController().navigate(
MainNavDirections.globalBrowseToAlbums(
currentTrack.artist,
currentTrack.album?.cover()
)
)
R.id.track_info_album -> currentTrack.album?.let { album ->
findNavController().navigate(MainNavDirections.globalBrowseTracks(album))
}
R.id.track_info_details -> TrackInfoDetailsFragment.new(currentTrack).show(
requireActivity().supportFragmentManager, "dialog"
)
}
true
}
show()
}
}
private fun refreshCurrentTrack(track: Track?) {
viewModel.currentTrack.postValue(track)
val cachedRepeatMode = FFACache.getLine(requireContext(), "repeat").toIntOrElse(0)
viewModel.repeatMode.postValue(cachedRepeatMode % 3)
// At this point, a non-null track is required
if (track == null) return
favoritedRepository.fetch().untilNetwork(lifecycleScope, Dispatchers.IO) { favorites, _, _, _ ->
lifecycleScope.launch(Dispatchers.Main) {
track.favorite = favorites.contains(track.id)
// Trigger UI refresh
viewModel.currentTrack.postValue(viewModel.currentTrack.value)
}
}
}
inner class OnSeekBarChanged : OnSeekBarChangeListener {
override fun onStopTrackingTouch(view: SeekBar?) {}
override fun onStartTrackingTouch(view: SeekBar?) {}
override fun onProgressChanged(view: SeekBar?, progress: Int, fromUser: Boolean) {
if (fromUser) {
CommandBus.send(Command.Seek(progress))
}
}
}
}
......@@ -6,68 +6,56 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
import androidx.core.os.bundleOf
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.RecyclerView
import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.adapters.FavoriteListener
import audio.funkwhale.ffa.adapters.PlaylistTracksAdapter
import audio.funkwhale.ffa.databinding.FragmentTracksBinding
import audio.funkwhale.ffa.model.Playlist
import audio.funkwhale.ffa.model.PlaylistTrack
import audio.funkwhale.ffa.model.Track
import audio.funkwhale.ffa.repositories.FavoritesRepository
import audio.funkwhale.ffa.repositories.ManagementPlaylistsRepository
import audio.funkwhale.ffa.repositories.PlaylistTracksRepository
import audio.funkwhale.ffa.utils.*
import com.squareup.picasso.Picasso
import audio.funkwhale.ffa.utils.Command
import audio.funkwhale.ffa.utils.CommandBus
import audio.funkwhale.ffa.utils.CoverArt
import audio.funkwhale.ffa.utils.Request
import audio.funkwhale.ffa.utils.RequestBus
import audio.funkwhale.ffa.utils.Response
import audio.funkwhale.ffa.utils.maybeNormalizeUrl
import audio.funkwhale.ffa.utils.toast
import audio.funkwhale.ffa.utils.wait
import jp.wasabeef.picasso.transformations.RoundedCornersTransformation
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
class PlaylistTracksFragment : FFAFragment<PlaylistTrack, PlaylistTracksAdapter>() {
override val recycler: RecyclerView get() = binding.tracks
private val args by navArgs<PlaylistTracksFragmentArgs>()
private var _binding: FragmentTracksBinding? = null
private val binding get() = _binding!!
lateinit var favoritesRepository: FavoritesRepository
lateinit var playlistsRepository: ManagementPlaylistsRepository
var albumId = 0
var albumArtist = ""
var albumTitle = ""
var albumCover = ""
companion object {
fun new(playlist: Playlist): PlaylistTracksFragment {
return PlaylistTracksFragment().apply {
arguments = bundleOf(
"albumId" to playlist.id,
"albumArtist" to "N/A",
"albumTitle" to playlist.name,
"albumCover" to ""
)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.apply {
albumId = getInt("albumId")
albumArtist = getString("albumArtist") ?: ""
albumTitle = getString("albumTitle") ?: ""
albumCover = getString("albumCover") ?: ""
}
adapter = PlaylistTracksAdapter(layoutInflater, context, FavoriteListener(), PlaylistListener())
repository = PlaylistTracksRepository(context, albumId)
favoritesRepository = FavoritesRepository(context)
playlistsRepository = ManagementPlaylistsRepository(context)
adapter = PlaylistTracksAdapter(
layoutInflater,
context,
FavoriteListener(favoritesRepository),
PlaylistListener()
)
repository = PlaylistTracksRepository(context, args.playlist.id)
watchEventBus()
}
......@@ -92,8 +80,8 @@ class PlaylistTracksFragment : FFAFragment<PlaylistTrack, PlaylistTracksAdapter>
binding.cover.visibility = View.INVISIBLE
binding.covers.visibility = View.VISIBLE
binding.artist.text = "Playlist"
binding.title.text = albumTitle
binding.artist.text = getString(R.string.playlist)
binding.title.text = args.playlist.name
}
override fun onResume() {
......@@ -122,7 +110,6 @@ class PlaylistTracksFragment : FFAFragment<PlaylistTrack, PlaylistTracksAdapter>
binding.play.setOnClickListener {
CommandBus.send(Command.ReplaceQueue(adapter.data.map { it.track }.shuffled()))
context.toast("All tracks were added to your queue")
}
......@@ -158,7 +145,11 @@ class PlaylistTracksFragment : FFAFragment<PlaylistTrack, PlaylistTracksAdapter>
}
override fun onDataFetched(data: List<PlaylistTrack>) {
data.map { it.track.album }.toSet().map { it?.cover() }.take(4).forEachIndexed { index, url ->
data.map { it.track.album }
.toSet()
.map { it?.cover() }
.take(4)
.forEachIndexed { index, url ->
val imageView = when (index) {
0 -> binding.coverTopLeft
1 -> binding.coverTopRight
......@@ -176,8 +167,7 @@ class PlaylistTracksFragment : FFAFragment<PlaylistTrack, PlaylistTracksAdapter>
}
lifecycleScope.launch(Main) {
Picasso.get()
.maybeLoad(maybeNormalizeUrl(url))
CoverArt.requestCreator(maybeNormalizeUrl(url))
.fit()
.centerCrop()
.transform(RoundedCornersTransformation(16, 0, corner))
......@@ -189,8 +179,8 @@ class PlaylistTracksFragment : FFAFragment<PlaylistTrack, PlaylistTracksAdapter>
private fun watchEventBus() {
lifecycleScope.launch(Main) {
CommandBus.get().collect { command ->
when (command) {
is Command.RefreshTrack -> refreshCurrentTrack(command.track)
if (command is Command.RefreshTrack) {
refreshCurrentTrack(command.track)
}
}
}
......@@ -203,23 +193,14 @@ class PlaylistTracksFragment : FFAFragment<PlaylistTrack, PlaylistTracksAdapter>
}
}
inner class FavoriteListener : PlaylistTracksAdapter.OnFavoriteListener {
override fun onToggleFavorite(id: Int, state: Boolean) {
when (state) {
true -> favoritesRepository.addFavorite(id)
false -> favoritesRepository.deleteFavorite(id)
}
}
}
inner class PlaylistListener : PlaylistTracksAdapter.OnPlaylistListener {
override fun onMoveTrack(from: Int, to: Int) {
playlistsRepository.move(albumId, from, to)
playlistsRepository.move(args.playlist.id, from, to)
}
override fun onRemoveTrackFromPlaylist(track: Track, index: Int) {
lifecycleScope.launch(Main) {
playlistsRepository.remove(albumId, index)
playlistsRepository.remove(args.playlist.id, index)
update()
}
}
......
......@@ -4,17 +4,12 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.RecyclerView
import androidx.transition.Fade
import androidx.transition.Slide
import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.activities.MainActivity
import audio.funkwhale.ffa.adapters.PlaylistsAdapter
import audio.funkwhale.ffa.databinding.FragmentPlaylistsBinding
import audio.funkwhale.ffa.repositories.PlaylistsRepository
import audio.funkwhale.ffa.utils.AppContext
import audio.funkwhale.ffa.model.Playlist
import audio.funkwhale.ffa.repositories.PlaylistsRepository
class PlaylistsFragment : FFAFragment<Playlist, PlaylistsAdapter>() {
......@@ -48,29 +43,7 @@ class PlaylistsFragment : FFAFragment<Playlist, PlaylistsAdapter>() {
inner class OnPlaylistClickListener : PlaylistsAdapter.OnPlaylistClickListener {
override fun onClick(holder: View?, playlist: Playlist) {
(context as? MainActivity)?.let { activity ->
exitTransition = Fade().apply {
duration = AppContext.TRANSITION_DURATION
interpolator = AccelerateDecelerateInterpolator()
view?.let {
addTarget(it)
}
}
val fragment = PlaylistTracksFragment.new(playlist).apply {
enterTransition = Slide().apply {
duration = AppContext.TRANSITION_DURATION
interpolator = AccelerateDecelerateInterpolator()
}
}
activity.supportFragmentManager
.beginTransaction()
.replace(R.id.container, fragment)
.addToBackStack(null)
.commit()
}
findNavController().navigate(BrowseFragmentDirections.browseToPlaylistTracks(playlist))
}
}
}
......@@ -9,6 +9,7 @@ import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import audio.funkwhale.ffa.R
import audio.funkwhale.ffa.adapters.FavoriteListener
import audio.funkwhale.ffa.adapters.TracksAdapter
import audio.funkwhale.ffa.databinding.FragmentQueueBinding
import audio.funkwhale.ffa.repositories.FavoritesRepository
......@@ -49,7 +50,9 @@ class QueueFragment : BottomSheetDialogFragment() {
return super.onCreateDialog(savedInstanceState).apply {
setOnShowListener {
findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)?.let {
BottomSheetBehavior.from(it).skipCollapsed = true
val behavior = BottomSheetBehavior.from(it)
behavior.skipCollapsed = true
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
}
}
......@@ -62,7 +65,12 @@ class QueueFragment : BottomSheetDialogFragment() {
): View {
_binding = FragmentQueueBinding.inflate(inflater)
return binding.root.apply {
adapter = TracksAdapter(layoutInflater, context, FavoriteListener(), fromQueue = true).also {
adapter = TracksAdapter(
layoutInflater,
context,
FavoriteListener(favoritesRepository),
fromQueue = true
).also {
binding.included.queue.layoutManager = LinearLayoutManager(context)
binding.included.queue.adapter = it
}
......@@ -94,15 +102,15 @@ class QueueFragment : BottomSheetDialogFragment() {
CommandBus.send(Command.ClearQueue)
}
refresh()
refresh(true)
}
private fun refresh() {
private fun refresh(scroll: Boolean) {
lifecycleScope.launch(Main) {
RequestBus.send(Request.GetQueue).wait<Response.Queue>()?.let { response ->
binding.included.let { included ->
adapter?.let {
it.data = response.queue.toMutableList()
it.setUnfilteredData(response.queue.toMutableList())
it.notifyDataSetChanged()
if (it.data.isEmpty()) {
......@@ -114,6 +122,11 @@ class QueueFragment : BottomSheetDialogFragment() {
}
}
}
if (scroll) {
RequestBus.send(Request.GetCurrentTrackIndex).wait<Response.CurrentTrackIndex>()?.let { sresp ->
binding.included.queue.scrollToPosition(sresp.index)
}
}
}
}
}
......@@ -121,27 +134,18 @@ class QueueFragment : BottomSheetDialogFragment() {
private fun watchEventBus() {
lifecycleScope.launch(Main) {
EventBus.get().collect { message ->
when (message) {
is Event.QueueChanged -> refresh()
if (message is Event.QueueChanged) {
refresh(false)
}
}
}
lifecycleScope.launch(Main) {
CommandBus.get().collect { command ->
when (command) {
is Command.RefreshTrack -> refresh()
}
if (command is Command.RefreshTrack) {
refresh(false)
}
}
}
inner class FavoriteListener : TracksAdapter.OnFavoriteListener {
override fun onToggleFavorite(id: Int, state: Boolean) {
when (state) {
true -> favoritesRepository.addFavorite(id)
false -> favoritesRepository.deleteFavorite(id)
}
}
}
}