diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt index 4eb9d4f56..e78957302 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -42,6 +42,8 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { private val mIsShuffling = MutableLiveData(false) private val mLoopMode = MutableLiveData(LoopMode.NONE) + private val mIsInUserQueue = MutableLiveData(false) + // Other private val mIsSeeking = MutableLiveData(false) private var mCanAnimate = false @@ -60,6 +62,8 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { /** The current [PlaybackMode] that also determines the queue */ val mode: LiveData get() = mMode + val isInUserQueue: LiveData = mIsInUserQueue + val isPlaying: LiveData get() = mIsPlaying val isShuffling: LiveData get() = mIsShuffling /** The current repeat mode, see [LoopMode] for more information */ @@ -390,4 +394,8 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { override fun onLoopUpdate(mode: LoopMode) { mLoopMode.value = mode } + + override fun onInUserQueueUpdate(isInUserQueue: Boolean) { + mIsInUserQueue.value = isInUserQueue + } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt index d8542fbfc..c65962223 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt @@ -86,6 +86,10 @@ class PlaybackStateManager private constructor() { callbacks.forEach { it.onLoopUpdate(value) } } private var mIsInUserQueue = false + set(value) { + field = value + callbacks.forEach { it.onInUserQueueUpdate(value) } + } private var mIsRestored = false private var mHasPlayed = false private var mShuffleSeed = -1L @@ -114,6 +118,8 @@ class PlaybackStateManager private constructor() { val isRestored: Boolean get() = mIsRestored /** Whether this instance has started playing or not */ val hasPlayed: Boolean get() = mHasPlayed + /** Whether playback is in the user queue or not */ + val isInUserQueue: Boolean get() = mIsInUserQueue private val settingsManager = SettingsManager.getInstance() @@ -239,17 +245,16 @@ class PlaybackStateManager private constructor() { /** * Shortcut function for updating what song is being played. ***USE THIS INSTEAD OF WRITING OUT ALL THE CODE YOURSELF!!!*** * @param song The song to play - * @param dontPlay (Optional, defaults to false) whether to not set [isPlaying] to true. */ private fun updatePlayback(song: Song) { + mIsInUserQueue = false + mSong = song mPosition = 0 if (!mIsPlaying) { setPlayingStatus(true) } - - mIsInUserQueue = false } /** @@ -846,6 +851,7 @@ class PlaybackStateManager private constructor() { fun onShuffleUpdate(isShuffling: Boolean) {} fun onLoopUpdate(mode: LoopMode) {} fun onSeekConfirm(position: Long) {} + fun onInUserQueueUpdate(isInUserQueue: Boolean) {} fun onRestoreFinish() {} } diff --git a/app/src/main/java/org/oxycblt/auxio/recycler/Highlightable.kt b/app/src/main/java/org/oxycblt/auxio/recycler/Highlightable.kt new file mode 100644 index 000000000..a3e3bcbec --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/recycler/Highlightable.kt @@ -0,0 +1,8 @@ +package org.oxycblt.auxio.recycler + +/** + * Interface that allows the highlighting of certain ViewHolders + */ +interface Highlightable { + fun setHighlighted(isHighlighted: Boolean) +} diff --git a/app/src/main/java/org/oxycblt/auxio/recycler/viewholders/ModelHolders.kt b/app/src/main/java/org/oxycblt/auxio/recycler/viewholders/ModelHolders.kt index c9e3eefc0..f6ef1d732 100644 --- a/app/src/main/java/org/oxycblt/auxio/recycler/viewholders/ModelHolders.kt +++ b/app/src/main/java/org/oxycblt/auxio/recycler/viewholders/ModelHolders.kt @@ -13,11 +13,6 @@ import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Header import org.oxycblt.auxio.music.Song -import org.oxycblt.auxio.recycler.viewholders.AlbumViewHolder.Companion.from -import org.oxycblt.auxio.recycler.viewholders.ArtistViewHolder.Companion.from -import org.oxycblt.auxio.recycler.viewholders.GenreViewHolder.Companion.from -import org.oxycblt.auxio.recycler.viewholders.HeaderViewHolder.Companion.from -import org.oxycblt.auxio.recycler.viewholders.SongViewHolder.Companion.from /* * A table of all ViewHolder codes. Please add to these so that all viewholder codes are unique. diff --git a/app/src/main/java/org/oxycblt/auxio/songs/SongsAdapter.kt b/app/src/main/java/org/oxycblt/auxio/songs/SongsAdapter.kt index f2d2c4419..c883aadae 100644 --- a/app/src/main/java/org/oxycblt/auxio/songs/SongsAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/songs/SongsAdapter.kt @@ -1,10 +1,15 @@ 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.viewholders.SongViewHolder +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 /** * The adapter for [SongsFragment], shows basic songs without durations. @@ -16,15 +21,61 @@ class SongsAdapter( private val data: List, private val doOnClick: (data: Song) -> Unit, private val doOnLongClick: (data: Song, view: View) -> Unit -) : RecyclerView.Adapter() { +) : RecyclerView.Adapter() { + + private var currentSong: Song? = null + private var lastHolder: Highlightable? = null override fun getItemCount(): Int = data.size override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SongViewHolder { - return SongViewHolder.from(parent.context, doOnClick, doOnLongClick) + return SongViewHolder( + ItemSongBinding.inflate(LayoutInflater.from(parent.context)) + ) } 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(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) + } + } } } diff --git a/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt b/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt index 8a5d9575c..5a0fd7beb 100644 --- a/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/songs/SongsFragment.kt @@ -20,6 +20,8 @@ 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.playback.state.PlaybackMode +import org.oxycblt.auxio.recycler.Highlightable import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.ui.accent import org.oxycblt.auxio.ui.getLandscapeSpans @@ -57,7 +59,9 @@ class SongsFragment : Fragment() { val songAdapter = SongsAdapter( musicStore.songs, - doOnClick = { playbackModel.playSong(it, settingsManager.songPlaybackMode) }, + doOnClick = { + playbackModel.playSong(it, settingsManager.songPlaybackMode) + }, doOnLongClick = { data, view -> PopupMenu(requireContext(), view).setupSongActions( requireContext(), data, playbackModel, detailModel @@ -65,6 +69,8 @@ class SongsFragment : Fragment() { } ) + var lastHolder: Highlightable? = null + // --- UI SETUP --- binding.songToolbar.apply { @@ -97,6 +103,40 @@ class SongsFragment : Fragment() { } } + // --- VIEWMODEL SETUP --- + + playbackModel.song.observe(viewLifecycleOwner) { song -> + if (playbackModel.mode.value == PlaybackMode.ALL_SONGS) { + logD(playbackModel.isInUserQueue.toString()) + 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) + } + } + } + } + } + + playbackModel.isInUserQueue.observe(viewLifecycleOwner) { + if (it) { + songAdapter.setCurrentSong(null) + lastHolder?.setHighlighted(false) + lastHolder = null + } + } + setupFastScroller(binding) logD("Fragment created.") diff --git a/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt b/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt index 05d0fe20d..144c779a0 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/InterfaceUtils.kt @@ -9,8 +9,11 @@ import android.text.Spanned import android.text.style.ForegroundColorSpan import android.view.MenuItem import android.widget.ImageButton +import android.widget.TextView import android.widget.Toast +import androidx.annotation.AttrRes import androidx.annotation.ColorInt +import androidx.annotation.ColorRes import androidx.annotation.MenuRes import androidx.appcompat.widget.PopupMenu import androidx.core.text.HtmlCompat @@ -81,6 +84,20 @@ fun Spanned.render(): Spanned { ) } +/** + * Set a [TextView] text color, without having to resolve the resource. + */ +fun TextView.setTextColorResource(@ColorRes color: Int) { + setTextColor(color.toColor(context)) +} + +/** + * Set a [TextView] text color, using an attr resource + */ +fun TextView.setTextColorAttr(@AttrRes attr: Int) { + setTextColor(resolveAttr(context, attr)) +} + /** * Show actions for a song item, such as the ones found in [org.oxycblt.auxio.songs.SongsFragment] * @param context [Context] required diff --git a/app/src/main/res/layout-land/item_artist_header.xml b/app/src/main/res/layout-land/item_artist_header.xml index 9faa2588e..61923ec06 100644 --- a/app/src/main/res/layout-land/item_artist_header.xml +++ b/app/src/main/res/layout-land/item_artist_header.xml @@ -1,7 +1,8 @@ + xmlns:tools="http://schemas.android.com/tools" + tools:context=".detail.adapters.ArtistDetailAdapter.ArtistHeaderViewHolder"> diff --git a/app/src/main/res/layout-land/item_genre_header.xml b/app/src/main/res/layout-land/item_genre_header.xml index 1ae2c3030..a93fcb5ee 100644 --- a/app/src/main/res/layout-land/item_genre_header.xml +++ b/app/src/main/res/layout-land/item_genre_header.xml @@ -1,7 +1,8 @@ + xmlns:tools="http://schemas.android.com/tools" + tools:context=".detail.adapters.GenreDetailAdapter.GenreHeaderViewHolder"> diff --git a/app/src/main/res/layout/fragment_about.xml b/app/src/main/res/layout/fragment_about.xml index 6ab1dbf4d..de61ba897 100644 --- a/app/src/main/res/layout/fragment_about.xml +++ b/app/src/main/res/layout/fragment_about.xml @@ -1,7 +1,8 @@ + xmlns:tools="http://schemas.android.com/tools" + tools:context=".settings.ui.AboutDialog"> + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + tools:context=".settings.SettingsFragment"> diff --git a/app/src/main/res/layout/item_accent.xml b/app/src/main/res/layout/item_accent.xml index 806bc47de..dfe49c589 100644 --- a/app/src/main/res/layout/item_accent.xml +++ b/app/src/main/res/layout/item_accent.xml @@ -1,6 +1,7 @@ + xmlns:tools="http://schemas.android.com/tools" + tools:context=".settings.ui.AccentAdapter.ViewHolder"> + tools:context=".detail.adapters.AlbumDetailAdapter.AlbumSongViewHolder"> diff --git a/app/src/main/res/layout/item_artist_album.xml b/app/src/main/res/layout/item_artist_album.xml index b29a76fa9..e61c220bd 100644 --- a/app/src/main/res/layout/item_artist_album.xml +++ b/app/src/main/res/layout/item_artist_album.xml @@ -2,7 +2,7 @@ + tools:context=".detail.adapters.ArtistDetailAdapter.ArtistAlbumViewHolder"> diff --git a/app/src/main/res/layout/item_artist_header.xml b/app/src/main/res/layout/item_artist_header.xml index 08a865f96..888769ec2 100644 --- a/app/src/main/res/layout/item_artist_header.xml +++ b/app/src/main/res/layout/item_artist_header.xml @@ -1,7 +1,8 @@ + xmlns:tools="http://schemas.android.com/tools" + tools:context=".detail.adapters.ArtistDetailAdapter.ArtistHeaderViewHolder"> diff --git a/app/src/main/res/layout/item_genre_header.xml b/app/src/main/res/layout/item_genre_header.xml index 1fa61f14c..15111a89e 100644 --- a/app/src/main/res/layout/item_genre_header.xml +++ b/app/src/main/res/layout/item_genre_header.xml @@ -1,7 +1,8 @@ + xmlns:tools="http://schemas.android.com/tools" + tools:context=".detail.adapters.GenreDetailAdapter.GenreHeaderViewHolder"> diff --git a/app/src/main/res/layout/item_genre_song.xml b/app/src/main/res/layout/item_genre_song.xml index 8088a2575..799b57877 100644 --- a/app/src/main/res/layout/item_genre_song.xml +++ b/app/src/main/res/layout/item_genre_song.xml @@ -2,7 +2,7 @@ + tools:context=".detail.adapters.GenreDetailAdapter.GenreSongViewHolder"> diff --git a/app/src/main/res/layout/item_queue_song.xml b/app/src/main/res/layout/item_queue_song.xml index 64119335a..c103fa24b 100644 --- a/app/src/main/res/layout/item_queue_song.xml +++ b/app/src/main/res/layout/item_queue_song.xml @@ -2,7 +2,7 @@ + tools:context=".playback.queue.QueueAdapter.QueueSongViewHolder"> diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 9fe5d61dc..ac498bb02 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -175,6 +175,7 @@ @style/Theme.BottomSheetHeightFix +