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 b78145a0b..af8d909f7 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/AlbumDetailFragment.kt @@ -55,16 +55,30 @@ class AlbumDetailFragment : Fragment() { binding.playbackModel = playbackModel binding.album = detailModel.currentAlbum.value!! + binding.albumToolbar.apply { + setNavigationOnClickListener { + findNavController().navigateUp() + } + + setOnMenuItemClickListener { + when (it.itemId) { + R.id.action_shuffle -> playbackModel.play( + detailModel.currentAlbum.value!!, + true + ) + R.id.action_play -> playbackModel.play(detailModel.currentAlbum.value!!, false) + } + + true + } + } + binding.albumSongRecycler.apply { adapter = songAdapter applyDivider() setHasFixedSize(true) } - binding.albumToolbar.setNavigationOnClickListener { - findNavController().navigateUp() - } - // Don't enable the sort button if there's only one song [or less] if (detailModel.currentAlbum.value!!.numSongs < 2) { binding.albumSortButton.disable(requireContext()) @@ -84,16 +98,6 @@ class AlbumDetailFragment : Fragment() { ) } - // Observe playback model to update the play button - // TODO: Make these icons animated - 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) { @@ -118,34 +122,4 @@ class AlbumDetailFragment : Fragment() { return binding.root } - - // Update the play button depending on the current playback status - // If playing this album -> Make button show media controls - // If not playing this album -> Make button update playback to the artist - private fun updatePlayButton( - mode: PlaybackMode, - binding: FragmentAlbumDetailBinding - ) { - playbackModel.currentParent.value?.let { parent -> - if (mode == PlaybackMode.IN_ALBUM && - parent.id == detailModel.currentAlbum.value!!.id - ) { - 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/detail/ArtistDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt index 410755d90..7c133d152 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/ArtistDetailFragment.kt @@ -13,13 +13,11 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentArtistDetailBinding import org.oxycblt.auxio.detail.adapters.DetailAlbumAdapter import org.oxycblt.auxio.music.MusicStore -import org.oxycblt.auxio.playback.PlaybackMode import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.theme.applyDivider import org.oxycblt.auxio.theme.disable class ArtistDetailFragment : Fragment() { - private val args: ArtistDetailFragmentArgs by navArgs() private val detailModel: DetailViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by activityViewModels() @@ -60,8 +58,22 @@ class ArtistDetailFragment : Fragment() { binding.playbackModel = playbackModel binding.artist = detailModel.currentArtist.value!! - binding.artistToolbar.setNavigationOnClickListener { - findNavController().navigateUp() + binding.artistToolbar.apply { + setNavigationOnClickListener { + findNavController().navigateUp() + } + + setOnMenuItemClickListener { + when (it.itemId) { + R.id.action_shuffle -> playbackModel.play( + detailModel.currentArtist.value!!, + true + ) + R.id.action_play -> playbackModel.play(detailModel.currentArtist.value!!, false) + } + + true + } } // Disable the sort button if there is only one album [Or less] @@ -89,49 +101,11 @@ class ArtistDetailFragment : Fragment() { ) } - playbackModel.currentMode.observe(viewLifecycleOwner) { - updatePlayButton(it, binding) - } - - playbackModel.isPlaying.observe(viewLifecycleOwner) { - updatePlayButton(playbackModel.currentMode.value!!, binding) - } - Log.d(this::class.simpleName, "Fragment created.") return binding.root } - // Update the play button depending on the current playback status - // If playing this artist -> Make button show media controls - // If not playing this artist -> Make button update playback to the artist - private fun updatePlayButton( - mode: PlaybackMode, - binding: FragmentArtistDetailBinding - ) { - playbackModel.currentParent.value?.let { parent -> - if (mode == PlaybackMode.IN_ARTIST && - parent.id == detailModel.currentArtist.value!!.id - ) { - if (playbackModel.isPlaying.value!!) { - binding.artistPlay.setImageResource(R.drawable.ic_pause) - } else { - binding.artistPlay.setImageResource(R.drawable.ic_play) - } - - binding.artistPlay.setOnClickListener { - playbackModel.invertPlayingStatus() - } - } else { - binding.artistPlay.setImageResource(R.drawable.ic_play) - - binding.artistPlay.setOnClickListener { - playbackModel.play(detailModel.currentAlbum.value!!, false) - } - } - } - } - override fun onResume() { super.onResume() diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt index b70e57f65..5e4d9fbce 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt @@ -10,8 +10,8 @@ import org.oxycblt.auxio.recycler.SortMode // ViewModel for the Detail Fragments. // TODO: -// - Implement a system where the Toolbar will update with the info [And Media Controls] when -// the main info of the detail fragment is removed. +// - Implement a system where the Toolbar will update with some infowhen +// the main detail header is obscured. class DetailViewModel : ViewModel() { private var mIsNavigating = false val isNavigating: Boolean get() = mIsNavigating diff --git a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt index 587af7192..0d7418022 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/GenreDetailFragment.kt @@ -9,9 +9,11 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs +import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentGenreDetailBinding import org.oxycblt.auxio.detail.adapters.DetailArtistAdapter import org.oxycblt.auxio.music.MusicStore +import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.theme.applyDivider import org.oxycblt.auxio.theme.disable @@ -19,6 +21,7 @@ class GenreDetailFragment : Fragment() { private val args: GenreDetailFragmentArgs by navArgs() private val detailModel: DetailViewModel by activityViewModels() + private val playbackModel: PlaybackViewModel by activityViewModels() override fun onCreateView( inflater: LayoutInflater, @@ -55,8 +58,22 @@ class GenreDetailFragment : Fragment() { binding.detailModel = detailModel binding.genre = detailModel.currentGenre.value - binding.genreToolbar.setNavigationOnClickListener { - findNavController().navigateUp() + binding.genreToolbar.apply { + setNavigationOnClickListener { + findNavController().navigateUp() + } + + setOnMenuItemClickListener { + when (it.itemId) { + R.id.action_shuffle -> playbackModel.play( + detailModel.currentGenre.value!!, + true + ) + R.id.action_play -> playbackModel.play(detailModel.currentGenre.value!!, false) + } + + true + } } // Disable the sort button if there is only one artist [Or less] diff --git a/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt b/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt index 2c1b8fb1f..81931d60f 100644 --- a/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/library/LibraryViewModel.kt @@ -21,7 +21,7 @@ class LibraryViewModel : ViewModel() { val searchHasFocus: Boolean get() = mSearchHasFocus // TODO: Move these to prefs when they're added - private val mShowMode = MutableLiveData(ShowMode.SHOW_ARTISTS) + private val mShowMode = MutableLiveData(ShowMode.SHOW_GENRES) val showMode: LiveData get() = mShowMode private val mSortMode = MutableLiveData(SortMode.ALPHA_DOWN) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt index a84126702..7ab8ae7d2 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/CompactPlaybackFragment.kt @@ -38,7 +38,7 @@ class CompactPlaybackFragment : Fragment() { binding.lifecycleOwner = viewLifecycleOwner // Put a placeholder song in the binding & hide the playback fragment initially, - // as for some reason the attach event doesn't register anymore w/LiveData + // just in case. binding.song = MusicStore.getInstance().songs[0] binding.playbackModel = playbackModel @@ -59,7 +59,6 @@ class CompactPlaybackFragment : Fragment() { } } - // TODO: Fix the thing where the icons will animate on startup playbackModel.isPlaying.observe(viewLifecycleOwner) { if (playbackModel.canAnimate) { if (it) { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt index da2df268f..d606c9ef3 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt @@ -46,7 +46,9 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener { // --- UI SETUP --- + binding.lifecycleOwner = viewLifecycleOwner binding.playbackModel = playbackModel + binding.song = playbackModel.currentSong.value!! binding.playbackToolbar.setNavigationOnClickListener { findNavController().navigateUp() @@ -54,7 +56,6 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener { // Make marquee scroll work binding.playbackSong.isSelected = true - binding.playbackSeekBar.setOnSeekBarChangeListener(this) // --- VIEWMODEL SETUP -- 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 8eb453c63..43ba15d34 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackMode.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackMode.kt @@ -5,5 +5,5 @@ 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 { - IN_ARTIST, IN_ALBUM, ALL_SONGS; + IN_ARTIST, IN_GENRE, IN_ALBUM, ALL_SONGS; } 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 d72c28651..0a623f501 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -8,6 +8,7 @@ import androidx.lifecycle.ViewModel import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.BaseModel +import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.toDuration @@ -63,6 +64,15 @@ class PlaybackViewModel : ViewModel() { // Update the current song while changing the queue mode. fun update(song: Song, mode: PlaybackMode) { + + // Auxio doesn't support playing songs while swapping the mode to GENRE, as genres + // are bound to artists, not songs. + if (mode == PlaybackMode.IN_GENRE) { + Log.e(this::class.simpleName, "Auxio cant play songs with the mode of IN_GENRE.") + + return + } + Log.d(this::class.simpleName, "Updating song to ${song.name} and mode to $mode") val musicStore = MusicStore.getInstance() @@ -75,12 +85,14 @@ class PlaybackViewModel : ViewModel() { PlaybackMode.ALL_SONGS -> musicStore.songs.toMutableList() PlaybackMode.IN_ARTIST -> song.album.artist.songs PlaybackMode.IN_ALBUM -> song.album.songs + PlaybackMode.IN_GENRE -> error("what") } mCurrentParent.value = when (mode) { PlaybackMode.ALL_SONGS -> null PlaybackMode.IN_ARTIST -> song.album.artist PlaybackMode.IN_ALBUM -> song.album + PlaybackMode.IN_GENRE -> error("what") } if (mIsShuffling.value!!) { @@ -92,6 +104,7 @@ class PlaybackViewModel : ViewModel() { mCurrentIndex.value = mQueue.value!!.indexOf(song) } + // Play some parent music model, whether that being albums/artists/genres. fun play(album: Album, isShuffled: Boolean) { Log.d(this::class.simpleName, "Playing album ${album.name}") @@ -132,6 +145,26 @@ class PlaybackViewModel : ViewModel() { } } + 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 + mCurrentParent.value = genre + mIsShuffling.value = isShuffled + mCurrentMode.value = PlaybackMode.IN_GENRE + + if (mIsShuffling.value!!) { + genShuffle(false) + } else { + resetShuffle() + } + } + // Update the current duration using a SeekBar progress fun updateCurrentDurationWithProgress(progress: Int) { mCurrentDuration.value = progress.toLong() @@ -232,6 +265,7 @@ class PlaybackViewModel : ViewModel() { mQueue.value = when (mCurrentMode.value!!) { PlaybackMode.IN_ARTIST -> orderSongsInArtist(mCurrentParent.value as Artist) PlaybackMode.IN_ALBUM -> orderSongsInAlbum(mCurrentParent.value as Album) + PlaybackMode.IN_GENRE -> orderSongsInGenre(mCurrentParent.value as Genre) PlaybackMode.ALL_SONGS -> MusicStore.getInstance().songs.toMutableList() } @@ -246,8 +280,22 @@ class PlaybackViewModel : ViewModel() { private fun orderSongsInArtist(artist: Artist): MutableList { val final = mutableListOf() - artist.albums.sortedByDescending { it.year }.forEach { - final.addAll(it.songs.sortedBy { it.track }) + artist.albums.sortedByDescending { it.year }.forEach { album -> + final.addAll(album.songs.sortedBy { it.track }) + } + + return final + } + + private fun orderSongsInGenre(genre: Genre): MutableList { + val final = mutableListOf() + + genre.artists.sortedWith( + compareBy(String.CASE_INSENSITIVE_ORDER) { it.name } + ).forEach { artist -> + artist.albums.sortedByDescending { it.year }.forEach { album -> + final.addAll(album.songs.sortedBy { it.track }) + } } return final 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 c59af94f3..b10ab2a43 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 @@ -82,7 +82,7 @@ class AlbumViewHolder private constructor( } } -// TODO: Add indicators to song recycler items when they're being played. +// TODO: Add indicators to song recycler items when they're being played? class SongViewHolder private constructor( doOnClick: (Song) -> Unit, diff --git a/app/src/main/res/drawable/ic_back.xml b/app/src/main/res/drawable/ic_back.xml index eaca5044c..080fc1b88 100644 --- a/app/src/main/res/drawable/ic_back.xml +++ b/app/src/main/res/drawable/ic_back.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="@color/control_color"> + android:tint="?android:attr/colorControlNormal"> diff --git a/app/src/main/res/drawable/ic_down.xml b/app/src/main/res/drawable/ic_down.xml index 00758a13a..c69ccb52a 100644 --- a/app/src/main/res/drawable/ic_down.xml +++ b/app/src/main/res/drawable/ic_down.xml @@ -4,7 +4,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="@color/control_color"> + android:tint="?android:attr/colorControlNormal"> diff --git a/app/src/main/res/drawable/ic_pause.xml b/app/src/main/res/drawable/ic_pause.xml index 71352025e..f5f4c3387 100644 --- a/app/src/main/res/drawable/ic_pause.xml +++ b/app/src/main/res/drawable/ic_pause.xml @@ -3,8 +3,7 @@ android:width="32dp" android:height="32dp" android:viewportWidth="24" - android:viewportHeight="24" - android:tint="@color/control_color"> + android:viewportHeight="24"> diff --git a/app/src/main/res/drawable/ic_pause_to_play.xml b/app/src/main/res/drawable/ic_pause_to_play.xml index adffcd70d..ddce07446 100644 --- a/app/src/main/res/drawable/ic_pause_to_play.xml +++ b/app/src/main/res/drawable/ic_pause_to_play.xml @@ -9,7 +9,6 @@ https://github.com/ashutoshgngwr/noice @@ -19,21 +18,20 @@ https://github.com/ashutoshgngwr/noice android:pivotY="12"> + android:pathData="M 22.677734 18.898438 L 22.677734 71.810547 L 37.794922 71.810547 L 37.794922 18.898438 L 22.677734 18.898438 z" /> + android:pathData="M 52.914062 18.898438 L 52.914062 71.810547 L 68.03125 71.810547 L 68.03125 18.898438 L 52.914062 18.898438 z " /> - - + - + + android:viewportHeight="24"> diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml index ae48aafda..76894fee3 100644 --- a/app/src/main/res/drawable/ic_search.xml +++ b/app/src/main/res/drawable/ic_search.xml @@ -4,7 +4,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="@color/control_color"> + android:tint="?android:attr/colorControlNormal"> diff --git a/app/src/main/res/drawable/ic_shuffle.xml b/app/src/main/res/drawable/ic_shuffle.xml index e68fd1c25..5020b8b43 100644 --- a/app/src/main/res/drawable/ic_shuffle.xml +++ b/app/src/main/res/drawable/ic_shuffle.xml @@ -4,7 +4,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="@color/control_color"> + android:tint="?android:attr/colorControlNormal"> diff --git a/app/src/main/res/drawable/ic_shuffle_large.xml b/app/src/main/res/drawable/ic_shuffle_large.xml index d207d1526..20617b959 100644 --- a/app/src/main/res/drawable/ic_shuffle_large.xml +++ b/app/src/main/res/drawable/ic_shuffle_large.xml @@ -4,7 +4,7 @@ android:height="32dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="@color/control_color"> + android:tint="?android:attr/colorControlNormal"> diff --git a/app/src/main/res/drawable/ic_skip_next.xml b/app/src/main/res/drawable/ic_skip_next.xml index a8ca69293..6826f7388 100644 --- a/app/src/main/res/drawable/ic_skip_next.xml +++ b/app/src/main/res/drawable/ic_skip_next.xml @@ -4,7 +4,7 @@ android:height="32dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="@color/control_color"> + android:tint="?android:attr/colorControlNormal"> diff --git a/app/src/main/res/drawable/ic_skip_prev.xml b/app/src/main/res/drawable/ic_skip_prev.xml index f69d615a0..2704abb08 100644 --- a/app/src/main/res/drawable/ic_skip_prev.xml +++ b/app/src/main/res/drawable/ic_skip_prev.xml @@ -4,7 +4,7 @@ android:height="32dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="@color/control_color"> + android:tint="?android:attr/colorControlNormal"> diff --git a/app/src/main/res/drawable/ic_sort_none.xml b/app/src/main/res/drawable/ic_sort_none.xml index d4647283b..9e4362c1a 100644 --- a/app/src/main/res/drawable/ic_sort_none.xml +++ b/app/src/main/res/drawable/ic_sort_none.xml @@ -4,7 +4,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="@color/control_color"> + android:tint="?android:attr/colorControlNormal"> diff --git a/app/src/main/res/drawable/ic_sort_numeric_up.xml b/app/src/main/res/drawable/ic_sort_numeric_up.xml index 61e6dab83..ee6167a32 100644 --- a/app/src/main/res/drawable/ic_sort_numeric_up.xml +++ b/app/src/main/res/drawable/ic_sort_numeric_up.xml @@ -5,7 +5,6 @@ android:viewportWidth="24" android:viewportHeight="24" android:tint="?android:attr/colorPrimary"> - @@ -103,42 +105,6 @@ app:layout_constraintTop_toBottomOf="@+id/album_artist" tools:text="2020 / 10 Songs / 16:16" /> - - - - @@ -98,43 +100,6 @@ app:layout_constraintTop_toBottomOf="@+id/artist_genre" tools:text="2 Albums, 20 Songs" /> - - - - - diff --git a/app/src/main/res/menu/menu_detail.xml b/app/src/main/res/menu/menu_detail.xml new file mode 100644 index 000000000..118d3eb14 --- /dev/null +++ b/app/src/main/res/menu/menu_detail.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index f14780398..e607c7847 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -6,6 +6,7 @@ @android:color/black @font/inter @drawable/ui_cursor + @color/control_color