Add play button to AlbumDetailFragment

Add a play button to AlbumDetailFragment that shows the option to play the album, or pause/resume the playback if the album is already playing.
This commit is contained in:
OxygenCobalt 2020-10-14 10:10:57 -06:00
parent 9f05ce6e52
commit a64627c7cf
8 changed files with 185 additions and 12 deletions

View file

@ -5,6 +5,7 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
@ -48,10 +49,14 @@ class AlbumDetailFragment : Fragment() {
playbackModel.update(it, PlaybackMode.IN_ALBUM)
}
val playIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_play)
val pauseIcon = ContextCompat.getDrawable(requireContext(), R.drawable.ic_pause)
// --- UI SETUP ---
binding.lifecycleOwner = this
binding.detailModel = detailModel
binding.playbackModel = playbackModel
binding.album = detailModel.currentAlbum.value!!
binding.albumSongRecycler.apply {
@ -83,6 +88,17 @@ class AlbumDetailFragment : Fragment() {
)
}
// Observe playback model to update the play button
// TODO: Make these icons animated
// TODO: Shuffle button/option, unsure of which one
playbackModel.currentMode.observe(viewLifecycleOwner) {
updatePlayButton(it, binding)
}
playbackModel.isPlaying.observe(viewLifecycleOwner) {
updatePlayButton(playbackModel.currentMode.value!!, binding)
}
// If the album was shown directly from LibraryFragment, Then enable the ability to
// navigate upwards to the parent artist
if (args.enableParentNav) {
@ -107,4 +123,31 @@ class AlbumDetailFragment : Fragment() {
return binding.root
}
// Update the play button depending on the current playback status
// If the shown album is currently playing, set the button to the current isPlaying status and
// its behavior to modify the current playing status
// If the shown album isn't currently playing, set the button to "Play" and its behavior
// to start the playback of the album.
private fun updatePlayButton(mode: PlaybackMode, binding: FragmentAlbumDetailBinding) {
playbackModel.currentSong.value?.let { song ->
if (mode == PlaybackMode.IN_ALBUM && song.album == detailModel.currentAlbum.value) {
if (playbackModel.isPlaying.value!!) {
binding.albumPlay.setImageResource(R.drawable.ic_pause)
} else {
binding.albumPlay.setImageResource(R.drawable.ic_play)
}
binding.albumPlay.setOnClickListener {
playbackModel.invertPlayingStatus()
}
} else {
binding.albumPlay.setImageResource(R.drawable.ic_play)
binding.albumPlay.setOnClickListener {
playbackModel.play(detailModel.currentAlbum.value!!, false)
}
}
}
}
}

View file

@ -6,5 +6,13 @@ package org.oxycblt.auxio.playback
// IN_ARTIST -> Play from the songs of the artist
// IN_ALBUM -> Play from the songs of the album
enum class PlaybackMode {
ALL_SONGS, IN_GENRE, IN_ARTIST, IN_ALBUM
IN_GENRE, IN_ARTIST, IN_ALBUM, ALL_SONGS;
// Make a slice of all the values that this ShowMode covers.
// ex. SHOW_ARTISTS would return SHOW_ARTISTS, SHOW_ALBUMS, and SHOW_SONGS
fun getChildren(): List<PlaybackMode> {
val vals = values()
return vals.slice(vals.indexOf(this) until vals.size)
}
}

View file

@ -5,6 +5,9 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.toDuration
@ -25,6 +28,7 @@ class PlaybackViewModel : ViewModel() {
val currentIndex: LiveData<Int> get() = mCurrentIndex
private val mCurrentMode = MutableLiveData(PlaybackMode.ALL_SONGS)
val currentMode: LiveData<PlaybackMode> get() = mCurrentMode
private val mCurrentDuration = MutableLiveData(0L)
@ -63,7 +67,7 @@ class PlaybackViewModel : ViewModel() {
Log.d(
this::class.simpleName,
"update() was called with IN_GENRES, using " +
"most prominent genre instead of the song's genre."
"most prominent genre instead of the song's genre."
)
song.album.artist.genres[0].songs
@ -74,6 +78,42 @@ class PlaybackViewModel : ViewModel() {
mCurrentIndex.value = mQueue.value!!.indexOf(song)
}
fun play(album: Album, isShuffled: Boolean) {
Log.d(this::class.simpleName, "Playing album ${album.name}")
val songs = orderSongsInAlbum(album)
updatePlayback(songs[0])
mQueue.value = songs
mCurrentIndex.value = 0
mCurrentMode.value = PlaybackMode.IN_ALBUM
}
fun play(artist: Artist, isShuffled: Boolean) {
Log.d(this::class.simpleName, "Playing artist ${artist.name}")
val songs = orderSongsInArtist(artist)
updatePlayback(songs[0])
mQueue.value = songs
mCurrentIndex.value = 0
mCurrentMode.value = PlaybackMode.IN_ARTIST
}
fun play(genre: Genre, isShuffled: Boolean) {
Log.d(this::class.simpleName, "Playing genre ${genre.name}")
val songs = orderSongsInGenre(genre)
updatePlayback(songs[0])
mQueue.value = songs
mCurrentIndex.value = 0
mCurrentMode.value = PlaybackMode.IN_GENRE
}
private fun updatePlayback(song: Song) {
mCurrentSong.value = song
mCurrentDuration.value = 0
@ -83,6 +123,33 @@ class PlaybackViewModel : ViewModel() {
}
}
// Basic sorting functions when things are played in order
private fun orderSongsInAlbum(album: Album): MutableList<Song> {
return album.songs.sortedBy { it.track }.toMutableList()
}
private fun orderSongsInArtist(artist: Artist): MutableList<Song> {
val final = mutableListOf<Song>()
artist.albums.sortedByDescending { it.year }.forEach {
final.addAll(it.songs.sortedBy { it.track })
}
return final
}
private fun orderSongsInGenre(genre: Genre): MutableList<Song> {
val final = mutableListOf<Song>()
genre.artists.sortedBy { it.name }.forEach { artist ->
artist.albums.sortedByDescending { it.year }.forEach { album ->
final.addAll(album.songs.sortedBy { it.track })
}
}
return final
}
// Invert, not directly set the playing status
fun invertPlayingStatus() {
mIsPlaying.value = !mIsPlaying.value!!

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/background">
<group
android:pivotX="12"
android:pivotY="12"
android:rotation="-90">
<path
android:name="pause_upper"
android:fillColor="@android:color/white"
android:pathData="M6,7.5L6,10.5L18,10.5L18,7.5Z" />
<path
android:name="pause_lower"
android:fillColor="@android:color/white"
android:pathData="M6,16.5L6,13.5L18,13.5L18,16.5Z" />
</group>
</vector>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/background">
<path
android:fillColor="@android:color/white"
android:pathData="M8.25,6L8.25,12L18.75,12L18.75,12ZM8.25,18L8.25,12L18.75,12L18.75,12Z" />
</vector>

View file

@ -6,13 +6,17 @@
<data>
<variable
name="album"
type="org.oxycblt.auxio.music.Album" />
<variable
name="detailModel"
type="org.oxycblt.auxio.detail.DetailViewModel" />
<variable
name="playbackModel"
type="org.oxycblt.auxio.playback.PlaybackViewModel" />
<variable
name="album"
type="org.oxycblt.auxio.music.Album" />
</data>
<LinearLayout
@ -98,6 +102,23 @@
app:layout_constraintTop_toBottomOf="@+id/album_artist"
tools:text="2020 / 10 Songs / 16:16" />
<ImageButton
android:id="@+id/album_play"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="@dimen/margin_tiny"
android:layout_marginEnd="@dimen/margin_medium"
android:background="@drawable/ui_circular_button"
android:backgroundTint="?android:attr/colorPrimary"
android:contentDescription="@string/description_play"
android:src="@drawable/ic_play"
android:onClick="@{() -> playbackModel.play(album, false)}"
app:layout_constraintBottom_toBottomOf="@+id/album_details"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/album_artist" />
<TextView
android:id="@+id/album_song_header"
android:layout_width="match_parent"
@ -121,15 +142,15 @@
android:layout_marginTop="@dimen/margin_medium"
android:background="@drawable/ui_header_dividers"
android:contentDescription="@string/description_sort_button"
android:onClick="@{() -> detailModel.incrementAlbumSortMode()}"
android:paddingStart="@dimen/padding_medium"
android:paddingTop="@dimen/padding_small"
android:paddingEnd="@dimen/margin_medium"
android:paddingBottom="@dimen/padding_small"
android:onClick="@{() -> detailModel.incrementAlbumSortMode()}"
tools:src="@drawable/ic_sort_numeric_down"
app:layout_constraintBottom_toTopOf="@+id/album_song_recycler"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_details" />
app:layout_constraintTop_toBottomOf="@+id/album_details"
tools:src="@drawable/ic_sort_numeric_down" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/album_song_recycler"

View file

@ -9,6 +9,7 @@
<dimen name="padding_huge">64dp</dimen>
<!-- Margin namespace | Dimens for margin attributes -->
<dimen name="margin_tiny">4dp</dimen>
<dimen name="margin_small">8dp</dimen>
<dimen name="margin_mid_small">10dp</dimen>
<dimen name="margin_medium">16dp</dimen>

View file

@ -11,7 +11,7 @@
</style>
<style name="Toolbar.Style" parent="ThemeOverlay.MaterialComponents.ActionBar">
<item name="android:searchViewStyle">@style/Toolbar.SearchViewStyle</item>
<item name="android:searchViewStyle">@style/Widget.AppCompat.SearchView</item>
</style>
<style name="TextAppearance.Toolbar.Header" parent="TextAppearance.Widget.AppCompat.Toolbar.Title">
@ -19,8 +19,6 @@
<item name="android:textColor">?android:attr/colorPrimary</item>
</style>
<style name="Toolbar.SearchViewStyle" parent="Widget.AppCompat.SearchView" />
<style name="DetailHeader">
<item name="android:textAppearance">?android:attr/textAppearanceLarge</item>
<item name="android:textColor">?android:attr/colorPrimary</item>