diff --git a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt index 6fcd8d1a8..8755493db 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -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) + } + } + } + } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackMode.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackMode.kt index 82d6b48b3..4d7cf1a06 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackMode.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackMode.kt @@ -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 { + val vals = values() + + return vals.slice(vals.indexOf(this) until vals.size) + } } 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 90c1a654d..589688ff8 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -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 get() = mCurrentIndex private val mCurrentMode = MutableLiveData(PlaybackMode.ALL_SONGS) + val currentMode: LiveData 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 { + return album.songs.sortedBy { it.track }.toMutableList() + } + + private fun orderSongsInArtist(artist: Artist): MutableList { + val final = mutableListOf() + + artist.albums.sortedByDescending { it.year }.forEach { + final.addAll(it.songs.sortedBy { it.track }) + } + + return final + } + + private fun orderSongsInGenre(genre: Genre): MutableList { + val final = mutableListOf() + + 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!! diff --git a/app/src/main/res/drawable/ic_pause.xml b/app/src/main/res/drawable/ic_pause.xml new file mode 100644 index 000000000..39eb975ac --- /dev/null +++ b/app/src/main/res/drawable/ic_pause.xml @@ -0,0 +1,24 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_play.xml b/app/src/main/res/drawable/ic_play.xml new file mode 100644 index 000000000..bb10b275c --- /dev/null +++ b/app/src/main/res/drawable/ic_play.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_album_detail.xml b/app/src/main/res/layout/fragment_album_detail.xml index f934cbcc5..b20820f17 100644 --- a/app/src/main/res/layout/fragment_album_detail.xml +++ b/app/src/main/res/layout/fragment_album_detail.xml @@ -6,13 +6,17 @@ - - + + + + + + + app:layout_constraintTop_toBottomOf="@+id/album_details" + tools:src="@drawable/ic_sort_numeric_down" /> 64dp + 4dp 8dp 10dp 16dp diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 3240b2562..431f218e1 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -11,7 +11,7 @@ -