diff --git a/app/src/main/java/org/oxycblt/auxio/music/coil/CoilUtils.kt b/app/src/main/java/org/oxycblt/auxio/music/coil/CoilUtils.kt index 5fc004b9e..f4116e139 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/coil/CoilUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/coil/CoilUtils.kt @@ -2,6 +2,7 @@ package org.oxycblt.auxio.music.coil import android.content.Context import android.net.Uri +import android.util.Log import android.widget.ImageView import androidx.databinding.BindingAdapter import coil.Coil @@ -79,17 +80,30 @@ fun ImageView.bindGenreImage(genre: Genre) { if (genre.numArtists >= 4) { val uris = mutableListOf() - // For each artist, get the nth album from them [if possible]. - for (i in 0..3) { - val artist = genre.artists[i] + Log.d(this::class.simpleName, genre.numAlbums.toString()) - uris.add( - if (artist.albums.size > i) { - artist.albums[i].coverUri - } else { - artist.albums[0].coverUri + // Try to create a 4x4 mosaic if possible, if not, just create a 2x2 mosaic. + if (genre.numAlbums >= 16) { + while (uris.size < 16) { + genre.artists.forEach { artist -> + artist.albums.forEach { + uris.add(it.coverUri) + } } - ) + } + } else { + // Get the Nth cover from each artist, if possible. + for (i in 0..3) { + val artist = genre.artists[i] + + uris.add( + if (artist.albums.size > i) { + artist.albums[i].coverUri + } else { + artist.albums[0].coverUri + } + ) + } } val fetcher = MosaicFetcher(context) diff --git a/app/src/main/java/org/oxycblt/auxio/music/coil/MosaicFetcher.kt b/app/src/main/java/org/oxycblt/auxio/music/coil/MosaicFetcher.kt index fe13a2470..99aff78b5 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/coil/MosaicFetcher.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/coil/MosaicFetcher.kt @@ -22,7 +22,7 @@ import java.io.InputStream const val MOSAIC_BITMAP_SIZE = 512 const val MOSAIC_BITMAP_INCREMENT = 256 -// A Fetcher that takes multiple cover uris and turns them into a 2x2 mosaic image. +// A Fetcher that takes multiple cover uris and turns them into a NxN mosaic image. class MosaicFetcher(private val context: Context) : Fetcher> { override suspend fun fetch( pool: BitmapPool, diff --git a/app/src/main/java/org/oxycblt/auxio/music/processing/MusicSorter.kt b/app/src/main/java/org/oxycblt/auxio/music/processing/MusicSorter.kt index 498db3451..ef2dd624b 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/processing/MusicSorter.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/processing/MusicSorter.kt @@ -20,15 +20,7 @@ class MusicSorter( sortSongsIntoAlbums() sortAlbumsIntoArtists() sortArtistsIntoGenres() - - // Remove genre duplicates at the end, as duplicate genres can be added during - // the sorting process as well. - genres = genres.distinctBy { - it.name - }.toMutableList() - - // Also elimate any genres that dont have artists, which also happens sometimes. - genres.removeAll { it.artists.isEmpty() } + fixBuggyGenres() } private fun sortSongsIntoAlbums() { @@ -161,4 +153,16 @@ class MusicSorter( ) } } + + // Band-aid any buggy genres created by the broken Music Loading system. + private fun fixBuggyGenres() { + // Remove genre duplicates at the end, as duplicate genres can be added during + // the sorting process as well. + genres = genres.distinctBy { + it.name + }.toMutableList() + + // Also eliminate any genres that don't have artists, which also happens sometimes. + genres.removeAll { it.artists.isEmpty() } + } } 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 d606c9ef3..6a67bf044 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackFragment.kt @@ -14,6 +14,7 @@ import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentPlaybackBinding +import org.oxycblt.auxio.playback.queue.QueueFragment import org.oxycblt.auxio.theme.accent import org.oxycblt.auxio.theme.disable import org.oxycblt.auxio.theme.enable @@ -50,8 +51,18 @@ class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener { binding.playbackModel = playbackModel binding.song = playbackModel.currentSong.value!! - binding.playbackToolbar.setNavigationOnClickListener { - findNavController().navigateUp() + binding.playbackToolbar.apply { + setNavigationOnClickListener { + findNavController().navigateUp() + } + + setOnMenuItemClickListener { + if (it.itemId == R.id.action_queue) { + QueueFragment().show(parentFragmentManager, "TAG_QUEUE") + } + + true + } } // Make marquee scroll work 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 f51116104..b53ae6cba 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -62,11 +62,16 @@ class PlaybackViewModel : ViewModel() { if (mCurrentSong.value != null) it.toInt() else 0 } + // Formatted queue that shows all the songs after the current playing song. + val formattedQueue = Transformations.map(mQueue) { + it.slice((mCurrentIndex.value!! + 1) until it.size) + } + // 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. + // Auxio doesn't support playing songs while swapping the mode to GENRE, as its impossible + // to determine what genre a song has. if (mode == PlaybackMode.IN_GENRE) { Log.e(this::class.simpleName, "Auxio cant play songs with the mode of IN_GENRE.") @@ -224,26 +229,36 @@ class PlaybackViewModel : ViewModel() { mIsSeeking.value = status } + // Skip to next song fun skipNext() { if (mCurrentIndex.value!! < mQueue.value!!.size) { mCurrentIndex.value = mCurrentIndex.value!!.inc() } updatePlayback(mQueue.value!![mCurrentIndex.value!!]) + + // Force the observers to actually update. + mQueue.value = mQueue.value } + // Skip to last song fun skipPrev() { if (mCurrentIndex.value!! > 0) { mCurrentIndex.value = mCurrentIndex.value!!.dec() } updatePlayback(mQueue.value!![mCurrentIndex.value!!]) + + // Force the observers to actually update. + mQueue.value = mQueue.value } fun resetAnimStatus() { mCanAnimate = false } + // Generic function for updating the playback with a new song. + // Use this instead of manually updating the values each time. private fun updatePlayback(song: Song) { mCurrentSong.value = song mCurrentDuration.value = 0 @@ -275,8 +290,12 @@ class PlaybackViewModel : ViewModel() { // Otherwise, just start from the zeroth position in the queue. mCurrentSong.value = mQueue.value!![0] } + + // Force the observers to actually update. + mQueue.value = mQueue.value } + // Stop the queue and attempt to restore to the previous state private fun resetShuffle() { mShuffleSeed.value = -1 diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt new file mode 100644 index 000000000..ae4fd09d8 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueAdapter.kt @@ -0,0 +1,20 @@ +package org.oxycblt.auxio.playback.queue + +import android.view.ViewGroup +import androidx.recyclerview.widget.ListAdapter +import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.recycler.DiffCallback +import org.oxycblt.auxio.recycler.viewholders.SongViewHolder + +class QueueAdapter( + private val doOnClick: (Song) -> Unit +) : ListAdapter(DiffCallback()) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SongViewHolder { + return SongViewHolder.from(parent.context, doOnClick) + } + + override fun onBindViewHolder(holder: SongViewHolder, position: Int) { + holder.bind(getItem(position)) + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt new file mode 100644 index 000000000..1136ab3dc --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt @@ -0,0 +1,47 @@ +package org.oxycblt.auxio.playback.queue + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.activityViewModels +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import org.oxycblt.auxio.R +import org.oxycblt.auxio.databinding.FragmentQueueBinding +import org.oxycblt.auxio.playback.PlaybackViewModel +import org.oxycblt.auxio.theme.accent +import org.oxycblt.auxio.theme.applyDivider +import org.oxycblt.auxio.theme.toColor + +class QueueFragment : BottomSheetDialogFragment() { + private val playbackModel: PlaybackViewModel by activityViewModels() + + override fun getTheme(): Int = R.style.Theme_BottomSheetFix + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val binding = FragmentQueueBinding.inflate(inflater) + + val queueAdapter = QueueAdapter {} + + // --- UI SETUP --- + + binding.queueHeader.setTextColor(accent.first.toColor(requireContext())) + binding.queueRecycler.apply { + adapter = queueAdapter + applyDivider() + setHasFixedSize(true) + } + + // --- VIEWMODEL SETUP --- + + playbackModel.formattedQueue.observe(viewLifecycleOwner) { + queueAdapter.submitList(it) + } + + return binding.root + } +} diff --git a/app/src/main/res/drawable/ic_queue.xml b/app/src/main/res/drawable/ic_queue.xml new file mode 100644 index 000000000..a5fc492bf --- /dev/null +++ b/app/src/main/res/drawable/ic_queue.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_playback.xml b/app/src/main/res/layout/fragment_playback.xml index 39f15cbc3..d714d11fe 100644 --- a/app/src/main/res/layout/fragment_playback.xml +++ b/app/src/main/res/layout/fragment_playback.xml @@ -20,6 +20,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:animateLayoutChanges="true" + android:fitsSystemWindows="true" android:background="@color/background"> + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_playback.xml b/app/src/main/res/menu/menu_playback.xml new file mode 100644 index 000000000..de755be64 --- /dev/null +++ b/app/src/main/res/menu/menu_playback.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index e1a317a10..cb36175d3 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -14,7 +14,6 @@ 10dp 16dp 24dp - 26dp 32dp 64dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 973e9e3d3..0747b22b6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -25,6 +25,7 @@ Z-A Shuffle Play + Queue Search Library… diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index e607c7847..dd488f8f4 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -7,6 +7,7 @@ @font/inter @drawable/ui_cursor @color/control_color + true @@ -36,4 +37,14 @@ @color/background @color/selection_color + + + \ No newline at end of file