diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt index b64b5cbce..0ed7afbd2 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -164,15 +164,11 @@ class HomeFragment : Fragment() { binding.homeAppbar.liftOnScrollTargetViewId = when (requireNotNull(tab)) { DisplayMode.SHOW_SONGS -> { updateSortMenu(sortItem, tab) - R.id.home_song_list } DisplayMode.SHOW_ALBUMS -> { - updateSortMenu(sortItem, tab) { id -> - id != R.id.option_sort_album - } - + updateSortMenu(sortItem, tab) { id -> id != R.id.option_sort_album } R.id.home_album_list } diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeListFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeListFragment.kt index 926a45842..efa05845a 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeListFragment.kt @@ -30,10 +30,14 @@ import androidx.navigation.fragment.findNavController import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.FragmentHomeListBinding +import org.oxycblt.auxio.home.recycler.HomeAdapter +import org.oxycblt.auxio.home.recycler.ParentAdapter +import org.oxycblt.auxio.home.recycler.SongsAdapter 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.Parent import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.ui.DisplayMode @@ -56,53 +60,34 @@ class HomeListFragment : Fragment() { ): View { val binding = FragmentHomeListBinding.inflate(inflater) - val homeAdapter = HomeAdapter( - doOnClick = { item -> - when (item) { - is Song -> playbackModel.playSong(item) - - is Album -> findNavController().navigate( - HomeFragmentDirections.actionShowAlbum(item.id) - ) - - is Artist -> findNavController().navigate( - HomeFragmentDirections.actionShowArtist(item.id) - ) - - is Genre -> findNavController().navigate( - HomeFragmentDirections.actionShowGenre(item.id) - ) - - else -> { - } - } - }, - ::newMenu - ) - // Get some tab-specific values before we go ahead. More specifically, the data to use // and the unique ID that HomeFragment's AppBarLayout uses to determine lift state. val pos = requireNotNull(arguments).getInt(ARG_POS) @IdRes val customId: Int + val homeAdapter: HomeAdapter val homeData: LiveData> when (homeModel.tabs.value!![pos]) { DisplayMode.SHOW_SONGS -> { customId = R.id.home_song_list homeData = homeModel.songs + homeAdapter = SongsAdapter(::onSongClick, ::newMenu, playbackModel) } DisplayMode.SHOW_ALBUMS -> { customId = R.id.home_album_list homeData = homeModel.albums + homeAdapter = ParentAdapter(::onParentClick, ::newMenu) } DisplayMode.SHOW_ARTISTS -> { customId = R.id.home_artist_list homeData = homeModel.artists + homeAdapter = ParentAdapter(::onParentClick, ::newMenu) } DisplayMode.SHOW_GENRES -> { customId = R.id.home_genre_list homeData = homeModel.genres + homeAdapter = ParentAdapter(::onParentClick, ::newMenu) } } @@ -129,6 +114,26 @@ class HomeListFragment : Fragment() { return binding.root } + private fun onSongClick(song: Song) { + playbackModel.playSong(song) + } + + private fun onParentClick(parent: Parent) { + when (parent) { + is Album -> findNavController().navigate( + HomeFragmentDirections.actionShowAlbum(parent.id) + ) + + is Artist -> findNavController().navigate( + HomeFragmentDirections.actionShowArtist(parent.id) + ) + + is Genre -> findNavController().navigate( + HomeFragmentDirections.actionShowGenre(parent.id) + ) + } + } + companion object { private const val ARG_POS = BuildConfig.APPLICATION_ID + ".key.POS" diff --git a/app/src/main/java/org/oxycblt/auxio/home/recycler/HomeAdapter.kt b/app/src/main/java/org/oxycblt/auxio/home/recycler/HomeAdapter.kt new file mode 100644 index 000000000..b8c8f5e4b --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/home/recycler/HomeAdapter.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 Auxio Project + * HomeAdapter.kt is part of Auxio. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.auxio.home.recycler + +import android.annotation.SuppressLint +import androidx.recyclerview.widget.RecyclerView +import org.oxycblt.auxio.music.BaseModel + +/** + * A base class that implements an [updateData] that is required across [SongsAdapter] and [ParentAdapter] + */ +abstract class HomeAdapter : RecyclerView.Adapter() { + protected var data = listOf() + + /** + * Update the data with [newData]. [notifyDataSetChanged] will be called. + */ + @SuppressLint("NotifyDataSetChanged") + fun updateData(newData: List) { + data = newData + + // I would use ListAdapter instead of this inefficient invalidate call, but they still + // haven't fixed the issue where ListAdapter's calculations will cause wild scrolling + // for basically no reason. + notifyDataSetChanged() + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeAdapter.kt b/app/src/main/java/org/oxycblt/auxio/home/recycler/ParentAdapter.kt similarity index 65% rename from app/src/main/java/org/oxycblt/auxio/home/HomeAdapter.kt rename to app/src/main/java/org/oxycblt/auxio/home/recycler/ParentAdapter.kt index 3d1ddbb3d..7f6cbe8df 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/recycler/ParentAdapter.kt @@ -1,6 +1,6 @@ /* * Copyright (c) 2021 Auxio Project - * HomeAdapter.kt is part of Auxio. + * ParentAdapter.kt is part of Auxio. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,32 +16,26 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.home +package org.oxycblt.auxio.home.recycler -import android.annotation.SuppressLint import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView 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.Song +import org.oxycblt.auxio.music.Parent import org.oxycblt.auxio.ui.AlbumViewHolder import org.oxycblt.auxio.ui.ArtistViewHolder import org.oxycblt.auxio.ui.GenreViewHolder -import org.oxycblt.auxio.ui.SongViewHolder /** - * A universal adapter for displaying data in [HomeFragment]. + * A universal adapter for displaying [Parent] data. */ -class HomeAdapter( - private val doOnClick: (data: BaseModel) -> Unit, - private val doOnLongClick: (view: View, data: BaseModel) -> Unit -) : RecyclerView.Adapter() { - - private var data = listOf() - +class ParentAdapter( + private val doOnClick: (data: Parent) -> Unit, + private val doOnLongClick: (view: View, data: Parent) -> Unit, +) : HomeAdapter() { override fun getItemCount(): Int = data.size override fun getItemViewType(position: Int): Int { @@ -49,7 +43,6 @@ class HomeAdapter( is Genre -> GenreViewHolder.ITEM_TYPE is Artist -> ArtistViewHolder.ITEM_TYPE is Album -> AlbumViewHolder.ITEM_TYPE - is Song -> SongViewHolder.ITEM_TYPE else -> error("Unsupported item ${data[position]::class.simpleName}") } } @@ -68,10 +61,6 @@ class HomeAdapter( parent.context, doOnClick, doOnLongClick ) - SongViewHolder.ITEM_TYPE -> SongViewHolder.from( - parent.context, doOnClick, doOnLongClick - ) - else -> error("Invalid viewholder item type.") } } @@ -81,20 +70,6 @@ class HomeAdapter( is Genre -> (holder as GenreViewHolder).bind(item) is Artist -> (holder as ArtistViewHolder).bind(item) is Album -> (holder as AlbumViewHolder).bind(item) - is Song -> (holder as SongViewHolder).bind(item) } } - - /** - * Update the data with [newData]. [notifyDataSetChanged] will be called. - */ - @SuppressLint("NotifyDataSetChanged") - fun updateData(newData: List) { - data = newData - - // I would use ListAdapter instead of this inefficient invalidate call, but they still - // haven't fixed the issue where ListAdapter's calculations will cause wild scrolling - // for basically no reason. - notifyDataSetChanged() - } } diff --git a/app/src/main/java/org/oxycblt/auxio/home/recycler/SongsAdapter.kt b/app/src/main/java/org/oxycblt/auxio/home/recycler/SongsAdapter.kt new file mode 100644 index 000000000..0a2c9f00f --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/home/recycler/SongsAdapter.kt @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2021 Auxio Project + * HomeAdapter.kt is part of Auxio. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.oxycblt.auxio.home.recycler + +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import org.oxycblt.auxio.databinding.ItemPlayShuffleBinding +import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.playback.PlaybackViewModel +import org.oxycblt.auxio.ui.SongViewHolder +import org.oxycblt.auxio.util.inflater + +/** + * An adapter for displaying a song list with a special play/shuffle header. + * Note that the data for the play/pause icon does not need to be included with the data + * you are submitting. It is automatically handled by the adapter. + * TODO: Maybe extend play/shuffle to all items? + */ +class SongsAdapter( + private val doOnClick: (data: Song) -> Unit, + private val doOnLongClick: (view: View, data: Song) -> Unit, + private val playbackModel: PlaybackViewModel +) : HomeAdapter() { + override fun getItemCount(): Int = if + (data.isEmpty()) 0 // Account for the play/shuffle viewholder + else + data.size + 1 + + override fun getItemViewType(position: Int): Int { + return if (position == 0) { + PLAY_ITEM_TYPE + } else { + SongViewHolder.ITEM_TYPE + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return when (viewType) { + PLAY_ITEM_TYPE -> PlayViewHolder( + ItemPlayShuffleBinding.inflate(parent.context.inflater) + ) + + SongViewHolder.ITEM_TYPE -> SongViewHolder.from( + parent.context, doOnClick, doOnLongClick + ) + + else -> error("Invalid viewholder item type.") + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + if (holder is SongViewHolder) { + holder.bind(data[position - 1] as Song) + } + } + + private inner class PlayViewHolder( + binding: ItemPlayShuffleBinding + ) : RecyclerView.ViewHolder(binding.root) { + init { + // Force the layout to *actually* be the screen width. + // We can't inherit BaseViewHolder here since this ViewHolder isn't really connected + // to an item. + binding.root.layoutParams = RecyclerView.LayoutParams( + RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT + ) + + binding.playButton.setOnClickListener { + playbackModel.playAll() + } + + binding.shuffleButton.setOnClickListener { + playbackModel.shuffleAll() + } + } + } + + companion object { + const val PLAY_ITEM_TYPE = 0xA00E + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt index 905f314fc..035c0bd4a 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicLoader.kt @@ -40,13 +40,14 @@ import org.oxycblt.auxio.util.logD * * You think that if you wanted to query a song's genre from a media database, you could just * put "genre" in the query and it would return it, right? But not with MediaStore! No, that's - * to straightfoward for this platform. So instead, you have to query for each genre, query all - * the songs in each genre, and then iterate through those songs to link every song with their - * genre. This is not documented anywhere in MediaStore's documentation, and the O(mom im scared) - * algorithm you have to run to get it working single-handedly DOUBLES Auxio's loading times. At - * no point have the devs considered that this column is absolutely busted, and instead focused on - * adding infuriat- I mean nice proprietary extensions to MediaStore for their own Google Play Music, - * and we all know how great that worked out! + * to straightfoward for this platform that was dropped on it's head as a baby. So instead, you + * have to query for each genre, query all the songs in each genre, and then iterate through those + * songs to link every song with their genre. This is not documented anywhere in MediaStore's + * documentation, and the O(mom im scared) algorithm you have to run to get it working + * single-handedly DOUBLES Auxio's loading times. At no point have the devs considered that this + * column is absolutely busted, and instead focused on adding infuriat- I mean nice proprietary + * extensions to MediaStore for their own Google Play Music, and we all know how great that worked + * out! * * It's not even ergonomics that makes this API bad. It's base implementation is completely borked * as well. Did you know that MediaStore doesn't accept dates that aren't from ID3v2.3 MP3 files? @@ -55,21 +56,21 @@ import org.oxycblt.auxio.util.logD * DATE tag. Once again, this is because internally android uses an ancient in-house metadata * parser to get everything indexed, and so far they have not bothered to modernize this parser * or even switch it to something more powerful like Taglib, not even in Android 12. ID3v2.4 is - * 21 years old. It can drink now. All my what. + * 21 years old. It can drink now. All of my what. * * Not to mention all the other infuriating quirks. Album artists can't be accessed from the albums * table, so we have to go for the less efficent "make a big query on all the songs lol" method * so that songs don't end up fragmented across artists. Pretty much every OEM has added some - * extension or quirk to MediaStore that I cannot determine, with some OEMs (COUGHSAMSUNGCOUGH) + * extension or quirk to MediaStore that I cannot reproduce, with some OEMs (COUGHSAMSUNGCOUGH) * crippling the normal tables so that you're railroaded into their ad-infested music app. * The way I do blacklisting relies on a deprecated method, and the supposedly "modern" method * is SLOWER and causes even more problems since I have to manage databases across version * boundaries. Sometimes music will have a deformed clone that I can't filter out, sometimes - * Genre's will just break for no reason, sometimes this plate of spaghetti just completely breaks + * Genres will just break for no reason, sometimes this spaghetti parser just completely breaks * down and is unable to get any metadata. Everything is broken in it's own special unique way and * I absolutely hate it. * - * Is there anything we can do about it? No. Google has routinely shut down issues that beg google + * Is there anything we can do about it? No. Google has routinely shut down issues that begged google * to fix glaring issues with MediaStore or to just take the API behind the woodshed and shoot it. * Largely because they have zero incentive to improve it, especially for such obscure things * as indexing music. As a result, some players like Vanilla and VLC just hack their own pidgin @@ -84,7 +85,7 @@ import org.oxycblt.auxio.util.logD * I'm pretty sure nothing is going to happen and MediaStore will continue to be neglected and * probably deprecated eventually for a "new" API that just coincidentally excludes music indexing. * Because go **** yourself for wanting to listen to music you own. Be a good consoomer and listen - * to your AlgoMix MusikStream™. + * to your AlgoPop StreamMix™. * * I hate this platform so much. * 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 8484e596f..95ddccb8f 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -204,6 +204,13 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { } } + /** + * Play all songs + */ + fun playAll() { + playbackManager.playAll() + } + /** * Shuffle all songs */ diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt index 2126933c7..809b3be30 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt @@ -232,6 +232,18 @@ class PlaybackStateManager private constructor() { updatePlayback(mQueue[0]) } + /** + * Play all songs. + */ + fun playAll() { + mMode = PlaybackMode.ALL_SONGS + mQueue = musicStore.songs.toMutableList() + mParent = null + + setShuffling(false, keepSong = false) + updatePlayback(mQueue[0]) + } + /** * Shuffle all songs. */ diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 81ed7122a..afede706b 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -49,6 +49,6 @@ app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" tools:layout="@layout/fragment_home_list" /> - + \ No newline at end of file diff --git a/app/src/main/res/layout/item_play_shuffle.xml b/app/src/main/res/layout/item_play_shuffle.xml new file mode 100644 index 000000000..f8e517724 --- /dev/null +++ b/app/src/main/res/layout/item_play_shuffle.xml @@ -0,0 +1,29 @@ + + + + + + + + + + diff --git a/info/ARCHITECTURE.md b/info/ARCHITECTURE.md index 0423277e4..daef33c9e 100644 --- a/info/ARCHITECTURE.md +++ b/info/ARCHITECTURE.md @@ -56,6 +56,7 @@ To prevent any strange bugs, all integer representations must be unique. A table 0xA00C | GenreSongViewHolder 0xA00D | QueueSongViewHolder +0xA00E | PlayPauseViewHolder 0xA0A0 | Auxio notification code 0xA0C0 | Auxio request code