Re-add playing indicator to AlbumDetailFragment

Re-add the playing indicators to AlbumDetailFragment, while removing them from SongsFragment as I personally dont think they work there.
This commit is contained in:
OxygenCobalt 2020-12-30 13:43:52 -07:00
parent fef8d4146e
commit 85a8241976
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
8 changed files with 124 additions and 109 deletions

View file

@ -16,6 +16,7 @@ import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.recycler.CenterSmoothScroller
import org.oxycblt.auxio.recycler.Highlightable
import org.oxycblt.auxio.ui.createToast
import org.oxycblt.auxio.ui.setupAlbumSongActions
@ -87,7 +88,7 @@ class AlbumDetailFragment : DetailFragment() {
setupRecycler(detailAdapter)
// -- VIEWMODEL SETUP ---
// -- DETAILVIEWMODEL SETUP ---
detailModel.albumSortMode.observe(viewLifecycleOwner) { mode ->
logD("Updating sort mode to $mode")
@ -129,6 +130,51 @@ class AlbumDetailFragment : DetailFragment() {
}
}
// --- PLAYBACKVIEWMODEL SETUP ---
playbackModel.song.observe(viewLifecycleOwner) { song ->
if (playbackModel.mode.value == PlaybackMode.IN_ALBUM &&
playbackModel.parent.value!!.id == detailModel.currentAlbum.value!!.id
) {
detailAdapter.setCurrentSong(song)
lastHolder?.setHighlighted(false)
lastHolder = null
if (song != null) {
// Use existing data instead of having to re-sort it.
val pos = detailAdapter.currentList.indexOfFirst {
it.name == song.name
}
// Check if the ViewHolder for this song is visible, if it is then highlight it.
// If the ViewHolder is not visible, then the adapter should take care of it if it does become visible.
binding.detailRecycler.layoutManager?.findViewByPosition(pos)?.let { child ->
binding.detailRecycler.getChildViewHolder(child)?.let {
lastHolder = it as Highlightable
lastHolder?.setHighlighted(true)
}
}
}
} else {
// Clear the viewholders if the mode isn't ALL_SONGS
detailAdapter.setCurrentSong(null)
lastHolder?.setHighlighted(false)
lastHolder = null
}
}
playbackModel.isInUserQueue.observe(viewLifecycleOwner) {
if (it) {
// Remove any highlighted ViewHolders if the playback is in the user queue.
detailAdapter.setCurrentSong(null)
lastHolder?.setHighlighted(false)
lastHolder = null
}
}
logD("Fragment created.")
return binding.root

View file

@ -13,6 +13,7 @@ import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.FragmentDetailBinding
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.recycler.Highlightable
import org.oxycblt.auxio.ui.isLandscape
import org.oxycblt.auxio.ui.memberBinding
@ -28,6 +29,7 @@ abstract class DetailFragment : Fragment() {
protected val binding: FragmentDetailBinding by memberBinding(
FragmentDetailBinding::inflate
)
protected var lastHolder: Highlightable? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
@ -45,6 +47,12 @@ abstract class DetailFragment : Fragment() {
callback.isEnabled = false
}
override fun onDestroyView() {
super.onDestroyView()
lastHolder = null
}
/**
* Shortcut method for doing setup of the detail toolbar.
*/

View file

@ -13,8 +13,11 @@ import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.BaseModel
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.recycler.DiffCallback
import org.oxycblt.auxio.recycler.Highlightable
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
import org.oxycblt.auxio.ui.accent
import org.oxycblt.auxio.ui.disable
import org.oxycblt.auxio.ui.setTextColorResource
/**
* An adapter for displaying the details and [Song]s of an [Album]
@ -25,6 +28,10 @@ class AlbumDetailAdapter(
private val doOnClick: (data: Song) -> Unit,
private val doOnLongClick: (data: Song, view: View) -> Unit
) : ListAdapter<BaseModel, RecyclerView.ViewHolder>(DiffCallback()) {
private var currentSong: Song? = null
private var lastHolder: Highlightable? = null
override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
is Album -> ALBUM_HEADER_ITEM_TYPE
@ -51,6 +58,30 @@ class AlbumDetailAdapter(
is Album -> (holder as AlbumHeaderViewHolder).bind(item)
is Song -> (holder as AlbumSongViewHolder).bind(item)
}
if (currentSong != null && position > 0) {
if (getItem(position).id == currentSong?.id) {
// Reset the last ViewHolder before assigning the new, correct one to be highlighted
lastHolder?.setHighlighted(false)
lastHolder = (holder as Highlightable)
holder.setHighlighted(true)
} else {
(holder as Highlightable).setHighlighted(false)
}
}
}
/**
* Update the current song that this adapter should be watching for to highlight.
* @param song The [Song] to highlight if found, null to clear any highlighted ViewHolders
*/
fun setCurrentSong(song: Song?) {
// Clear out the last ViewHolder as a song update usually signifies that this current
// ViewHolder is likely invalid.
lastHolder?.setHighlighted(false)
lastHolder = null
currentSong = song
}
inner class AlbumHeaderViewHolder(
@ -70,12 +101,25 @@ class AlbumDetailAdapter(
inner class AlbumSongViewHolder(
private val binding: ItemAlbumSongBinding,
) : BaseViewHolder<Song>(binding, doOnClick, doOnLongClick) {
) : BaseViewHolder<Song>(binding, doOnClick, doOnLongClick), Highlightable {
private val normalTextColor = binding.songName.currentTextColor
private val inactiveTextColor = binding.songTrack.currentTextColor
override fun onBind(data: Song) {
binding.song = data
binding.songName.requestLayout()
}
override fun setHighlighted(isHighlighted: Boolean) {
if (isHighlighted) {
binding.songName.setTextColorResource(accent.first)
binding.songTrack.setTextColorResource(accent.first)
} else {
binding.songName.setTextColor(normalTextColor)
binding.songTrack.setTextColor(inactiveTextColor)
}
}
}
companion object {

View file

@ -78,10 +78,8 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
if (it.itemId != R.id.action_search) {
libraryModel.updateSortMode(it.itemId)
} else {
// Do whatever this is in order to make the SearchView focusable.
(it.actionView as SearchView).isIconified = false
// Then also do a basic animation
// Then also do a basic animation on the enter transition. Not done on exit
// because that causes issues with the SearchView.
TransitionManager.beginDelayedTransition(
binding.libraryToolbar, Fade()
)
@ -98,14 +96,12 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
searchView.queryHint = getString(R.string.hint_search_library)
searchView.maxWidth = Int.MAX_VALUE
searchView.setOnQueryTextListener(this@LibraryFragment)
searchView.setOnQueryTextFocusChangeListener { _, hasFocus ->
item.isVisible = !hasFocus
}
item.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
binding.libraryRecycler.adapter = searchAdapter
setGroupVisible(R.id.group_sorting, false)
item.isVisible = false
libraryModel.resetQuery()
@ -115,6 +111,7 @@ class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
binding.libraryRecycler.adapter = libraryAdapter
setGroupVisible(R.id.group_sorting, true)
item.isVisible = true
libraryModel.resetQuery()

View file

@ -47,10 +47,20 @@ data class Song(
}
}
/**
* Apply a genre to a song.
* @throws IllegalArgumentException When a genre is already applied.
*/
fun applyGenre(genre: Genre) {
check(mGenre == null) { "Genre is already applied" }
mGenre = genre
}
/**
* Apply an album to a song.
* @throws IllegalArgumentException When a album is already applied.
*/
fun applyAlbum(album: Album) {
check(mAlbum == null) { "Album is already applied" }

View file

@ -26,7 +26,9 @@ import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.audio.AudioAttributes
import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -345,7 +347,11 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca
)
}
return SimpleExoPlayer.Builder(this, audioRenderer).build()
val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)
return SimpleExoPlayer.Builder(this, audioRenderer)
.setMediaSourceFactory(DefaultMediaSourceFactory(this, extractorsFactory))
.build()
}
/**

View file

@ -1,15 +1,10 @@
package org.oxycblt.auxio.songs
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.databinding.ItemSongBinding
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.recycler.Highlightable
import org.oxycblt.auxio.recycler.viewholders.BaseViewHolder
import org.oxycblt.auxio.ui.accent
import org.oxycblt.auxio.ui.setTextColorResource
import org.oxycblt.auxio.recycler.viewholders.SongViewHolder
/**
* The adapter for [SongsFragment], shows basic songs without durations.
@ -21,61 +16,15 @@ class SongsAdapter(
private val data: List<Song>,
private val doOnClick: (data: Song) -> Unit,
private val doOnLongClick: (data: Song, view: View) -> Unit
) : RecyclerView.Adapter<SongsAdapter.SongViewHolder>() {
/* private var currentSong: Song? = null
private var lastHolder: Highlightable? = null*/
) : RecyclerView.Adapter<SongViewHolder>() {
override fun getItemCount(): Int = data.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SongViewHolder {
return SongViewHolder(
ItemSongBinding.inflate(LayoutInflater.from(parent.context))
)
return SongViewHolder.from(parent.context, doOnClick, doOnLongClick)
}
override fun onBindViewHolder(holder: SongViewHolder, position: Int) {
holder.bind(data[position])
/* if (currentSong != null) {
if (data[position].id == currentSong?.id) {
// Reset the last ViewHolder before assigning the new, correct one to be highlighted
lastHolder?.setHighlighted(false)
lastHolder = holder
holder.setHighlighted(true)
} else {
holder.setHighlighted(false)
}
}*/
}
/* fun setCurrentSong(song: Song?) {
// Clear out the last ViewHolder as a song update usually signifies that this current
// ViewHolder is likely invalid.
lastHolder?.setHighlighted(false)
lastHolder = null
currentSong = song
}*/
inner class SongViewHolder(
private val binding: ItemSongBinding
) : BaseViewHolder<Song>(binding, doOnClick, doOnLongClick), Highlightable {
private val normalTextColor = binding.songName.currentTextColor
override fun onBind(data: Song) {
binding.song = data
binding.songName.requestLayout()
binding.songInfo.requestLayout()
}
override fun setHighlighted(isHighlighted: Boolean) {
if (isHighlighted) {
binding.songName.setTextColorResource(accent.first)
} else {
binding.songName.setTextColor(normalTextColor)
}
}
}
}

View file

@ -20,7 +20,6 @@ import org.oxycblt.auxio.detail.DetailViewModel
import org.oxycblt.auxio.logD
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.recycler.Highlightable
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.ui.accent
import org.oxycblt.auxio.ui.getLandscapeSpans
@ -70,8 +69,6 @@ class SongsFragment : Fragment() {
}
)
var lastHolder: Highlightable? = null
// --- UI SETUP ---
binding.songToolbar.apply {
@ -106,48 +103,6 @@ class SongsFragment : Fragment() {
// --- VIEWMODEL SETUP ---
/*
Unused, not needed for SongsFragment
TODO: Move this code over to AlbumDetailFragment
playbackModel.song.observe(viewLifecycleOwner) { song ->
if (playbackModel.mode.value == PlaybackMode.ALL_SONGS) {
songAdapter.setCurrentSong(song)
lastHolder?.setHighlighted(false)
lastHolder = null
if (song != null) {
val pos = musicStore.songs.indexOfFirst { it.id == song.id }
// Check if the ViewHolder for this song is visible, if it is then highlight it.
// If it isn't, SongsAdapter will take care of it when it is visible.
binding.songRecycler.layoutManager?.findViewByPosition(pos)?.let { child ->
binding.songRecycler.getChildViewHolder(child)?.let {
lastHolder = it as Highlightable
lastHolder?.setHighlighted(true)
}
}
}
} else {
// Clear the viewholders if the mode isnt ALL_SONGS
songAdapter.setCurrentSong(null)
lastHolder?.setHighlighted(false)
lastHolder = null
}
}
playbackModel.isInUserQueue.observe(viewLifecycleOwner) {
if (it) {
// Remove any highlighted ViewHolders if the playback is in the user queue.
songAdapter.setCurrentSong(null)
lastHolder?.setHighlighted(false)
lastHolder = null
}
}
*/
setupFastScroller(binding)
logD("Fragment created.")