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:
parent
9f05ce6e52
commit
a64627c7cf
8 changed files with 185 additions and 12 deletions
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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!!
|
||||
|
|
24
app/src/main/res/drawable/ic_pause.xml
Normal file
24
app/src/main/res/drawable/ic_pause.xml
Normal 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>
|
11
app/src/main/res/drawable/ic_play.xml
Normal file
11
app/src/main/res/drawable/ic_play.xml
Normal 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>
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue