deps: update

Spotless -> 6.15.0
Core -> 1.9.0
This commit is contained in:
Alexander Capehart 2023-02-24 21:57:01 -07:00
parent d6e7b99e1f
commit 83c5b85424
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
158 changed files with 889 additions and 226 deletions

View file

@ -82,7 +82,7 @@ dependencies {
implementation "androidx.recyclerview:recyclerview:1.2.1" implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation "androidx.constraintlayout:constraintlayout:2.1.4" implementation "androidx.constraintlayout:constraintlayout:2.1.4"
implementation "androidx.viewpager2:viewpager2:1.1.0-beta01" implementation "androidx.viewpager2:viewpager2:1.1.0-beta01"
implementation 'androidx.core:core-ktx:+' implementation 'androidx.core:core-ktx:1.9.0'
// Lifecycle // Lifecycle
def lifecycle_version = "2.5.1" def lifecycle_version = "2.5.1"

View file

@ -30,6 +30,7 @@ import org.oxycblt.auxio.ui.UISettings
/** /**
* A simple, rational music player for android. * A simple, rational music player for android.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@HiltAndroidApp @HiltAndroidApp

View file

@ -20,6 +20,7 @@ package org.oxycblt.auxio
/** /**
* A table containing all of the magic integer codes that the codebase has currently reserved. May * A table containing all of the magic integer codes that the codebase has currently reserved. May
* be non-contiguous. * be non-contiguous.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
object IntegerTable { object IntegerTable {

View file

@ -40,17 +40,13 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
/** /**
* Auxio's single [AppCompatActivity]. * Auxio's single [AppCompatActivity].
* *
* TODO: Add error screens
*
* TODO: Custom language support
*
* TODO: Use proper material attributes (Not the weird dimen attributes I currently have)
*
* TODO: Migrate to material animation system
*
* TODO: Unit testing
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*
* TODO: Add error screens
* TODO: Custom language support
* TODO: Use proper material attributes (Not the weird dimen attributes I currently have)
* TODO: Migrate to material animation system
* TODO: Unit testing
*/ */
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
@ -112,9 +108,10 @@ class MainActivity : AppCompatActivity() {
/** /**
* Transform an [Intent] given to [MainActivity] into a [InternalPlayer.Action] that can be used * Transform an [Intent] given to [MainActivity] into a [InternalPlayer.Action] that can be used
* in the playback system. * in the playback system.
*
* @param intent The (new) [Intent] given to this [MainActivity], or null if there is no intent. * @param intent The (new) [Intent] given to this [MainActivity], or null if there is no intent.
* @return true If the analogous [InternalPlayer.Action] to the given [Intent] was started, * @return true If the analogous [InternalPlayer.Action] to the given [Intent] was started,
* false otherwise. * false otherwise.
*/ */
private fun startIntentAction(intent: Intent?): Boolean { private fun startIntentAction(intent: Intent?): Boolean {
if (intent == null) { if (intent == null) {

View file

@ -51,6 +51,7 @@ import org.oxycblt.auxio.util.*
/** /**
* A wrapper around the home fragment that shows the playback fragment and controls the more * A wrapper around the home fragment that shows the playback fragment and controls the more
* high-level navigation features. * high-level navigation features.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint

View file

@ -47,6 +47,7 @@ import org.oxycblt.auxio.util.*
/** /**
* A [ListFragment] that shows information about an [Album]. * A [ListFragment] that shows information about an [Album].
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint

View file

@ -50,6 +50,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
/** /**
* A [ListFragment] that shows information about an [Artist]. * A [ListFragment] that shows information about an [Artist].
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint

View file

@ -44,6 +44,7 @@ import org.oxycblt.auxio.util.*
/** /**
* [ViewModel] that manages the Song, Album, Artist, and Genre detail views. Keeps track of the * [ViewModel] that manages the Song, Album, Artist, and Genre detail views. Keeps track of the
* current item they are showing, sub-data to display, and configuration. * current item they are showing, sub-data to display, and configuration.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@HiltViewModel @HiltViewModel
@ -182,6 +183,7 @@ constructor(
/** /**
* Set a new [currentSong] from it's [Music.UID]. If the [Music.UID] differs, [currentSong] and * Set a new [currentSong] from it's [Music.UID]. If the [Music.UID] differs, [currentSong] and
* [songAudioInfo] will be updated to align with the new [Song]. * [songAudioInfo] will be updated to align with the new [Song].
*
* @param uid The UID of the [Song] to load. Must be valid. * @param uid The UID of the [Song] to load. Must be valid.
*/ */
fun setSongUid(uid: Music.UID) { fun setSongUid(uid: Music.UID) {
@ -196,6 +198,7 @@ constructor(
/** /**
* Set a new [currentAlbum] from it's [Music.UID]. If the [Music.UID] differs, [currentAlbum] * Set a new [currentAlbum] from it's [Music.UID]. If the [Music.UID] differs, [currentAlbum]
* and [albumList] will be updated to align with the new [Album]. * and [albumList] will be updated to align with the new [Album].
*
* @param uid The [Music.UID] of the [Album] to update [currentAlbum] to. Must be valid. * @param uid The [Music.UID] of the [Album] to update [currentAlbum] to. Must be valid.
*/ */
fun setAlbumUid(uid: Music.UID) { fun setAlbumUid(uid: Music.UID) {
@ -210,6 +213,7 @@ constructor(
/** /**
* Set a new [currentArtist] from it's [Music.UID]. If the [Music.UID] differs, [currentArtist] * Set a new [currentArtist] from it's [Music.UID]. If the [Music.UID] differs, [currentArtist]
* and [artistList] will be updated to align with the new [Artist]. * and [artistList] will be updated to align with the new [Artist].
*
* @param uid The [Music.UID] of the [Artist] to update [currentArtist] to. Must be valid. * @param uid The [Music.UID] of the [Artist] to update [currentArtist] to. Must be valid.
*/ */
fun setArtistUid(uid: Music.UID) { fun setArtistUid(uid: Music.UID) {
@ -224,6 +228,7 @@ constructor(
/** /**
* Set a new [currentGenre] from it's [Music.UID]. If the [Music.UID] differs, [currentGenre] * Set a new [currentGenre] from it's [Music.UID]. If the [Music.UID] differs, [currentGenre]
* and [genreList] will be updated to align with the new album. * and [genreList] will be updated to align with the new album.
*
* @param uid The [Music.UID] of the [Genre] to update [currentGenre] to. Must be valid. * @param uid The [Music.UID] of the [Genre] to update [currentGenre] to. Must be valid.
*/ */
fun setGenreUid(uid: Music.UID) { fun setGenreUid(uid: Music.UID) {
@ -239,6 +244,7 @@ constructor(
/** /**
* Start a new job to load a given [Song]'s [AudioInfo]. Result is pushed to [songAudioInfo]. * Start a new job to load a given [Song]'s [AudioInfo]. Result is pushed to [songAudioInfo].
*
* @param song The song to load. * @param song The song to load.
*/ */
private fun refreshAudioInfo(song: Song) { private fun refreshAudioInfo(song: Song) {
@ -333,8 +339,9 @@ constructor(
/** /**
* A simpler mapping of [ReleaseType] used for grouping and sorting songs. * A simpler mapping of [ReleaseType] used for grouping and sorting songs.
*
* @param headerTitleRes The title string resource to use for a header created out of an * @param headerTitleRes The title string resource to use for a header created out of an
* instance of this enum. * instance of this enum.
*/ */
private enum class AlbumGrouping(@StringRes val headerTitleRes: Int) { private enum class AlbumGrouping(@StringRes val headerTitleRes: Int) {
ALBUMS(R.string.lbl_albums), ALBUMS(R.string.lbl_albums),

View file

@ -51,6 +51,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
/** /**
* A [ListFragment] that shows information for a particular [Genre]. * A [ListFragment] that shows information for a particular [Genre].
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint

View file

@ -42,6 +42,7 @@ import org.oxycblt.auxio.util.concatLocalized
/** /**
* A [ViewBindingDialogFragment] that shows information about a Song. * A [ViewBindingDialogFragment] that shows information about a Song.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint

View file

@ -43,6 +43,7 @@ import org.oxycblt.auxio.util.inflater
/** /**
* An [DetailAdapter] implementing the header and sub-items for the [Album] detail view. * An [DetailAdapter] implementing the header and sub-items for the [Album] detail view.
*
* @param listener A [Listener] to bind interactions to. * @param listener A [Listener] to bind interactions to.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@ -118,6 +119,7 @@ class AlbumDetailAdapter(private val listener: Listener) : DetailAdapter(listene
/** /**
* A [RecyclerView.ViewHolder] that displays the [Album] header in the detail view. Use [from] to * A [RecyclerView.ViewHolder] that displays the [Album] header in the detail view. Use [from] to
* create an instance. * create an instance.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
private class AlbumDetailViewHolder private constructor(private val binding: ItemDetailBinding) : private class AlbumDetailViewHolder private constructor(private val binding: ItemDetailBinding) :
@ -125,6 +127,7 @@ private class AlbumDetailViewHolder private constructor(private val binding: Ite
/** /**
* Bind new data to this instance. * Bind new data to this instance.
*
* @param album The new [Album] to bind. * @param album The new [Album] to bind.
* @param listener A [AlbumDetailAdapter.Listener] to bind interactions to. * @param listener A [AlbumDetailAdapter.Listener] to bind interactions to.
*/ */
@ -164,6 +167,7 @@ private class AlbumDetailViewHolder private constructor(private val binding: Ite
/** /**
* Create a new instance. * Create a new instance.
*
* @param parent The parent to inflate this instance from. * @param parent The parent to inflate this instance from.
* @return A new instance. * @return A new instance.
*/ */
@ -187,12 +191,14 @@ private class AlbumDetailViewHolder private constructor(private val binding: Ite
/** /**
* A [RecyclerView.ViewHolder] that displays a [Disc] to delimit different disc groups. Use [from] * A [RecyclerView.ViewHolder] that displays a [Disc] to delimit different disc groups. Use [from]
* to create an instance. * to create an instance.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
private class DiscViewHolder(private val binding: ItemDiscHeaderBinding) : private class DiscViewHolder(private val binding: ItemDiscHeaderBinding) :
RecyclerView.ViewHolder(binding.root) { RecyclerView.ViewHolder(binding.root) {
/** /**
* Bind new data to this instance. * Bind new data to this instance.
*
* @param disc The new [disc] to bind. * @param disc The new [disc] to bind.
*/ */
fun bind(disc: Disc) { fun bind(disc: Disc) {
@ -209,6 +215,7 @@ private class DiscViewHolder(private val binding: ItemDiscHeaderBinding) :
/** /**
* Create a new instance. * Create a new instance.
*
* @param parent The parent to inflate this instance from. * @param parent The parent to inflate this instance from.
* @return A new instance. * @return A new instance.
*/ */
@ -227,12 +234,14 @@ private class DiscViewHolder(private val binding: ItemDiscHeaderBinding) :
/** /**
* A [RecyclerView.ViewHolder] that displays a [Song] in the context of an [Album]. Use [from] to * A [RecyclerView.ViewHolder] that displays a [Song] in the context of an [Album]. Use [from] to
* create an instance. * create an instance.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
private class AlbumSongViewHolder private constructor(private val binding: ItemAlbumSongBinding) : private class AlbumSongViewHolder private constructor(private val binding: ItemAlbumSongBinding) :
SelectionIndicatorAdapter.ViewHolder(binding.root) { SelectionIndicatorAdapter.ViewHolder(binding.root) {
/** /**
* Bind new data to this instance. * Bind new data to this instance.
*
* @param song The new [Song] to bind. * @param song The new [Song] to bind.
* @param listener A [SelectableListListener] to bind interactions to. * @param listener A [SelectableListListener] to bind interactions to.
*/ */
@ -276,6 +285,7 @@ private class AlbumSongViewHolder private constructor(private val binding: ItemA
/** /**
* Create a new instance. * Create a new instance.
*
* @param parent The parent to inflate this instance from. * @param parent The parent to inflate this instance from.
* @return A new instance. * @return A new instance.
*/ */

View file

@ -37,6 +37,7 @@ import org.oxycblt.auxio.util.inflater
/** /**
* A [DetailAdapter] implementing the header and sub-items for the [Artist] detail view. * A [DetailAdapter] implementing the header and sub-items for the [Artist] detail view.
*
* @param listener A [DetailAdapter.Listener] to bind interactions to. * @param listener A [DetailAdapter.Listener] to bind interactions to.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@ -100,6 +101,7 @@ class ArtistDetailAdapter(private val listener: Listener<Music>) :
/** /**
* A [RecyclerView.ViewHolder] that displays the [Artist] header in the detail view. Use [from] to * A [RecyclerView.ViewHolder] that displays the [Artist] header in the detail view. Use [from] to
* create an instance. * create an instance.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
private class ArtistDetailViewHolder private constructor(private val binding: ItemDetailBinding) : private class ArtistDetailViewHolder private constructor(private val binding: ItemDetailBinding) :
@ -107,6 +109,7 @@ private class ArtistDetailViewHolder private constructor(private val binding: It
/** /**
* Bind new data to this instance. * Bind new data to this instance.
*
* @param artist The new [Artist] to bind. * @param artist The new [Artist] to bind.
* @param listener A [DetailAdapter.Listener] to bind interactions to. * @param listener A [DetailAdapter.Listener] to bind interactions to.
*/ */
@ -154,6 +157,7 @@ private class ArtistDetailViewHolder private constructor(private val binding: It
/** /**
* Create a new instance. * Create a new instance.
*
* @param parent The parent to inflate this instance from. * @param parent The parent to inflate this instance from.
* @return A new instance. * @return A new instance.
*/ */
@ -175,12 +179,14 @@ private class ArtistDetailViewHolder private constructor(private val binding: It
/** /**
* A [RecyclerView.ViewHolder] that displays an [Album] in the context of an [Artist]. Use [from] to * A [RecyclerView.ViewHolder] that displays an [Album] in the context of an [Artist]. Use [from] to
* create an instance. * create an instance.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
private class ArtistAlbumViewHolder private constructor(private val binding: ItemParentBinding) : private class ArtistAlbumViewHolder private constructor(private val binding: ItemParentBinding) :
SelectionIndicatorAdapter.ViewHolder(binding.root) { SelectionIndicatorAdapter.ViewHolder(binding.root) {
/** /**
* Bind new data to this instance. * Bind new data to this instance.
*
* @param album The new [Album] to bind. * @param album The new [Album] to bind.
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
*/ */
@ -209,6 +215,7 @@ private class ArtistAlbumViewHolder private constructor(private val binding: Ite
/** /**
* Create a new instance. * Create a new instance.
*
* @param parent The parent to inflate this instance from. * @param parent The parent to inflate this instance from.
* @return A new instance. * @return A new instance.
*/ */
@ -227,12 +234,14 @@ private class ArtistAlbumViewHolder private constructor(private val binding: Ite
/** /**
* A [RecyclerView.ViewHolder] that displays a [Song] in the context of an [Artist]. Use [from] to * A [RecyclerView.ViewHolder] that displays a [Song] in the context of an [Artist]. Use [from] to
* create an instance. * create an instance.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
private class ArtistSongViewHolder private constructor(private val binding: ItemSongBinding) : private class ArtistSongViewHolder private constructor(private val binding: ItemSongBinding) :
SelectionIndicatorAdapter.ViewHolder(binding.root) { SelectionIndicatorAdapter.ViewHolder(binding.root) {
/** /**
* Bind new data to this instance. * Bind new data to this instance.
*
* @param song The new [Song] to bind. * @param song The new [Song] to bind.
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
*/ */
@ -258,6 +267,7 @@ private class ArtistSongViewHolder private constructor(private val binding: Item
/** /**
* Create a new instance. * Create a new instance.
*
* @param parent The parent to inflate this instance from. * @param parent The parent to inflate this instance from.
* @return A new instance. * @return A new instance.
*/ */

View file

@ -37,9 +37,10 @@ import org.oxycblt.auxio.util.inflater
/** /**
* A [RecyclerView.Adapter] that implements behavior shared across each detail view's adapters. * A [RecyclerView.Adapter] that implements behavior shared across each detail view's adapters.
*
* @param listener A [Listener] to bind interactions to. * @param listener A [Listener] to bind interactions to.
* @param diffCallback A [DiffUtil.ItemCallback] to use for item comparison when diffing the * @param diffCallback A [DiffUtil.ItemCallback] to use for item comparison when diffing the
* internal list. * internal list.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
abstract class DetailAdapter( abstract class DetailAdapter(
@ -119,6 +120,7 @@ abstract class DetailAdapter(
/** /**
* A header variation that displays a button to open a sort menu. * A header variation that displays a button to open a sort menu.
*
* @param titleRes The string resource to use as the header title * @param titleRes The string resource to use as the header title
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@ -127,12 +129,14 @@ data class SortHeader(@StringRes override val titleRes: Int) : Header
/** /**
* A [RecyclerView.ViewHolder] that displays a [SortHeader], a variation on [BasicHeader] that adds * A [RecyclerView.ViewHolder] that displays a [SortHeader], a variation on [BasicHeader] that adds
* a button opening a menu for sorting. Use [from] to create an instance. * a button opening a menu for sorting. Use [from] to create an instance.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
private class SortHeaderViewHolder(private val binding: ItemSortHeaderBinding) : private class SortHeaderViewHolder(private val binding: ItemSortHeaderBinding) :
RecyclerView.ViewHolder(binding.root) { RecyclerView.ViewHolder(binding.root) {
/** /**
* Bind new data to this instance. * Bind new data to this instance.
*
* @param sortHeader The new [SortHeader] to bind. * @param sortHeader The new [SortHeader] to bind.
* @param listener An [DetailAdapter.Listener] to bind interactions to. * @param listener An [DetailAdapter.Listener] to bind interactions to.
*/ */
@ -152,6 +156,7 @@ private class SortHeaderViewHolder(private val binding: ItemSortHeaderBinding) :
/** /**
* Create a new instance. * Create a new instance.
*
* @param parent The parent to inflate this instance from. * @param parent The parent to inflate this instance from.
* @return A new instance. * @return A new instance.
*/ */

View file

@ -38,6 +38,7 @@ import org.oxycblt.auxio.util.inflater
/** /**
* An [DetailAdapter] implementing the header and sub-items for the [Genre] detail view. * An [DetailAdapter] implementing the header and sub-items for the [Genre] detail view.
*
* @param listener A [DetailAdapter.Listener] to bind interactions to. * @param listener A [DetailAdapter.Listener] to bind interactions to.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@ -100,12 +101,14 @@ class GenreDetailAdapter(private val listener: Listener<Music>) :
/** /**
* A [RecyclerView.ViewHolder] that displays the [Genre] header in the detail view. Use [from] to * A [RecyclerView.ViewHolder] that displays the [Genre] header in the detail view. Use [from] to
* create an instance. * create an instance.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
private class GenreDetailViewHolder private constructor(private val binding: ItemDetailBinding) : private class GenreDetailViewHolder private constructor(private val binding: ItemDetailBinding) :
RecyclerView.ViewHolder(binding.root) { RecyclerView.ViewHolder(binding.root) {
/** /**
* Bind new data to this instance. * Bind new data to this instance.
*
* @param genre The new [Song] to bind. * @param genre The new [Song] to bind.
* @param listener A [DetailAdapter.Listener] to bind interactions to. * @param listener A [DetailAdapter.Listener] to bind interactions to.
*/ */
@ -131,6 +134,7 @@ private class GenreDetailViewHolder private constructor(private val binding: Ite
/** /**
* Create a new instance. * Create a new instance.
*
* @param parent The parent to inflate this instance from. * @param parent The parent to inflate this instance from.
* @return A new instance. * @return A new instance.
*/ */

View file

@ -33,6 +33,7 @@ import org.oxycblt.auxio.util.inflater
/** /**
* An adapter for [SongProperty] instances. * An adapter for [SongProperty] instances.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class SongPropertyAdapter : class SongPropertyAdapter :
@ -48,6 +49,7 @@ class SongPropertyAdapter :
/** /**
* A property entry for use in [SongPropertyAdapter]. * A property entry for use in [SongPropertyAdapter].
*
* @param name The contextual title to use for the property. * @param name The contextual title to use for the property.
* @param value The value of the property. * @param value The value of the property.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
@ -56,6 +58,7 @@ data class SongProperty(@StringRes val name: Int, val value: String) : Item
/** /**
* A [RecyclerView.ViewHolder] that displays a [SongProperty]. Use [from] to create an instance. * A [RecyclerView.ViewHolder] that displays a [SongProperty]. Use [from] to create an instance.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class SongPropertyViewHolder private constructor(private val binding: ItemSongPropertyBinding) : class SongPropertyViewHolder private constructor(private val binding: ItemSongPropertyBinding) :
@ -69,6 +72,7 @@ class SongPropertyViewHolder private constructor(private val binding: ItemSongPr
companion object { companion object {
/** /**
* Create a new instance. * Create a new instance.
*
* @param parent The parent to inflate this instance from. * @param parent The parent to inflate this instance from.
* @return A new instance. * @return A new instance.
*/ */

View file

@ -27,6 +27,7 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
/** /**
* A [FrameLayout] that automatically applies bottom insets. * A [FrameLayout] that automatically applies bottom insets.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class EdgeFrameLayout class EdgeFrameLayout

View file

@ -64,6 +64,7 @@ import org.oxycblt.auxio.util.*
/** /**
* The starting [SelectionFragment] of Auxio. Shows the user's music library and enables navigation * The starting [SelectionFragment] of Auxio. Shows the user's music library and enables navigation
* to other views. * to other views.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint
@ -483,10 +484,11 @@ class HomeFragment :
/** /**
* [FragmentStateAdapter] implementation for the [HomeFragment]'s [ViewPager2] instance. * [FragmentStateAdapter] implementation for the [HomeFragment]'s [ViewPager2] instance.
*
* @param tabs The current tab configuration. This will define the [Fragment]s created. * @param tabs The current tab configuration. This will define the [Fragment]s created.
* @param fragmentManager The [FragmentManager] required by [FragmentStateAdapter]. * @param fragmentManager The [FragmentManager] required by [FragmentStateAdapter].
* @param lifecycleOwner The [LifecycleOwner], whose Lifecycle is required by * @param lifecycleOwner The [LifecycleOwner], whose Lifecycle is required by
* [FragmentStateAdapter]. * [FragmentStateAdapter].
*/ */
private class HomePagerAdapter( private class HomePagerAdapter(
private val tabs: List<MusicMode>, private val tabs: List<MusicMode>,

View file

@ -28,6 +28,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
/** /**
* User configuration specific to the home UI. * User configuration specific to the home UI.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface HomeSettings : Settings<HomeSettings.Listener> { interface HomeSettings : Settings<HomeSettings.Listener> {

View file

@ -31,6 +31,7 @@ import org.oxycblt.auxio.util.logD
/** /**
* The ViewModel for managing the tab data and lists of the home view. * The ViewModel for managing the tab data and lists of the home view.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@HiltViewModel @HiltViewModel
@ -138,6 +139,7 @@ constructor(
/** /**
* Get the preferred [Sort] for a given [Tab]. * Get the preferred [Sort] for a given [Tab].
*
* @param tabMode The [MusicMode] of the [Tab] desired. * @param tabMode The [MusicMode] of the [Tab] desired.
* @return The [Sort] preferred for that [Tab] * @return The [Sort] preferred for that [Tab]
*/ */
@ -151,6 +153,7 @@ constructor(
/** /**
* Update the preferred [Sort] for the current [Tab]. Will update corresponding list. * Update the preferred [Sort] for the current [Tab]. Will update corresponding list.
*
* @param sort The new [Sort] to apply. Assumed to be an allowed sort for the current [Tab]. * @param sort The new [Sort] to apply. Assumed to be an allowed sort for the current [Tab].
*/ */
fun setSortForCurrentTab(sort: Sort) { fun setSortForCurrentTab(sort: Sort) {
@ -178,6 +181,7 @@ constructor(
/** /**
* Update [currentTabMode] to reflect a new ViewPager2 position * Update [currentTabMode] to reflect a new ViewPager2 position
*
* @param pagerPos The new position of the ViewPager2 instance. * @param pagerPos The new position of the ViewPager2 instance.
*/ */
fun synchronizeTabPosition(pagerPos: Int) { fun synchronizeTabPosition(pagerPos: Int) {
@ -187,6 +191,7 @@ constructor(
/** /**
* Mark the recreation process as complete. * Mark the recreation process as complete.
*
* @see shouldRecreate * @see shouldRecreate
*/ */
fun finishRecreate() { fun finishRecreate() {
@ -195,6 +200,7 @@ constructor(
/** /**
* Update whether the user is fast scrolling or not in the home view. * Update whether the user is fast scrolling or not in the home view.
*
* @param isFastScrolling true if the user is currently fast scrolling, false otherwise. * @param isFastScrolling true if the user is currently fast scrolling, false otherwise.
*/ */
fun setFastScrolling(isFastScrolling: Boolean) { fun setFastScrolling(isFastScrolling: Boolean) {
@ -204,8 +210,9 @@ constructor(
/** /**
* Create a list of [MusicMode]s representing a simpler version of the [Tab] configuration. * Create a list of [MusicMode]s representing a simpler version of the [Tab] configuration.
*
* @return A list of the [MusicMode]s for each visible [Tab] in the configuration, ordered in * @return A list of the [MusicMode]s for each visible [Tab] in the configuration, ordered in
* the same way as the configuration. * the same way as the configuration.
*/ */
private fun makeTabModes() = private fun makeTabModes() =
homeSettings.homeTabs.filterIsInstance<Tab.Visible>().map { it.mode } homeSettings.homeTabs.filterIsInstance<Tab.Visible>().map { it.mode }

View file

@ -40,6 +40,7 @@ import org.oxycblt.auxio.util.isRtl
/** /**
* A [MaterialTextView] that displays the popup indicator used in FastScrollRecyclerView * A [MaterialTextView] that displays the popup indicator used in FastScrollRecyclerView
*
* @author Alexander Capehart (OxygenCobalt), Hai Zhang * @author Alexander Capehart (OxygenCobalt), Hai Zhang
*/ */
class FastScrollPopupView class FastScrollPopupView

View file

@ -49,7 +49,7 @@ import org.oxycblt.auxio.util.*
* *
* !!! MODIFICATIONS !!!: * !!! MODIFICATIONS !!!:
* - Scroller will no longer show itself on startup or relayouts, which looked unpleasant with * - Scroller will no longer show itself on startup or relayouts, which looked unpleasant with
* multiple views * multiple views
* - DefaultAnimationHelper and RecyclerViewHelper were merged into the class * - DefaultAnimationHelper and RecyclerViewHelper were merged into the class
* - FastScroller overlay was merged into RecyclerView instance * - FastScroller overlay was merged into RecyclerView instance
* - Removed FastScrollerBuilder * - Removed FastScrollerBuilder
@ -61,11 +61,10 @@ import org.oxycblt.auxio.util.*
* - Added drag listener * - Added drag listener
* - Added documentation * - Added documentation
* *
* TODO: Add vibration when popup changes
*
* TODO: Improve support for variably sized items (Re-back with library fast scroller?)
*
* @author Hai Zhang, Alexander Capehart (OxygenCobalt) * @author Hai Zhang, Alexander Capehart (OxygenCobalt)
*
* TODO: Add vibration when popup changes
* TODO: Improve support for variably sized items (Re-back with library fast scroller?)
*/ */
class FastScrollRecyclerView class FastScrollRecyclerView
@JvmOverloads @JvmOverloads
@ -508,9 +507,10 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
interface PopupProvider { interface PopupProvider {
/** /**
* Get text to use in the popup at the specified position. * Get text to use in the popup at the specified position.
*
* @param pos The position in the list. * @param pos The position in the list.
* @return A [String] to use in the popup. Null if there is no applicable text for the popup * @return A [String] to use in the popup. Null if there is no applicable text for the popup
* at [pos]. * at [pos].
*/ */
fun getPopup(pos: Int): String? fun getPopup(pos: Int): String?
} }
@ -519,6 +519,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
interface Listener { interface Listener {
/** /**
* Called when the fast scrolling state changes. * Called when the fast scrolling state changes.
*
* @param isFastScrolling true if the user is currently fast scrolling, false otherwise. * @param isFastScrolling true if the user is currently fast scrolling, false otherwise.
*/ */
fun onFastScrollingChanged(isFastScrolling: Boolean) fun onFastScrollingChanged(isFastScrolling: Boolean)

View file

@ -46,6 +46,7 @@ import org.oxycblt.auxio.util.collectImmediately
/** /**
* A [ListFragment] that shows a list of [Album]s. * A [ListFragment] that shows a list of [Album]s.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint
@ -154,6 +155,7 @@ class AlbumListFragment :
/** /**
* A [SelectionIndicatorAdapter] that shows a list of [Album]s using [AlbumViewHolder]. * A [SelectionIndicatorAdapter] that shows a list of [Album]s using [AlbumViewHolder].
*
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
*/ */
private class AlbumAdapter(private val listener: SelectableListListener<Album>) : private class AlbumAdapter(private val listener: SelectableListListener<Album>) :

View file

@ -47,6 +47,7 @@ import org.oxycblt.auxio.util.nonZeroOrNull
/** /**
* A [ListFragment] that shows a list of [Artist]s. * A [ListFragment] that shows a list of [Artist]s.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint
@ -132,6 +133,7 @@ class ArtistListFragment :
/** /**
* A [SelectionIndicatorAdapter] that shows a list of [Artist]s using [ArtistViewHolder]. * A [SelectionIndicatorAdapter] that shows a list of [Artist]s using [ArtistViewHolder].
*
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
*/ */
private class ArtistAdapter(private val listener: SelectableListListener<Artist>) : private class ArtistAdapter(private val listener: SelectableListListener<Artist>) :

View file

@ -46,6 +46,7 @@ import org.oxycblt.auxio.util.collectImmediately
/** /**
* A [ListFragment] that shows a list of [Genre]s. * A [ListFragment] that shows a list of [Genre]s.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint
@ -131,6 +132,7 @@ class GenreListFragment :
/** /**
* A [SelectionIndicatorAdapter] that shows a list of [Genre]s using [GenreViewHolder]. * A [SelectionIndicatorAdapter] that shows a list of [Genre]s using [GenreViewHolder].
*
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
*/ */
private class GenreAdapter(private val listener: SelectableListListener<Genre>) : private class GenreAdapter(private val listener: SelectableListListener<Genre>) :

View file

@ -49,6 +49,7 @@ import org.oxycblt.auxio.util.collectImmediately
/** /**
* A [ListFragment] that shows a list of [Song]s. * A [ListFragment] that shows a list of [Song]s.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint
@ -165,6 +166,7 @@ class SongListFragment :
/** /**
* A [SelectionIndicatorAdapter] that shows a list of [Song]s using [SongViewHolder]. * A [SelectionIndicatorAdapter] that shows a list of [Song]s using [SongViewHolder].
*
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
*/ */
private class SongAdapter(private val listener: SelectableListListener<Song>) : private class SongAdapter(private val listener: SelectableListListener<Song>) :

View file

@ -27,6 +27,7 @@ import org.oxycblt.auxio.util.logD
/** /**
* A [TabLayoutMediator.TabConfigurationStrategy] that uses larger/smaller tab configurations * A [TabLayoutMediator.TabConfigurationStrategy] that uses larger/smaller tab configurations
* depending on the screen configuration. * depending on the screen configuration.
*
* @param context [Context] required to obtain window information * @param context [Context] required to obtain window information
* @param tabs Current tab configuration from settings * @param tabs Current tab configuration from settings
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)

View file

@ -22,18 +22,21 @@ import org.oxycblt.auxio.util.logE
/** /**
* A representation of a library tab suitable for configuration. * A representation of a library tab suitable for configuration.
*
* @param mode The type of list in the home view this instance corresponds to. * @param mode The type of list in the home view this instance corresponds to.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
sealed class Tab(open val mode: MusicMode) { sealed class Tab(open val mode: MusicMode) {
/** /**
* A visible tab. This will be visible in the home and tab configuration views. * A visible tab. This will be visible in the home and tab configuration views.
*
* @param mode The type of list in the home view this instance corresponds to. * @param mode The type of list in the home view this instance corresponds to.
*/ */
data class Visible(override val mode: MusicMode) : Tab(mode) data class Visible(override val mode: MusicMode) : Tab(mode)
/** /**
* A visible tab. This will be visible in the tab configuration view, but not in the home view. * A visible tab. This will be visible in the tab configuration view, but not in the home view.
*
* @param mode The type of list in the home view this instance corresponds to. * @param mode The type of list in the home view this instance corresponds to.
*/ */
data class Invisible(override val mode: MusicMode) : Tab(mode) data class Invisible(override val mode: MusicMode) : Tab(mode)
@ -68,6 +71,7 @@ sealed class Tab(open val mode: MusicMode) {
/** /**
* Convert an array of [Tab]s into it's integer representation. * Convert an array of [Tab]s into it's integer representation.
*
* @param tabs The array of [Tab]s to convert * @param tabs The array of [Tab]s to convert
* @return An integer representation of the [Tab] array * @return An integer representation of the [Tab] array
*/ */
@ -93,6 +97,7 @@ sealed class Tab(open val mode: MusicMode) {
/** /**
* Convert a [Tab] integer representation into it's corresponding array of [Tab]s. * Convert a [Tab] integer representation into it's corresponding array of [Tab]s.
*
* @param intCode The integer representation of the [Tab]s. * @param intCode The integer representation of the [Tab]s.
* @return An array of [Tab]s corresponding to the sequence. * @return An array of [Tab]s corresponding to the sequence.
*/ */

View file

@ -30,6 +30,7 @@ import org.oxycblt.auxio.util.inflater
/** /**
* A [RecyclerView.Adapter] that displays an array of [Tab]s open for configuration. * A [RecyclerView.Adapter] that displays an array of [Tab]s open for configuration.
*
* @param listener A [EditableListListener] for tab interactions. * @param listener A [EditableListListener] for tab interactions.
*/ */
class TabAdapter(private val listener: EditableListListener<Tab>) : class TabAdapter(private val listener: EditableListListener<Tab>) :
@ -46,6 +47,7 @@ class TabAdapter(private val listener: EditableListListener<Tab>) :
/** /**
* Immediately update the tab array. This should be used when initializing the list. * Immediately update the tab array. This should be used when initializing the list.
*
* @param newTabs The new array of tabs to show. * @param newTabs The new array of tabs to show.
*/ */
fun submitTabs(newTabs: Array<Tab>) { fun submitTabs(newTabs: Array<Tab>) {
@ -55,6 +57,7 @@ class TabAdapter(private val listener: EditableListListener<Tab>) :
/** /**
* Update a specific tab to the given value. * Update a specific tab to the given value.
*
* @param at The position of the tab to update. * @param at The position of the tab to update.
* @param tab The new tab. * @param tab The new tab.
*/ */
@ -66,6 +69,7 @@ class TabAdapter(private val listener: EditableListListener<Tab>) :
/** /**
* Swap two tabs with each other. * Swap two tabs with each other.
*
* @param a The position of the first tab to swap. * @param a The position of the first tab to swap.
* @param b The position of the second tab to swap. * @param b The position of the second tab to swap.
*/ */
@ -83,12 +87,14 @@ class TabAdapter(private val listener: EditableListListener<Tab>) :
/** /**
* A [RecyclerView.ViewHolder] that displays a [Tab]. Use [from] to create an instance. * A [RecyclerView.ViewHolder] that displays a [Tab]. Use [from] to create an instance.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class TabViewHolder private constructor(private val binding: ItemTabBinding) : class TabViewHolder private constructor(private val binding: ItemTabBinding) :
DialogRecyclerView.ViewHolder(binding.root) { DialogRecyclerView.ViewHolder(binding.root) {
/** /**
* Bind new data to this instance. * Bind new data to this instance.
*
* @param tab The new [Tab] to bind. * @param tab The new [Tab] to bind.
* @param listener A [EditableListListener] to bind interactions to. * @param listener A [EditableListListener] to bind interactions to.
*/ */
@ -114,6 +120,7 @@ class TabViewHolder private constructor(private val binding: ItemTabBinding) :
companion object { companion object {
/** /**
* Create a new instance. * Create a new instance.
*
* @param parent The parent to inflate this instance from. * @param parent The parent to inflate this instance from.
* @return A new instance. * @return A new instance.
*/ */

View file

@ -34,6 +34,7 @@ import org.oxycblt.auxio.util.logD
/** /**
* A [ViewBindingDialogFragment] that allows the user to modify the home [Tab] configuration. * A [ViewBindingDialogFragment] that allows the user to modify the home [Tab] configuration.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint

View file

@ -23,6 +23,7 @@ import androidx.recyclerview.widget.RecyclerView
/** /**
* An [ItemTouchHelper.Callback] that implements dragging in the [TabAdapter]. * An [ItemTouchHelper.Callback] that implements dragging in the [TabAdapter].
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class TabDragCallback(private val adapter: TabAdapter) : ItemTouchHelper.Callback() { class TabDragCallback(private val adapter: TabAdapter) : ItemTouchHelper.Callback() {

View file

@ -55,14 +55,16 @@ constructor(
interface Target { interface Target {
/** /**
* Configure the [ImageRequest.Builder] to enable [Target]-specific configuration. * Configure the [ImageRequest.Builder] to enable [Target]-specific configuration.
*
* @param builder The [ImageRequest.Builder] that will be used to request the desired * @param builder The [ImageRequest.Builder] that will be used to request the desired
* [Bitmap]. * [Bitmap].
* @return The same [ImageRequest.Builder] in order to easily chain configuration methods. * @return The same [ImageRequest.Builder] in order to easily chain configuration methods.
*/ */
fun onConfigRequest(builder: ImageRequest.Builder): ImageRequest.Builder = builder fun onConfigRequest(builder: ImageRequest.Builder): ImageRequest.Builder = builder
/** /**
* Called when the loading process is completed. * Called when the loading process is completed.
*
* @param bitmap The loaded bitmap, or null if the bitmap could not be loaded. * @param bitmap The loaded bitmap, or null if the bitmap could not be loaded.
*/ */
fun onCompleted(bitmap: Bitmap?) fun onCompleted(bitmap: Bitmap?)
@ -77,6 +79,7 @@ constructor(
/** /**
* Load the Album cover [Bitmap] from a [Song]. * Load the Album cover [Bitmap] from a [Song].
*
* @param song The song to load a [Bitmap] of it's album cover from. * @param song The song to load a [Bitmap] of it's album cover from.
* @param target The [Target] to deliver the [Bitmap] to asynchronously. * @param target The [Target] to deliver the [Bitmap] to asynchronously.
*/ */

View file

@ -21,6 +21,7 @@ import org.oxycblt.auxio.IntegerTable
/** /**
* Represents the options available for album cover loading. * Represents the options available for album cover loading.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
enum class CoverMode { enum class CoverMode {
@ -33,6 +34,7 @@ enum class CoverMode {
/** /**
* The integer representation of this instance. * The integer representation of this instance.
*
* @see fromIntCode * @see fromIntCode
*/ */
val intCode: Int val intCode: Int
@ -46,6 +48,7 @@ enum class CoverMode {
companion object { companion object {
/** /**
* Convert a [CoverMode] integer representation into an instance. * Convert a [CoverMode] integer representation into an instance.
*
* @param intCode An integer representation of a [CoverMode] * @param intCode An integer representation of a [CoverMode]
* @return The corresponding [CoverMode], or null if the [CoverMode] is invalid. * @return The corresponding [CoverMode], or null if the [CoverMode] is invalid.
* @see CoverMode.intCode * @see CoverMode.intCode

View file

@ -48,9 +48,9 @@ import org.oxycblt.auxio.util.getInteger
* This class is primarily intended for list items. For other uses, [StyledImageView] is more * This class is primarily intended for list items. For other uses, [StyledImageView] is more
* suitable. * suitable.
* *
* TODO: Rework content descriptions here
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*
* TODO: Rework content descriptions here
*/ */
class ImageGroup class ImageGroup
@JvmOverloads @JvmOverloads
@ -146,6 +146,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
/** /**
* Bind a [Song] to the internal [StyledImageView]. * Bind a [Song] to the internal [StyledImageView].
*
* @param song The [Song] to bind to the view. * @param song The [Song] to bind to the view.
* @see StyledImageView.bind * @see StyledImageView.bind
*/ */
@ -153,6 +154,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
/** /**
* Bind a [Album] to the internal [StyledImageView]. * Bind a [Album] to the internal [StyledImageView].
*
* @param album The [Album] to bind to the view. * @param album The [Album] to bind to the view.
* @see StyledImageView.bind * @see StyledImageView.bind
*/ */
@ -160,6 +162,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
/** /**
* Bind a [Genre] to the internal [StyledImageView]. * Bind a [Genre] to the internal [StyledImageView].
*
* @param artist The [Artist] to bind to the view. * @param artist The [Artist] to bind to the view.
* @see StyledImageView.bind * @see StyledImageView.bind
*/ */
@ -167,6 +170,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
/** /**
* Bind a [Genre] to the internal [StyledImageView]. * Bind a [Genre] to the internal [StyledImageView].
*
* @param genre The [Genre] to bind to the view. * @param genre The [Genre] to bind to the view.
* @see StyledImageView.bind * @see StyledImageView.bind
*/ */

View file

@ -27,6 +27,7 @@ import org.oxycblt.auxio.util.logD
/** /**
* User configuration specific to image loading. * User configuration specific to image loading.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface ImageSettings : Settings<ImageSettings.Listener> { interface ImageSettings : Settings<ImageSettings.Listener> {

View file

@ -48,11 +48,10 @@ import org.oxycblt.auxio.util.getDrawableCompat
/** /**
* An [AppCompatImageView] with some additional styling, including: * An [AppCompatImageView] with some additional styling, including:
*
* - Tonal background * - Tonal background
* - Rounded corners based on user preferences * - Rounded corners based on user preferences
* - Built-in support for binding image data or using a static icon with the same styling as * - Built-in support for binding image data or using a static icon with the same styling as
* placeholder drawables. * placeholder drawables.
* *
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@ -97,34 +96,39 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
/** /**
* Bind a [Song]'s album cover to this view, also updating the content description. * Bind a [Song]'s album cover to this view, also updating the content description.
*
* @param song The [Song] to bind. * @param song The [Song] to bind.
*/ */
fun bind(song: Song) = bindImpl(song, R.drawable.ic_song_24, R.string.desc_album_cover) fun bind(song: Song) = bindImpl(song, R.drawable.ic_song_24, R.string.desc_album_cover)
/** /**
* Bind an [Album]'s cover to this view, also updating the content description. * Bind an [Album]'s cover to this view, also updating the content description.
*
* @param album the [Album] to bind. * @param album the [Album] to bind.
*/ */
fun bind(album: Album) = bindImpl(album, R.drawable.ic_album_24, R.string.desc_album_cover) fun bind(album: Album) = bindImpl(album, R.drawable.ic_album_24, R.string.desc_album_cover)
/** /**
* Bind an [Artist]'s image to this view, also updating the content description. * Bind an [Artist]'s image to this view, also updating the content description.
*
* @param artist the [Artist] to bind. * @param artist the [Artist] to bind.
*/ */
fun bind(artist: Artist) = bindImpl(artist, R.drawable.ic_artist_24, R.string.desc_artist_image) fun bind(artist: Artist) = bindImpl(artist, R.drawable.ic_artist_24, R.string.desc_artist_image)
/** /**
* Bind an [Genre]'s image to this view, also updating the content description. * Bind an [Genre]'s image to this view, also updating the content description.
*
* @param genre the [Genre] to bind. * @param genre the [Genre] to bind.
*/ */
fun bind(genre: Genre) = bindImpl(genre, R.drawable.ic_genre_24, R.string.desc_genre_image) fun bind(genre: Genre) = bindImpl(genre, R.drawable.ic_genre_24, R.string.desc_genre_image)
/** /**
* Internally bind a [Music]'s image to this view. * Internally bind a [Music]'s image to this view.
*
* @param music The music to find. * @param music The music to find.
* @param errorRes The error drawable resource to use if the music cannot be loaded. * @param errorRes The error drawable resource to use if the music cannot be loaded.
* @param descRes The content description string resource to use. The resource must have one * @param descRes The content description string resource to use. The resource must have one
* field for the name of the [Music]. * field for the name of the [Music].
*/ */
private fun bindImpl(music: Music, @DrawableRes errorRes: Int, @StringRes descRes: Int) { private fun bindImpl(music: Music, @DrawableRes errorRes: Int, @StringRes descRes: Int) {
val request = val request =
@ -144,6 +148,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
/** /**
* A [Drawable] wrapper that re-styles the drawable to better align with the style of * A [Drawable] wrapper that re-styles the drawable to better align with the style of
* [StyledImageView]. * [StyledImageView].
*
* @param context [Context] required for initialization. * @param context [Context] required for initialization.
* @param inner The [Drawable] to wrap. * @param inner The [Drawable] to wrap.
*/ */

View file

@ -41,6 +41,7 @@ import org.oxycblt.auxio.music.Song
/** /**
* A [Keyer] implementation for [Music] data. * A [Keyer] implementation for [Music] data.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class MusicKeyer : Keyer<Music> { class MusicKeyer : Keyer<Music> {
@ -56,6 +57,7 @@ class MusicKeyer : Keyer<Music> {
/** /**
* Generic [Fetcher] for [Album] covers. Works with both [Album] and [Song]. Use [SongFactory] or * Generic [Fetcher] for [Album] covers. Works with both [Album] and [Song]. Use [SongFactory] or
* [AlbumFactory] for instantiation. * [AlbumFactory] for instantiation.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class AlbumCoverFetcher class AlbumCoverFetcher
@ -89,6 +91,7 @@ private constructor(
/** /**
* [Fetcher] for [Artist] images. Use [Factory] for instantiation. * [Fetcher] for [Artist] images. Use [Factory] for instantiation.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class ArtistImageFetcher class ArtistImageFetcher
@ -116,6 +119,7 @@ private constructor(
/** /**
* [Fetcher] for [Genre] images. Use [Factory] for instantiation. * [Fetcher] for [Genre] images. Use [Factory] for instantiation.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class GenreImageFetcher class GenreImageFetcher
@ -141,9 +145,10 @@ private constructor(
/** /**
* Map at most N [T] items a collection into a collection of [R], ignoring [T] that cannot be * Map at most N [T] items a collection into a collection of [R], ignoring [T] that cannot be
* transformed into [R]. * transformed into [R].
*
* @param n The maximum amount of items to map. * @param n The maximum amount of items to map.
* @param transform The function that transforms data [T] from the original list into data [R] in * @param transform The function that transforms data [T] from the original list into data [R] in
* the new list. Can return null if the [T] cannot be transformed into an [R]. * the new list. Can return null if the [T] cannot be transformed into an [R].
* @return A new list of at most N non-null [R] items. * @return A new list of at most N non-null [R] items.
*/ */
private inline fun <T : Any, R : Any> Collection<T>.mapAtMostNotNull( private inline fun <T : Any, R : Any> Collection<T>.mapAtMostNotNull(

View file

@ -38,16 +38,18 @@ import org.oxycblt.auxio.util.logW
/** /**
* Internal utilities for loading album covers. * Internal utilities for loading album covers.
*
* @author Alexander Capehart (OxygenCobalt). * @author Alexander Capehart (OxygenCobalt).
*/ */
object Covers { object Covers {
/** /**
* Fetch an album cover, respecting the current cover configuration. * Fetch an album cover, respecting the current cover configuration.
*
* @param context [Context] required to load the image. * @param context [Context] required to load the image.
* @param imageSettings [ImageSettings] required to obtain configuration information. * @param imageSettings [ImageSettings] required to obtain configuration information.
* @param album [Album] to load the cover from. * @param album [Album] to load the cover from.
* @return An [InputStream] of image data if the cover loading was successful, null if the cover * @return An [InputStream] of image data if the cover loading was successful, null if the cover
* loading failed or should not occur. * loading failed or should not occur.
*/ */
suspend fun fetch(context: Context, imageSettings: ImageSettings, album: Album): InputStream? { suspend fun fetch(context: Context, imageSettings: ImageSettings, album: Album): InputStream? {
return try { return try {
@ -67,7 +69,7 @@ object Covers {
* order: * order:
* - [MediaMetadataRetriever], as it has the best support and speed. * - [MediaMetadataRetriever], as it has the best support and speed.
* - ExoPlayer's [MetadataRetriever], as some devices (notably Samsung) can have broken * - ExoPlayer's [MetadataRetriever], as some devices (notably Samsung) can have broken
* [MediaMetadataRetriever] implementations. * [MediaMetadataRetriever] implementations.
* - MediaStore, as a last-ditch fallback if the format is really obscure. * - MediaStore, as a last-ditch fallback if the format is really obscure.
* *
* @param context [Context] required to load the image. * @param context [Context] required to load the image.
@ -80,6 +82,7 @@ object Covers {
/** /**
* Loads an album cover with [MediaMetadataRetriever]. * Loads an album cover with [MediaMetadataRetriever].
*
* @param context [Context] required to load the image. * @param context [Context] required to load the image.
* @param album [Album] to load the cover from. * @param album [Album] to load the cover from.
* @return An [InputStream] of image data if the cover loading was successful, null otherwise. * @return An [InputStream] of image data if the cover loading was successful, null otherwise.
@ -99,6 +102,7 @@ object Covers {
/** /**
* Loads an [Album] cover with ExoPlayer's [MetadataRetriever]. * Loads an [Album] cover with ExoPlayer's [MetadataRetriever].
*
* @param context [Context] required to load the image. * @param context [Context] required to load the image.
* @param album [Album] to load the cover from. * @param album [Album] to load the cover from.
* @return An [InputStream] of image data if the cover loading was successful, null otherwise. * @return An [InputStream] of image data if the cover loading was successful, null otherwise.
@ -173,6 +177,7 @@ object Covers {
/** /**
* Loads an [Album] cover from MediaStore. * Loads an [Album] cover from MediaStore.
*
* @param context [Context] required to load the image. * @param context [Context] required to load the image.
* @param album [Album] to load the cover from. * @param album [Album] to load the cover from.
* @return An [InputStream] of image data if the cover loading was successful, null otherwise. * @return An [InputStream] of image data if the cover loading was successful, null otherwise.

View file

@ -27,6 +27,7 @@ import coil.transition.TransitionTarget
/** /**
* A copy of [CrossfadeTransition.Factory] that also applies a transition to error results. * A copy of [CrossfadeTransition.Factory] that also applies a transition to error results.
*
* @author Coil Team, Alexander Capehart (OxygenCobalt) * @author Coil Team, Alexander Capehart (OxygenCobalt)
*/ */
class ErrorCrossfadeTransitionFactory : Transition.Factory { class ErrorCrossfadeTransitionFactory : Transition.Factory {

View file

@ -37,12 +37,14 @@ import okio.source
/** /**
* Utilities for constructing Artist and Genre images. * Utilities for constructing Artist and Genre images.
*
* @author Alexander Capehart (OxygenCobalt), Karim Abou Zeid * @author Alexander Capehart (OxygenCobalt), Karim Abou Zeid
*/ */
object Images { object Images {
/** /**
* Create a mosaic image from the given image [InputStream]s. Derived from phonograph: * Create a mosaic image from the given image [InputStream]s. Derived from phonograph:
* https://github.com/kabouzeid/Phonograph * https://github.com/kabouzeid/Phonograph
*
* @param context [Context] required to generate the mosaic. * @param context [Context] required to generate the mosaic.
* @param streams [InputStream]s of image data to create the mosaic out of. * @param streams [InputStream]s of image data to create the mosaic out of.
* @param size [Size] of the Mosaic to generate. * @param size [Size] of the Mosaic to generate.
@ -104,8 +106,9 @@ object Images {
/** /**
* Get an image dimension suitable to create a mosaic with. * Get an image dimension suitable to create a mosaic with.
*
* @return A pixel dimension derived from the given [Dimension] that will always be even, * @return A pixel dimension derived from the given [Dimension] that will always be even,
* allowing it to be sub-divided. * allowing it to be sub-divided.
*/ */
private fun Dimension.mosaicSize(): Int { private fun Dimension.mosaicSize(): Int {
val size = pxOrElse { 512 } val size = pxOrElse { 512 }

View file

@ -26,6 +26,7 @@ import kotlin.math.min
/** /**
* A transformation that performs a center crop-style transformation on an image. Allowing this * A transformation that performs a center crop-style transformation on an image. Allowing this
* behavior to be intrinsic without any view configuration. * behavior to be intrinsic without any view configuration.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class SquareFrameTransform : Transformation { class SquareFrameTransform : Transformation {

View file

@ -24,6 +24,7 @@ interface Item
/** /**
* A "header" used for delimiting groups of data. * A "header" used for delimiting groups of data.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface Header : Item { interface Header : Item {
@ -33,6 +34,7 @@ interface Header : Item {
/** /**
* A basic header with no additional actions. * A basic header with no additional actions.
*
* @param titleRes The string resource used for the header's title. * @param titleRes The string resource used for the header's title.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */

View file

@ -36,6 +36,7 @@ import org.oxycblt.auxio.util.showToast
/** /**
* A Fragment containing a selectable list. * A Fragment containing a selectable list.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
abstract class ListFragment<in T : Music, VB : ViewBinding> : abstract class ListFragment<in T : Music, VB : ViewBinding> :
@ -52,6 +53,7 @@ abstract class ListFragment<in T : Music, VB : ViewBinding> :
/** /**
* Called when [onClick] is called, but does not result in the item being selected. This more or * Called when [onClick] is called, but does not result in the item being selected. This more or
* less corresponds to an [onClick] implementation in a non-[ListFragment]. * less corresponds to an [onClick] implementation in a non-[ListFragment].
*
* @param item The [T] data of the item that was clicked. * @param item The [T] data of the item that was clicked.
*/ */
abstract fun onRealClick(item: T) abstract fun onRealClick(item: T)
@ -73,6 +75,7 @@ abstract class ListFragment<in T : Music, VB : ViewBinding> :
/** /**
* Opens a menu in the context of a [Song]. This menu will be managed by the Fragment and closed * Opens a menu in the context of a [Song]. This menu will be managed by the Fragment and closed
* when the view is destroyed. If a menu is already opened, this call is ignored. * when the view is destroyed. If a menu is already opened, this call is ignored.
*
* @param anchor The [View] to anchor the menu to. * @param anchor The [View] to anchor the menu to.
* @param menuRes The resource of the menu to load. * @param menuRes The resource of the menu to load.
* @param song The [Song] to create the menu for. * @param song The [Song] to create the menu for.
@ -111,6 +114,7 @@ abstract class ListFragment<in T : Music, VB : ViewBinding> :
/** /**
* Opens a menu in the context of a [Album]. This menu will be managed by the Fragment and * Opens a menu in the context of a [Album]. This menu will be managed by the Fragment and
* closed when the view is destroyed. If a menu is already opened, this call is ignored. * closed when the view is destroyed. If a menu is already opened, this call is ignored.
*
* @param anchor The [View] to anchor the menu to. * @param anchor The [View] to anchor the menu to.
* @param menuRes The resource of the menu to load. * @param menuRes The resource of the menu to load.
* @param album The [Album] to create the menu for. * @param album The [Album] to create the menu for.
@ -147,6 +151,7 @@ abstract class ListFragment<in T : Music, VB : ViewBinding> :
/** /**
* Opens a menu in the context of a [Artist]. This menu will be managed by the Fragment and * Opens a menu in the context of a [Artist]. This menu will be managed by the Fragment and
* closed when the view is destroyed. If a menu is already opened, this call is ignored. * closed when the view is destroyed. If a menu is already opened, this call is ignored.
*
* @param anchor The [View] to anchor the menu to. * @param anchor The [View] to anchor the menu to.
* @param menuRes The resource of the menu to load. * @param menuRes The resource of the menu to load.
* @param artist The [Artist] to create the menu for. * @param artist The [Artist] to create the menu for.
@ -180,6 +185,7 @@ abstract class ListFragment<in T : Music, VB : ViewBinding> :
/** /**
* Opens a menu in the context of a [Genre]. This menu will be managed by the Fragment and * Opens a menu in the context of a [Genre]. This menu will be managed by the Fragment and
* closed when the view is destroyed. If a menu is already opened, this call is ignored. * closed when the view is destroyed. If a menu is already opened, this call is ignored.
*
* @param anchor The [View] to anchor the menu to. * @param anchor The [View] to anchor the menu to.
* @param menuRes The resource of the menu to load. * @param menuRes The resource of the menu to load.
* @param genre The [Genre] to create the menu for. * @param genre The [Genre] to create the menu for.
@ -226,6 +232,7 @@ abstract class ListFragment<in T : Music, VB : ViewBinding> :
/** /**
* Open a menu. This menu will be managed by the Fragment and closed when the view is destroyed. * Open a menu. This menu will be managed by the Fragment and closed when the view is destroyed.
* If a menu is already opened, this call is ignored. * If a menu is already opened, this call is ignored.
*
* @param anchor The [View] to anchor the menu to. * @param anchor The [View] to anchor the menu to.
* @param menuRes The resource of the menu to load. * @param menuRes The resource of the menu to load.
* @param block A block that is ran within [PopupMenu] that allows further configuration. * @param block A block that is ran within [PopupMenu] that allows further configuration.

View file

@ -23,11 +23,13 @@ import androidx.recyclerview.widget.RecyclerView
/** /**
* A basic listener for list interactions. * A basic listener for list interactions.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface ClickableListListener<in T> { interface ClickableListListener<in T> {
/** /**
* Called when an item in the list is clicked. * Called when an item in the list is clicked.
*
* @param item The [T] item that was clicked. * @param item The [T] item that was clicked.
* @param viewHolder The [RecyclerView.ViewHolder] of the item that was clicked. * @param viewHolder The [RecyclerView.ViewHolder] of the item that was clicked.
*/ */
@ -35,10 +37,11 @@ interface ClickableListListener<in T> {
/** /**
* Binds this instance to a list item. * Binds this instance to a list item.
*
* @param item The [T] to bind this item to. * @param item The [T] to bind this item to.
* @param viewHolder The [RecyclerView.ViewHolder] of the item that was clicked. * @param viewHolder The [RecyclerView.ViewHolder] of the item that was clicked.
* @param bodyView The [View] containing the main body of the list item. Any click events on * @param bodyView The [View] containing the main body of the list item. Any click events on
* this [View] are routed to the listener. Defaults to the root view. * this [View] are routed to the listener. Defaults to the root view.
*/ */
fun bind(item: T, viewHolder: RecyclerView.ViewHolder, bodyView: View = viewHolder.itemView) { fun bind(item: T, viewHolder: RecyclerView.ViewHolder, bodyView: View = viewHolder.itemView) {
bodyView.setOnClickListener { onClick(item, viewHolder) } bodyView.setOnClickListener { onClick(item, viewHolder) }
@ -47,21 +50,24 @@ interface ClickableListListener<in T> {
/** /**
* An extension of [ClickableListListener] that enables list editing functionality. * An extension of [ClickableListListener] that enables list editing functionality.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface EditableListListener<in T> : ClickableListListener<T> { interface EditableListListener<in T> : ClickableListListener<T> {
/** /**
* Called when a [RecyclerView.ViewHolder] requests that it should be dragged. * Called when a [RecyclerView.ViewHolder] requests that it should be dragged.
*
* @param viewHolder The [RecyclerView.ViewHolder] that should start being dragged. * @param viewHolder The [RecyclerView.ViewHolder] that should start being dragged.
*/ */
fun onPickUp(viewHolder: RecyclerView.ViewHolder) fun onPickUp(viewHolder: RecyclerView.ViewHolder)
/** /**
* Binds this instance to a list item. * Binds this instance to a list item.
*
* @param item The [T] to bind this item to. * @param item The [T] to bind this item to.
* @param viewHolder The [RecyclerView.ViewHolder] to bind. * @param viewHolder The [RecyclerView.ViewHolder] to bind.
* @param bodyView The [View] containing the main body of the list item. Any click events on * @param bodyView The [View] containing the main body of the list item. Any click events on
* this [View] are routed to the listener. Defaults to the root view. * this [View] are routed to the listener. Defaults to the root view.
* @param dragHandle A touchable [View]. Any drag on this view will start a drag event. * @param dragHandle A touchable [View]. Any drag on this view will start a drag event.
*/ */
fun bind( fun bind(
@ -83,11 +89,13 @@ interface EditableListListener<in T> : ClickableListListener<T> {
/** /**
* An extension of [ClickableListListener] that enables menu and selection functionality. * An extension of [ClickableListListener] that enables menu and selection functionality.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface SelectableListListener<in T> : ClickableListListener<T> { interface SelectableListListener<in T> : ClickableListListener<T> {
/** /**
* Called when an item in the list requests that a menu related to it should be opened. * Called when an item in the list requests that a menu related to it should be opened.
*
* @param item The [T] item to open a menu for. * @param item The [T] item to open a menu for.
* @param anchor The [View] to anchor the menu to. * @param anchor The [View] to anchor the menu to.
*/ */
@ -95,16 +103,18 @@ interface SelectableListListener<in T> : ClickableListListener<T> {
/** /**
* Called when an item in the list requests that it be selected. * Called when an item in the list requests that it be selected.
*
* @param item The [T] item to select. * @param item The [T] item to select.
*/ */
fun onSelect(item: T) fun onSelect(item: T)
/** /**
* Binds this instance to a list item. * Binds this instance to a list item.
*
* @param item The [T] to bind this item to. * @param item The [T] to bind this item to.
* @param viewHolder The [RecyclerView.ViewHolder] to bind. * @param viewHolder The [RecyclerView.ViewHolder] to bind.
* @param bodyView The [View] containing the main body of the list item. Any click events on * @param bodyView The [View] containing the main body of the list item. Any click events on
* this [View] are routed to the listener. Defaults to the root view. * this [View] are routed to the listener. Defaults to the root view.
* @param menuButton A clickable [View]. Any click events on this [View] will open a menu. * @param menuButton A clickable [View]. Any click events on this [View] will open a menu.
*/ */
fun bind( fun bind(

View file

@ -38,6 +38,7 @@ import org.oxycblt.auxio.music.metadata.Disc
data class Sort(val mode: Mode, val direction: Direction) { data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Create a new [Sort] with the same [mode], but a different [Direction]. * Create a new [Sort] with the same [mode], but a different [Direction].
*
* @param direction The new [Direction] to sort in. * @param direction The new [Direction] to sort in.
* @return A new sort with the same mode, but with the new [Direction] value applied. * @return A new sort with the same mode, but with the new [Direction] value applied.
*/ */
@ -45,6 +46,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Create a new [Sort] with the same [direction] value, but different [mode] value. * Create a new [Sort] with the same [direction] value, but different [mode] value.
*
* @param mode Tbe new mode to use for the Sort. * @param mode Tbe new mode to use for the Sort.
* @return A new sort with the same [direction] value, but with the new [mode] applied. * @return A new sort with the same [direction] value, but with the new [mode] applied.
*/ */
@ -52,6 +54,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Sort a list of [Song]s. * Sort a list of [Song]s.
*
* @param songs The list of [Song]s. * @param songs The list of [Song]s.
* @return A new list of [Song]s sorted by this [Sort]'s configuration. * @return A new list of [Song]s sorted by this [Sort]'s configuration.
*/ */
@ -63,6 +66,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Sort a list of [Album]s. * Sort a list of [Album]s.
*
* @param albums The list of [Album]s. * @param albums The list of [Album]s.
* @return A new list of [Album]s sorted by this [Sort]'s configuration. * @return A new list of [Album]s sorted by this [Sort]'s configuration.
*/ */
@ -74,6 +78,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Sort a list of [Artist]s. * Sort a list of [Artist]s.
*
* @param artists The list of [Artist]s. * @param artists The list of [Artist]s.
* @return A new list of [Artist]s sorted by this [Sort]'s configuration. * @return A new list of [Artist]s sorted by this [Sort]'s configuration.
*/ */
@ -85,6 +90,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Sort a list of [Genre]s. * Sort a list of [Genre]s.
*
* @param genres The list of [Genre]s. * @param genres The list of [Genre]s.
* @return A new list of [Genre]s sorted by this [Sort]'s configuration. * @return A new list of [Genre]s sorted by this [Sort]'s configuration.
*/ */
@ -96,6 +102,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Sort a *mutable* list of [Song]s in-place using this [Sort]'s configuration. * Sort a *mutable* list of [Song]s in-place using this [Sort]'s configuration.
*
* @param songs The [Song]s to sort. * @param songs The [Song]s to sort.
*/ */
private fun songsInPlace(songs: MutableList<out Song>) { private fun songsInPlace(songs: MutableList<out Song>) {
@ -104,6 +111,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Sort a *mutable* list of [Album]s in-place using this [Sort]'s configuration. * Sort a *mutable* list of [Album]s in-place using this [Sort]'s configuration.
*
* @param albums The [Album]s to sort. * @param albums The [Album]s to sort.
*/ */
private fun albumsInPlace(albums: MutableList<out Album>) { private fun albumsInPlace(albums: MutableList<out Album>) {
@ -112,6 +120,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Sort a *mutable* list of [Artist]s in-place using this [Sort]'s configuration. * Sort a *mutable* list of [Artist]s in-place using this [Sort]'s configuration.
*
* @param artists The [Album]s to sort. * @param artists The [Album]s to sort.
*/ */
private fun artistsInPlace(artists: MutableList<out Artist>) { private fun artistsInPlace(artists: MutableList<out Artist>) {
@ -120,6 +129,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Sort a *mutable* list of [Genre]s in-place using this [Sort]'s configuration. * Sort a *mutable* list of [Genre]s in-place using this [Sort]'s configuration.
*
* @param genres The [Genre]s to sort. * @param genres The [Genre]s to sort.
*/ */
private fun genresInPlace(genres: MutableList<out Genre>) { private fun genresInPlace(genres: MutableList<out Genre>) {
@ -128,6 +138,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* The integer representation of this instance. * The integer representation of this instance.
*
* @see fromIntCode * @see fromIntCode
*/ */
val intCode: Int val intCode: Int
@ -150,6 +161,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Get a [Comparator] that sorts [Song]s according to this [Mode]. * Get a [Comparator] that sorts [Song]s according to this [Mode].
*
* @param direction The direction to sort in. * @param direction The direction to sort in.
* @return A [Comparator] that can be used to sort a [Song] list according to this [Mode]. * @return A [Comparator] that can be used to sort a [Song] list according to this [Mode].
*/ */
@ -159,6 +171,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Get a [Comparator] that sorts [Album]s according to this [Mode]. * Get a [Comparator] that sorts [Album]s according to this [Mode].
*
* @param direction The direction to sort in. * @param direction The direction to sort in.
* @return A [Comparator] that can be used to sort a [Album] list according to this [Mode]. * @return A [Comparator] that can be used to sort a [Album] list according to this [Mode].
*/ */
@ -168,6 +181,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Return a [Comparator] that sorts [Artist]s according to this [Mode]. * Return a [Comparator] that sorts [Artist]s according to this [Mode].
*
* @param direction The direction to sort in. * @param direction The direction to sort in.
* @return A [Comparator] that can be used to sort a [Artist] list according to this [Mode]. * @return A [Comparator] that can be used to sort a [Artist] list according to this [Mode].
*/ */
@ -177,6 +191,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Return a [Comparator] that sorts [Genre]s according to this [Mode]. * Return a [Comparator] that sorts [Genre]s according to this [Mode].
*
* @param direction The direction to sort in. * @param direction The direction to sort in.
* @return A [Comparator] that can be used to sort a [Genre] list according to this [Mode]. * @return A [Comparator] that can be used to sort a [Genre] list according to this [Mode].
*/ */
@ -186,6 +201,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Sort by the item's name. * Sort by the item's name.
*
* @see Music.collationKey * @see Music.collationKey
*/ */
object ByName : Mode() { object ByName : Mode() {
@ -210,6 +226,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Sort by the [Album] of an item. Only available for [Song]s. * Sort by the [Album] of an item. Only available for [Song]s.
*
* @see Album.collationKey * @see Album.collationKey
*/ */
object ByAlbum : Mode() { object ByAlbum : Mode() {
@ -229,6 +246,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Sort by the [Artist] name of an item. Only available for [Song] and [Album]. * Sort by the [Artist] name of an item. Only available for [Song] and [Album].
*
* @see Artist.collationKey * @see Artist.collationKey
*/ */
object ByArtist : Mode() { object ByArtist : Mode() {
@ -256,6 +274,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Sort by the [Date] of an item. Only available for [Song] and [Album]. * Sort by the [Date] of an item. Only available for [Song] and [Album].
*
* @see Song.date * @see Song.date
* @see Album.dates * @see Album.dates
*/ */
@ -308,6 +327,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Sort by the amount of songs an item contains. Only available for [MusicParent]s. * Sort by the amount of songs an item contains. Only available for [MusicParent]s.
*
* @see MusicParent.songs * @see MusicParent.songs
*/ */
object ByCount : Mode() { object ByCount : Mode() {
@ -333,6 +353,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Sort by the disc number of an item. Only available for [Song]s. * Sort by the disc number of an item. Only available for [Song]s.
*
* @see Song.disc * @see Song.disc
*/ */
object ByDisc : Mode() { object ByDisc : Mode() {
@ -351,6 +372,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Sort by the track number of an item. Only available for [Song]s. * Sort by the track number of an item. Only available for [Song]s.
*
* @see Song.track * @see Song.track
*/ */
object ByTrack : Mode() { object ByTrack : Mode() {
@ -369,6 +391,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Sort by the date an item was added. Only supported by [Song]s and [Album]s. * Sort by the date an item was added. Only supported by [Song]s and [Album]s.
*
* @see Song.dateAdded * @see Song.dateAdded
* @see Album.dates * @see Album.dates
*/ */
@ -391,6 +414,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Utility function to create a [Comparator] in a dynamic way determined by [direction]. * Utility function to create a [Comparator] in a dynamic way determined by [direction].
*
* @param direction The [Direction] to sort in. * @param direction The [Direction] to sort in.
* @see compareBy * @see compareBy
* @see compareByDescending * @see compareByDescending
@ -406,6 +430,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Utility function to create a [Comparator] in a dynamic way determined by [direction] * Utility function to create a [Comparator] in a dynamic way determined by [direction]
*
* @param direction The [Direction] to sort in. * @param direction The [Direction] to sort in.
* @param comparator A [Comparator] to wrap. * @param comparator A [Comparator] to wrap.
* @return A new [Comparator] with the specified configuration. * @return A new [Comparator] with the specified configuration.
@ -419,6 +444,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Utility function to create a [Comparator] a dynamic way determined by [direction] * Utility function to create a [Comparator] a dynamic way determined by [direction]
*
* @param direction The [Direction] to sort in. * @param direction The [Direction] to sort in.
* @param comparator A [Comparator] to wrap. * @param comparator A [Comparator] to wrap.
* @param selector Called to obtain a specific attribute to sort by. * @param selector Called to obtain a specific attribute to sort by.
@ -439,6 +465,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Utility function to create a [Comparator] that sorts in ascending order based on the * Utility function to create a [Comparator] that sorts in ascending order based on the
* given [Comparator], with a selector based on the item itself. * given [Comparator], with a selector based on the item itself.
*
* @param comparator The [Comparator] to wrap. * @param comparator The [Comparator] to wrap.
* @return A new [Comparator] with the specified configuration. * @return A new [Comparator] with the specified configuration.
* @see compareBy * @see compareBy
@ -448,8 +475,9 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* A [Comparator] that chains several other [Comparator]s together to form one comparison. * A [Comparator] that chains several other [Comparator]s together to form one comparison.
*
* @param comparators The [Comparator]s to chain. These will be iterated through in order * @param comparators The [Comparator]s to chain. These will be iterated through in order
* during a comparison, with the first non-equal result becoming the result. * during a comparison, with the first non-equal result becoming the result.
*/ */
private class MultiComparator<T>(vararg comparators: Comparator<T>) : Comparator<T> { private class MultiComparator<T>(vararg comparators: Comparator<T>) : Comparator<T> {
private val _comparators = comparators private val _comparators = comparators
@ -468,6 +496,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Wraps a [Comparator], extending it to compare two lists. * Wraps a [Comparator], extending it to compare two lists.
*
* @param inner The [Comparator] to use. * @param inner The [Comparator] to use.
*/ */
private class ListComparator<T>(private val inner: Comparator<T>) : Comparator<List<T>> { private class ListComparator<T>(private val inner: Comparator<T>) : Comparator<List<T>> {
@ -500,6 +529,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* A [Comparator] that compares abstract [Music] values. Internally, this is similar to * A [Comparator] that compares abstract [Music] values. Internally, this is similar to
* [NullableComparator], however comparing [Music.collationKey] instead of [Comparable]. * [NullableComparator], however comparing [Music.collationKey] instead of [Comparable].
*
* @see NullableComparator * @see NullableComparator
* @see Music.collationKey * @see Music.collationKey
*/ */
@ -555,6 +585,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
companion object { companion object {
/** /**
* Convert a [Mode] integer representation into an instance. * Convert a [Mode] integer representation into an instance.
*
* @param intCode An integer representation of a [Mode] * @param intCode An integer representation of a [Mode]
* @return The corresponding [Mode], or null if the [Mode] is invalid. * @return The corresponding [Mode], or null if the [Mode] is invalid.
* @see intCode * @see intCode
@ -575,6 +606,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
/** /**
* Convert a menu item ID into a [Mode]. * Convert a menu item ID into a [Mode].
*
* @param itemId The menu resource ID to convert * @param itemId The menu resource ID to convert
* @return A [Mode] corresponding to the given ID, or null if the ID is invalid. * @return A [Mode] corresponding to the given ID, or null if the ID is invalid.
* @see itemId * @see itemId
@ -604,6 +636,7 @@ data class Sort(val mode: Mode, val direction: Direction) {
companion object { companion object {
/** /**
* Convert a [Sort] integer representation into an instance. * Convert a [Sort] integer representation into an instance.
*
* @param intCode An integer representation of a [Sort] * @param intCode An integer representation of a [Sort]
* @return The corresponding [Sort], or null if the [Sort] is invalid. * @return The corresponding [Sort], or null if the [Sort] is invalid.
* @see intCode * @see intCode

View file

@ -21,6 +21,7 @@ import androidx.recyclerview.widget.RecyclerView
/** /**
* A [RecyclerView.Adapter] with [ListDiffer] integration. * A [RecyclerView.Adapter] with [ListDiffer] integration.
*
* @param differFactory The [ListDiffer.Factory] that defines the type of [ListDiffer] to use. * @param differFactory The [ListDiffer.Factory] that defines the type of [ListDiffer] to use.
*/ */
abstract class DiffAdapter<T, I, VH : RecyclerView.ViewHolder>( abstract class DiffAdapter<T, I, VH : RecyclerView.ViewHolder>(
@ -36,6 +37,7 @@ abstract class DiffAdapter<T, I, VH : RecyclerView.ViewHolder>(
/** /**
* Get a [T] item at the given position. * Get a [T] item at the given position.
*
* @param at The position to get the item at. * @param at The position to get the item at.
* @throws IndexOutOfBoundsException If the index is not in the list bounds/ * @throws IndexOutOfBoundsException If the index is not in the list bounds/
*/ */
@ -43,6 +45,7 @@ abstract class DiffAdapter<T, I, VH : RecyclerView.ViewHolder>(
/** /**
* Dynamically determine how to update the list based on the given instructions. * Dynamically determine how to update the list based on the given instructions.
*
* @param newList The new list of [T] items to show. * @param newList The new list of [T] items to show.
* @param instructions The instructions specifying how to update the list. * @param instructions The instructions specifying how to update the list.
* @param onDone Called when the update process is completed. Defaults to a no-op. * @param onDone Called when the update process is completed. Defaults to a no-op.

View file

@ -28,6 +28,7 @@ import androidx.recyclerview.widget.RecyclerView
/** /**
* List differ wrapper that provides more flexibility regarding the way lists are updated. * List differ wrapper that provides more flexibility regarding the way lists are updated.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface ListDiffer<T, I> { interface ListDiffer<T, I> {
@ -36,6 +37,7 @@ interface ListDiffer<T, I> {
/** /**
* Dynamically determine how to update the list based on the given instructions. * Dynamically determine how to update the list based on the given instructions.
*
* @param newList The new list of [T] items to show. * @param newList The new list of [T] items to show.
* @param instructions The [BasicListInstructions] specifying how to update the list. * @param instructions The [BasicListInstructions] specifying how to update the list.
* @param onDone Called when the update process is completed. * @param onDone Called when the update process is completed.
@ -49,6 +51,7 @@ interface ListDiffer<T, I> {
abstract class Factory<T, I> { abstract class Factory<T, I> {
/** /**
* Create a new [ListDiffer] bound to the given [RecyclerView.Adapter]. * Create a new [ListDiffer] bound to the given [RecyclerView.Adapter].
*
* @param adapter The [RecyclerView.Adapter] to bind to. * @param adapter The [RecyclerView.Adapter] to bind to.
*/ */
abstract fun new(adapter: RecyclerView.Adapter<*>): ListDiffer<T, I> abstract fun new(adapter: RecyclerView.Adapter<*>): ListDiffer<T, I>
@ -57,8 +60,9 @@ interface ListDiffer<T, I> {
/** /**
* Update lists on another thread. This is useful when large diffs are likely to occur in this * Update lists on another thread. This is useful when large diffs are likely to occur in this
* list that would be exceedingly slow with [Blocking]. * list that would be exceedingly slow with [Blocking].
*
* @param diffCallback A [DiffUtil.ItemCallback] to use for item comparison when diffing the * @param diffCallback A [DiffUtil.ItemCallback] to use for item comparison when diffing the
* internal list. * internal list.
*/ */
class Async<T>(private val diffCallback: DiffUtil.ItemCallback<T>) : class Async<T>(private val diffCallback: DiffUtil.ItemCallback<T>) :
Factory<T, BasicListInstructions>() { Factory<T, BasicListInstructions>() {
@ -69,8 +73,9 @@ interface ListDiffer<T, I> {
/** /**
* Update lists on the main thread. This is useful when many small, discrete list diffs are * Update lists on the main thread. This is useful when many small, discrete list diffs are
* likely to occur that would cause [Async] to suffer from race conditions. * likely to occur that would cause [Async] to suffer from race conditions.
*
* @param diffCallback A [DiffUtil.ItemCallback] to use for item comparison when diffing the * @param diffCallback A [DiffUtil.ItemCallback] to use for item comparison when diffing the
* internal list. * internal list.
*/ */
class Blocking<T>(private val diffCallback: DiffUtil.ItemCallback<T>) : class Blocking<T>(private val diffCallback: DiffUtil.ItemCallback<T>) :
Factory<T, BasicListInstructions>() { Factory<T, BasicListInstructions>() {
@ -81,6 +86,7 @@ interface ListDiffer<T, I> {
/** /**
* Represents the specific way to update a list of items. * Represents the specific way to update a list of items.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
enum class BasicListInstructions { enum class BasicListInstructions {

View file

@ -23,6 +23,7 @@ import org.oxycblt.auxio.util.logD
/** /**
* A [RecyclerView.Adapter] that supports indicating the playback status of a particular item. * A [RecyclerView.Adapter] that supports indicating the playback status of a particular item.
*
* @param differFactory The [ListDiffer.Factory] that defines the type of [ListDiffer] to use. * @param differFactory The [ListDiffer.Factory] that defines the type of [ListDiffer] to use.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@ -50,6 +51,7 @@ abstract class PlayingIndicatorAdapter<T, I, VH : RecyclerView.ViewHolder>(
} }
/** /**
* Update the currently playing item in the list. * Update the currently playing item in the list.
*
* @param item The [T] currently being played, or null if it is not being played. * @param item The [T] currently being played, or null if it is not being played.
* @param isPlaying Whether playback is ongoing or paused. * @param isPlaying Whether playback is ongoing or paused.
*/ */
@ -103,9 +105,10 @@ abstract class PlayingIndicatorAdapter<T, I, VH : RecyclerView.ViewHolder>(
abstract class ViewHolder(root: View) : RecyclerView.ViewHolder(root) { abstract class ViewHolder(root: View) : RecyclerView.ViewHolder(root) {
/** /**
* Update the playing indicator within this [RecyclerView.ViewHolder]. * Update the playing indicator within this [RecyclerView.ViewHolder].
*
* @param isActive True if this item is playing, false otherwise. * @param isActive True if this item is playing, false otherwise.
* @param isPlaying True if playback is ongoing, false if paused. If this is true, * @param isPlaying True if playback is ongoing, false if paused. If this is true,
* [isActive] will also be true. * [isActive] will also be true.
*/ */
abstract fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean) abstract fun updatePlayingIndicator(isActive: Boolean, isPlaying: Boolean)
} }

View file

@ -24,6 +24,7 @@ import org.oxycblt.auxio.music.Music
/** /**
* A [PlayingIndicatorAdapter] that also supports indicating the selection status of a group of * A [PlayingIndicatorAdapter] that also supports indicating the selection status of a group of
* items. * items.
*
* @param differFactory The [ListDiffer.Factory] that defines the type of [ListDiffer] to use. * @param differFactory The [ListDiffer.Factory] that defines the type of [ListDiffer] to use.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@ -41,6 +42,7 @@ abstract class SelectionIndicatorAdapter<T, I, VH : RecyclerView.ViewHolder>(
/** /**
* Update the list of selected items. * Update the list of selected items.
*
* @param items A set of selected [T] items. * @param items A set of selected [T] items.
*/ */
fun setSelected(items: Set<T>) { fun setSelected(items: Set<T>) {
@ -74,6 +76,7 @@ abstract class SelectionIndicatorAdapter<T, I, VH : RecyclerView.ViewHolder>(
abstract class ViewHolder(root: View) : PlayingIndicatorAdapter.ViewHolder(root) { abstract class ViewHolder(root: View) : PlayingIndicatorAdapter.ViewHolder(root) {
/** /**
* Update the selection indicator within this [PlayingIndicatorAdapter.ViewHolder]. * Update the selection indicator within this [PlayingIndicatorAdapter.ViewHolder].
*
* @param isSelected Whether this [PlayingIndicatorAdapter.ViewHolder] is selected. * @param isSelected Whether this [PlayingIndicatorAdapter.ViewHolder] is selected.
*/ */
abstract fun updateSelectionIndicator(isSelected: Boolean) abstract fun updateSelectionIndicator(isSelected: Boolean)

View file

@ -23,6 +23,7 @@ import org.oxycblt.auxio.list.Item
/** /**
* A [DiffUtil.ItemCallback] that automatically implements the [areItemsTheSame] method. Use this * A [DiffUtil.ItemCallback] that automatically implements the [areItemsTheSame] method. Use this
* whenever creating [DiffUtil.ItemCallback] implementations with an [Item] subclass. * whenever creating [DiffUtil.ItemCallback] implementations with an [Item] subclass.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
abstract class SimpleDiffCallback<T : Item> : DiffUtil.ItemCallback<T>() { abstract class SimpleDiffCallback<T : Item> : DiffUtil.ItemCallback<T>() {

View file

@ -31,6 +31,7 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
* - Automatic edge-to-edge support * - Automatic edge-to-edge support
* - Adapter-based [SpanSizeLookup] implementation * - Adapter-based [SpanSizeLookup] implementation
* - Automatic [setHasFixedSize] setup * - Automatic [setHasFixedSize] setup
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
open class AuxioRecyclerView open class AuxioRecyclerView
@ -89,6 +90,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
interface SpanSizeLookup { interface SpanSizeLookup {
/** /**
* Get if the item at a position takes up the whole width of the [RecyclerView] or not. * Get if the item at a position takes up the whole width of the [RecyclerView] or not.
*
* @param position The position of the item. * @param position The position of the item.
* @return true if the item is full-width, false otherwise. * @return true if the item is full-width, false otherwise.
*/ */

View file

@ -34,6 +34,7 @@ import org.oxycblt.auxio.util.getDimenPixels
* A [RecyclerView] intended for use in Dialogs, adding features such as: * A [RecyclerView] intended for use in Dialogs, adding features such as:
* - NestedScrollView scrollIndicators behavior emulation * - NestedScrollView scrollIndicators behavior emulation
* - Dialog-specific [ViewHolder] that automatically resolves certain issues. * - Dialog-specific [ViewHolder] that automatically resolves certain issues.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class DialogRecyclerView class DialogRecyclerView

View file

@ -29,6 +29,7 @@ import org.oxycblt.auxio.list.adapter.DiffAdapter
/** /**
* A [BackportMaterialDividerItemDecoration] that sets up the divider configuration to correctly * A [BackportMaterialDividerItemDecoration] that sets up the divider configuration to correctly
* separate content with headers. * separate content with headers.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class HeaderItemDecoration class HeaderItemDecoration

View file

@ -36,12 +36,14 @@ import org.oxycblt.auxio.util.logD
/** /**
* A [RecyclerView.ViewHolder] that displays a [Song]. Use [from] to create an instance. * A [RecyclerView.ViewHolder] that displays a [Song]. Use [from] to create an instance.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class SongViewHolder private constructor(private val binding: ItemSongBinding) : class SongViewHolder private constructor(private val binding: ItemSongBinding) :
SelectionIndicatorAdapter.ViewHolder(binding.root) { SelectionIndicatorAdapter.ViewHolder(binding.root) {
/** /**
* Bind new data to this instance. * Bind new data to this instance.
*
* @param song The new [Song] to bind. * @param song The new [Song] to bind.
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
*/ */
@ -67,6 +69,7 @@ class SongViewHolder private constructor(private val binding: ItemSongBinding) :
/** /**
* Create a new instance. * Create a new instance.
*
* @param parent The parent to inflate this instance from. * @param parent The parent to inflate this instance from.
* @return A new instance. * @return A new instance.
*/ */
@ -84,12 +87,14 @@ class SongViewHolder private constructor(private val binding: ItemSongBinding) :
/** /**
* A [RecyclerView.ViewHolder] that displays a [Album]. Use [from] to create an instance. * A [RecyclerView.ViewHolder] that displays a [Album]. Use [from] to create an instance.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class AlbumViewHolder private constructor(private val binding: ItemParentBinding) : class AlbumViewHolder private constructor(private val binding: ItemParentBinding) :
SelectionIndicatorAdapter.ViewHolder(binding.root) { SelectionIndicatorAdapter.ViewHolder(binding.root) {
/** /**
* Bind new data to this instance. * Bind new data to this instance.
*
* @param album The new [Album] to bind. * @param album The new [Album] to bind.
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
*/ */
@ -115,6 +120,7 @@ class AlbumViewHolder private constructor(private val binding: ItemParentBinding
/** /**
* Create a new instance. * Create a new instance.
*
* @param parent The parent to inflate this instance from. * @param parent The parent to inflate this instance from.
* @return A new instance. * @return A new instance.
*/ */
@ -133,12 +139,14 @@ class AlbumViewHolder private constructor(private val binding: ItemParentBinding
/** /**
* A [RecyclerView.ViewHolder] that displays a [Artist]. Use [from] to create an instance. * A [RecyclerView.ViewHolder] that displays a [Artist]. Use [from] to create an instance.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class ArtistViewHolder private constructor(private val binding: ItemParentBinding) : class ArtistViewHolder private constructor(private val binding: ItemParentBinding) :
SelectionIndicatorAdapter.ViewHolder(binding.root) { SelectionIndicatorAdapter.ViewHolder(binding.root) {
/** /**
* Bind new data to this instance. * Bind new data to this instance.
*
* @param artist The new [Artist] to bind. * @param artist The new [Artist] to bind.
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
*/ */
@ -173,6 +181,7 @@ class ArtistViewHolder private constructor(private val binding: ItemParentBindin
/** /**
* Create a new instance. * Create a new instance.
*
* @param parent The parent to inflate this instance from. * @param parent The parent to inflate this instance from.
* @return A new instance. * @return A new instance.
*/ */
@ -192,12 +201,14 @@ class ArtistViewHolder private constructor(private val binding: ItemParentBindin
/** /**
* A [RecyclerView.ViewHolder] that displays a [Genre]. Use [from] to create an instance. * A [RecyclerView.ViewHolder] that displays a [Genre]. Use [from] to create an instance.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class GenreViewHolder private constructor(private val binding: ItemParentBinding) : class GenreViewHolder private constructor(private val binding: ItemParentBinding) :
SelectionIndicatorAdapter.ViewHolder(binding.root) { SelectionIndicatorAdapter.ViewHolder(binding.root) {
/** /**
* Bind new data to this instance. * Bind new data to this instance.
*
* @param genre The new [Genre] to bind. * @param genre The new [Genre] to bind.
* @param listener An [SelectableListListener] to bind interactions to. * @param listener An [SelectableListListener] to bind interactions to.
*/ */
@ -227,6 +238,7 @@ class GenreViewHolder private constructor(private val binding: ItemParentBinding
/** /**
* Create a new instance. * Create a new instance.
*
* @param parent The parent to inflate this instance from. * @param parent The parent to inflate this instance from.
* @return A new instance. * @return A new instance.
*/ */
@ -243,12 +255,14 @@ class GenreViewHolder private constructor(private val binding: ItemParentBinding
/** /**
* A [RecyclerView.ViewHolder] that displays a [BasicHeader]. Use [from] to create an instance. * A [RecyclerView.ViewHolder] that displays a [BasicHeader]. Use [from] to create an instance.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class BasicHeaderViewHolder private constructor(private val binding: ItemHeaderBinding) : class BasicHeaderViewHolder private constructor(private val binding: ItemHeaderBinding) :
RecyclerView.ViewHolder(binding.root) { RecyclerView.ViewHolder(binding.root) {
/** /**
* Bind new data to this instance. * Bind new data to this instance.
*
* @param basicHeader The new [BasicHeader] to bind. * @param basicHeader The new [BasicHeader] to bind.
*/ */
fun bind(basicHeader: BasicHeader) { fun bind(basicHeader: BasicHeader) {
@ -262,6 +276,7 @@ class BasicHeaderViewHolder private constructor(private val binding: ItemHeaderB
/** /**
* Create a new instance. * Create a new instance.
*
* @param parent The parent to inflate this instance from. * @param parent The parent to inflate this instance from.
* @return A new instance. * @return A new instance.
*/ */

View file

@ -28,6 +28,7 @@ import org.oxycblt.auxio.util.showToast
/** /**
* A subset of ListFragment that implements aspects of the selection UI. * A subset of ListFragment that implements aspects of the selection UI.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
abstract class SelectionFragment<VB : ViewBinding> : abstract class SelectionFragment<VB : ViewBinding> :
@ -38,8 +39,9 @@ abstract class SelectionFragment<VB : ViewBinding> :
/** /**
* Get the [SelectionToolbarOverlay] of the concrete Fragment to be automatically managed by * Get the [SelectionToolbarOverlay] of the concrete Fragment to be automatically managed by
* [SelectionFragment]. * [SelectionFragment].
*
* @return The [SelectionToolbarOverlay] of the concrete [SelectionFragment]'s [VB], or null if * @return The [SelectionToolbarOverlay] of the concrete [SelectionFragment]'s [VB], or null if
* there is not one. * there is not one.
*/ */
open fun getSelectionToolbar(binding: VB): SelectionToolbarOverlay? = null open fun getSelectionToolbar(binding: VB): SelectionToolbarOverlay? = null

View file

@ -32,6 +32,7 @@ import org.oxycblt.auxio.util.logD
/** /**
* A wrapper around a [MaterialToolbar] that adds an additional [MaterialToolbar] showing the * A wrapper around a [MaterialToolbar] that adds an additional [MaterialToolbar] showing the
* current selection state. * current selection state.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class SelectionToolbarOverlay class SelectionToolbarOverlay
@ -65,6 +66,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
/** /**
* Set an OnClickListener for when the "cancel" button in the selection [MaterialToolbar] is * Set an OnClickListener for when the "cancel" button in the selection [MaterialToolbar] is
* pressed. * pressed.
*
* @param listener The OnClickListener to respond to this interaction. * @param listener The OnClickListener to respond to this interaction.
* @see MaterialToolbar.setNavigationOnClickListener * @see MaterialToolbar.setNavigationOnClickListener
*/ */
@ -75,6 +77,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
/** /**
* Set an [OnMenuItemClickListener] for when a MenuItem is selected from the selection * Set an [OnMenuItemClickListener] for when a MenuItem is selected from the selection
* [MaterialToolbar]. * [MaterialToolbar].
*
* @param listener The [OnMenuItemClickListener] to respond to this interaction. * @param listener The [OnMenuItemClickListener] to respond to this interaction.
* @see MaterialToolbar.setOnMenuItemClickListener * @see MaterialToolbar.setOnMenuItemClickListener
*/ */
@ -84,6 +87,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
/** /**
* Update the selection [MaterialToolbar] to reflect the current selection amount. * Update the selection [MaterialToolbar] to reflect the current selection amount.
*
* @param amount The amount of items that are currently selected. * @param amount The amount of items that are currently selected.
* @return true if the selection [MaterialToolbar] changes, false otherwise. * @return true if the selection [MaterialToolbar] changes, false otherwise.
*/ */
@ -101,6 +105,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
/** /**
* Animate the visibility of the inner and selection [MaterialToolbar]s to the given state. * Animate the visibility of the inner and selection [MaterialToolbar]s to the given state.
*
* @param selectionVisible Whether the selection [MaterialToolbar] should be visible or not. * @param selectionVisible Whether the selection [MaterialToolbar] should be visible or not.
* @return true if the toolbars have changed, false otherwise. * @return true if the toolbars have changed, false otherwise.
*/ */
@ -152,8 +157,9 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
/** /**
* Update the alpha of the inner and selection [MaterialToolbar]s. * Update the alpha of the inner and selection [MaterialToolbar]s.
*
* @param innerAlpha The opacity of the inner [MaterialToolbar]. This will map to the inverse * @param innerAlpha The opacity of the inner [MaterialToolbar]. This will map to the inverse
* opacity of the selection [MaterialToolbar]. * opacity of the selection [MaterialToolbar].
*/ */
private fun setToolbarsAlpha(innerAlpha: Float) { private fun setToolbarsAlpha(innerAlpha: Float) {
innerToolbar.apply { innerToolbar.apply {

View file

@ -27,6 +27,7 @@ import org.oxycblt.auxio.music.model.Library
/** /**
* A [ViewModel] that manages the current selection. * A [ViewModel] that manages the current selection.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@HiltViewModel @HiltViewModel
@ -67,6 +68,7 @@ class SelectionViewModel @Inject constructor(private val musicRepository: MusicR
/** /**
* Select a new [Music] item. If this item is already within the selected items, the item will * Select a new [Music] item. If this item is already within the selected items, the item will
* be removed. Otherwise, it will be added. * be removed. Otherwise, it will be added.
*
* @param music The [Music] item to select. * @param music The [Music] item to select.
*/ */
fun select(music: Music) { fun select(music: Music) {
@ -79,6 +81,7 @@ class SelectionViewModel @Inject constructor(private val musicRepository: MusicR
/** /**
* Consume the current selection. This will clear any items that were selected prior. * Consume the current selection. This will clear any items that were selected prior.
*
* @return The list of selected items before it was cleared. * @return The list of selected items before it was cleared.
*/ */
fun consume() = _selected.value.also { _selected.value = listOf() } fun consume() = _selected.value.also { _selected.value = listOf() }

View file

@ -28,6 +28,7 @@ import com.google.android.exoplayer2.extractor.wav.WavExtractor
/** /**
* A [ExtractorsFactory] that only provides audio containers to save APK space. * A [ExtractorsFactory] that only provides audio containers to save APK space.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
object AudioOnlyExtractors : ExtractorsFactory { object AudioOnlyExtractors : ExtractorsFactory {

View file

@ -38,11 +38,13 @@ import org.oxycblt.auxio.util.toUuidOrNull
/** /**
* Abstract music data. This contains universal information about all concrete music * Abstract music data. This contains universal information about all concrete music
* implementations, such as identification information and names. * implementations, such as identification information and names.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
sealed interface Music : Item { sealed interface Music : Item {
/** /**
* A unique identifier for this music item. * A unique identifier for this music item.
*
* @see UID * @see UID
*/ */
val uid: UID val uid: UID
@ -56,9 +58,10 @@ sealed interface Music : Item {
/** /**
* Returns a name suitable for use in the app UI. This should be favored over [rawName] in * Returns a name suitable for use in the app UI. This should be favored over [rawName] in
* nearly all cases. * nearly all cases.
*
* @param context [Context] required to obtain placeholder text or formatting information. * @param context [Context] required to obtain placeholder text or formatting information.
* @return A human-readable string representing the name of this music. In the case that the * @return A human-readable string representing the name of this music. In the case that the
* item does not have a name, an analogous "Unknown X" name is returned. * item does not have a name, an analogous "Unknown X" name is returned.
*/ */
fun resolveName(context: Context): String fun resolveName(context: Context): String
@ -76,7 +79,7 @@ sealed interface Music : Item {
* The key will have the following attributes: * The key will have the following attributes:
* - If [rawSortName] is present, this key will be derived from it. Otherwise [rawName] is used. * - If [rawSortName] is present, this key will be derived from it. Otherwise [rawName] is used.
* - If the string begins with an article, such as "the", it will be stripped, as is usually * - If the string begins with an article, such as "the", it will be stripped, as is usually
* convention for sorting media. This is not internationalized. * convention for sorting media. This is not internationalized.
*/ */
val collationKey: CollationKey? val collationKey: CollationKey?
@ -86,15 +89,14 @@ sealed interface Music : Item {
* [UID] enables a much cheaper and more reliable form of differentiating music, derived from * [UID] enables a much cheaper and more reliable form of differentiating music, derived from
* either a hash of meaningful metadata or the MusicBrainz ID spec. Using this enables several * either a hash of meaningful metadata or the MusicBrainz ID spec. Using this enables several
* improvements to music management in this app, including: * improvements to music management in this app, including:
*
* - Proper differentiation of identical music. It's common for large, well-tagged libraries to * - Proper differentiation of identical music. It's common for large, well-tagged libraries to
* have functionally duplicate items that are differentiated with MusicBrainz IDs, and so [UID] * have functionally duplicate items that are differentiated with MusicBrainz IDs, and so
* allows us to properly differentiate between these in the app. * [UID] allows us to properly differentiate between these in the app.
* - Better music persistence between restarts. Whereas directly storing song names would be * - Better music persistence between restarts. Whereas directly storing song names would be
* prone to collisions, and storing MediaStore IDs would drift rapidly as the music library * prone to collisions, and storing MediaStore IDs would drift rapidly as the music library
* changes, [UID] enables a much stronger form of persistence given it's unique link to a * changes, [UID] enables a much stronger form of persistence given it's unique link to a
* specific files metadata configuration, which is unlikely to collide with another item or * specific files metadata configuration, which is unlikely to collide with another item or
* drift as the music library changes. * drift as the music library changes.
* *
* Note: Generally try to use [UID] as a black box that can only be read, written, and compared. * Note: Generally try to use [UID] as a black box that can only be read, written, and compared.
* It will not be fun if you try to manipulate it in any other manner. * It will not be fun if you try to manipulate it in any other manner.
@ -125,6 +127,7 @@ sealed interface Music : Item {
/** /**
* Internal marker of [Music.UID] format type. * Internal marker of [Music.UID] format type.
*
* @param namespace Namespace to use in the [Music.UID]'s string representation. * @param namespace Namespace to use in the [Music.UID]'s string representation.
*/ */
private enum class Format(val namespace: String) { private enum class Format(val namespace: String) {
@ -139,10 +142,11 @@ sealed interface Music : Item {
/** /**
* Creates an Auxio-style [UID] with a [UUID] composed of a hash of the non-subjective, * Creates an Auxio-style [UID] with a [UUID] composed of a hash of the non-subjective,
* unlikely-to-change metadata of the music. * unlikely-to-change metadata of the music.
*
* @param mode The analogous [MusicMode] of the item that created this [UID]. * @param mode The analogous [MusicMode] of the item that created this [UID].
* @param updates Block to update the [MessageDigest] hash with the metadata of the * @param updates Block to update the [MessageDigest] hash with the metadata of the
* item. Make sure the metadata hashed semantically aligns with the format * item. Make sure the metadata hashed semantically aligns with the format
* specification. * specification.
* @return A new auxio-style [UID]. * @return A new auxio-style [UID].
*/ */
fun auxio(mode: MusicMode, updates: MessageDigest.() -> Unit): UID { fun auxio(mode: MusicMode, updates: MessageDigest.() -> Unit): UID {
@ -181,19 +185,21 @@ sealed interface Music : Item {
/** /**
* Creates a MusicBrainz-style [UID] with a [UUID] derived from the MusicBrainz ID * Creates a MusicBrainz-style [UID] with a [UUID] derived from the MusicBrainz ID
* extracted from a file. * extracted from a file.
*
* @param mode The analogous [MusicMode] of the item that created this [UID]. * @param mode The analogous [MusicMode] of the item that created this [UID].
* @param mbid The analogous MusicBrainz ID for this item that was extracted from a * @param mbid The analogous MusicBrainz ID for this item that was extracted from a
* file. * file.
* @return A new MusicBrainz-style [UID]. * @return A new MusicBrainz-style [UID].
*/ */
fun musicBrainz(mode: MusicMode, mbid: UUID): UID = UID(Format.MUSICBRAINZ, mode, mbid) fun musicBrainz(mode: MusicMode, mbid: UUID): UID = UID(Format.MUSICBRAINZ, mode, mbid)
/** /**
* Convert a [UID]'s string representation back into a concrete [UID] instance. * Convert a [UID]'s string representation back into a concrete [UID] instance.
*
* @param uid The [UID]'s string representation, formatted as * @param uid The [UID]'s string representation, formatted as
* `format_namespace:music_mode_int-uuid`. * `format_namespace:music_mode_int-uuid`.
* @return A [UID] converted from the string representation, or null if the string * @return A [UID] converted from the string representation, or null if the string
* representation was invalid. * representation was invalid.
*/ */
fun fromString(uid: String): UID? { fun fromString(uid: String): UID? {
val split = uid.split(':', limit = 2) val split = uid.split(':', limit = 2)
@ -224,6 +230,7 @@ sealed interface Music : Item {
/** /**
* An abstract grouping of [Song]s and other [Music] data. * An abstract grouping of [Song]s and other [Music] data.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
sealed interface MusicParent : Music { sealed interface MusicParent : Music {
@ -233,6 +240,7 @@ sealed interface MusicParent : Music {
/** /**
* A song. * A song.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface Song : Music { interface Song : Music {
@ -281,6 +289,7 @@ interface Song : Music {
/** /**
* An abstract release group. While it may be called an album, it encompasses other types of * An abstract release group. While it may be called an album, it encompasses other types of
* releases like singles, EPs, and compilations. * releases like singles, EPs, and compilations.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface Album : MusicParent { interface Album : MusicParent {
@ -311,6 +320,7 @@ interface Album : MusicParent {
/** /**
* An abstract artist. These are actually a combination of the artist and album artist tags from * An abstract artist. These are actually a combination of the artist and album artist tags from
* within the library, derived from [Song]s and [Album]s respectively. * within the library, derived from [Song]s and [Album]s respectively.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface Artist : MusicParent { interface Artist : MusicParent {
@ -336,6 +346,7 @@ interface Artist : MusicParent {
/** /**
* A genre. * A genre.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface Genre : MusicParent { interface Genre : MusicParent {
@ -350,6 +361,7 @@ interface Genre : MusicParent {
/** /**
* Run [Music.resolveName] on each instance in the given list and concatenate them into a [String] * Run [Music.resolveName] on each instance in the given list and concatenate them into a [String]
* in a localized manner. * in a localized manner.
*
* @param context [Context] required * @param context [Context] required
* @return A concatenated string. * @return A concatenated string.
*/ */
@ -359,6 +371,7 @@ fun <T : Music> List<T>.resolveNames(context: Context) =
/** /**
* Returns if [Music.rawName] matches for each item in a list. Useful for scenarios where the * Returns if [Music.rawName] matches for each item in a list. Useful for scenarios where the
* display information of an item must be compared without a context. * display information of an item must be compared without a context.
*
* @param other The list of items to compare to. * @param other The list of items to compare to.
* @return True if they are the same (by [Music.rawName]), false otherwise. * @return True if they are the same (by [Music.rawName]), false otherwise.
*/ */

View file

@ -21,6 +21,7 @@ import org.oxycblt.auxio.IntegerTable
/** /**
* Represents a data configuration corresponding to a specific type of [Music], * Represents a data configuration corresponding to a specific type of [Music],
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
enum class MusicMode { enum class MusicMode {
@ -35,6 +36,7 @@ enum class MusicMode {
/** /**
* The integer representation of this instance. * The integer representation of this instance.
*
* @see fromIntCode * @see fromIntCode
*/ */
val intCode: Int val intCode: Int
@ -49,6 +51,7 @@ enum class MusicMode {
companion object { companion object {
/** /**
* Convert a [MusicMode] integer representation into an instance. * Convert a [MusicMode] integer representation into an instance.
*
* @param intCode An integer representation of a [MusicMode] * @param intCode An integer representation of a [MusicMode]
* @return The corresponding [MusicMode], or null if the [MusicMode] is invalid. * @return The corresponding [MusicMode], or null if the [MusicMode] is invalid.
* @see MusicMode.intCode * @see MusicMode.intCode

View file

@ -40,6 +40,7 @@ interface MusicRepository {
/** /**
* Add a [Listener] to this instance. This can be used to receive changes in the music library. * Add a [Listener] to this instance. This can be used to receive changes in the music library.
* Will invoke all [Listener] methods to initialize the instance with the current state. * Will invoke all [Listener] methods to initialize the instance with the current state.
*
* @param listener The [Listener] to add. * @param listener The [Listener] to add.
* @see Listener * @see Listener
*/ */
@ -47,8 +48,9 @@ interface MusicRepository {
/** /**
* Remove a [Listener] from this instance, preventing it from receiving any further updates. * Remove a [Listener] from this instance, preventing it from receiving any further updates.
*
* @param listener The [Listener] to remove. Does nothing if the [Listener] was never added in * @param listener The [Listener] to remove. Does nothing if the [Listener] was never added in
* the first place. * the first place.
* @see Listener * @see Listener
*/ */
fun removeListener(listener: Listener) fun removeListener(listener: Listener)
@ -57,6 +59,7 @@ interface MusicRepository {
interface Listener { interface Listener {
/** /**
* Called when the current [Library] has changed. * Called when the current [Library] has changed.
*
* @param library The new [Library], or null if no [Library] has been loaded yet. * @param library The new [Library], or null if no [Library] has been loaded yet.
*/ */
fun onLibraryChanged(library: Library?) fun onLibraryChanged(library: Library?)

View file

@ -31,6 +31,7 @@ import org.oxycblt.auxio.util.getSystemServiceCompat
/** /**
* User configuration specific to music system. * User configuration specific to music system.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface MusicSettings : Settings<MusicSettings.Listener> { interface MusicSettings : Settings<MusicSettings.Listener> {

View file

@ -26,6 +26,7 @@ import org.oxycblt.auxio.music.system.Indexer
/** /**
* A [ViewModel] providing data specific to the music loading process. * A [ViewModel] providing data specific to the music loading process.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@HiltViewModel @HiltViewModel
@ -76,6 +77,7 @@ class MusicViewModel @Inject constructor(private val indexer: Indexer) :
/** /**
* Non-manipulated statistics bound the last successful music load. * Non-manipulated statistics bound the last successful music load.
*
* @param songs The amount of [Song]s that were loaded. * @param songs The amount of [Song]s that were loaded.
* @param albums The amount of [Album]s that were created. * @param albums The amount of [Album]s that were created.
* @param artists The amount of [Artist]s that were created. * @param artists The amount of [Artist]s that were created.

View file

@ -23,17 +23,20 @@ import org.oxycblt.auxio.util.*
/** /**
* A repository allowing access to cached metadata obtained in prior music loading operations. * A repository allowing access to cached metadata obtained in prior music loading operations.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface CacheRepository { interface CacheRepository {
/** /**
* Read the current [Cache], if it exists. * Read the current [Cache], if it exists.
*
* @return The stored [Cache], or null if it could not be obtained. * @return The stored [Cache], or null if it could not be obtained.
*/ */
suspend fun readCache(): Cache? suspend fun readCache(): Cache?
/** /**
* Write the list of newly-loaded [RawSong]s to the cache, replacing the prior data. * Write the list of newly-loaded [RawSong]s to the cache, replacing the prior data.
*
* @param rawSongs The [rawSongs] to write to the cache. * @param rawSongs The [rawSongs] to write to the cache.
*/ */
suspend fun writeCache(rawSongs: List<RawSong>) suspend fun writeCache(rawSongs: List<RawSong>)
@ -67,6 +70,7 @@ class CacheRepositoryImpl @Inject constructor(private val cachedSongsDao: Cached
/** /**
* A cache of music metadata obtained in prior music loading operations. Obtain an instance with * A cache of music metadata obtained in prior music loading operations. Obtain an instance with
* [CacheRepository]. * [CacheRepository].
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface Cache { interface Cache {
@ -75,6 +79,7 @@ interface Cache {
/** /**
* Populate a [RawSong] from a cache entry, if it exists. * Populate a [RawSong] from a cache entry, if it exists.
*
* @param rawSong The [RawSong] to populate. * @param rawSong The [RawSong] to populate.
* @return true if a cache entry could be applied to [rawSong], false otherwise. * @return true if a cache entry could be applied to [rawSong], false otherwise.
*/ */

View file

@ -30,6 +30,7 @@ import org.oxycblt.auxio.util.logW
/** /**
* The properties of a [Song]'s file. * The properties of a [Song]'s file.
*
* @param bitrateKbps The bit rate, in kilobytes-per-second. Null if it could not be parsed. * @param bitrateKbps The bit rate, in kilobytes-per-second. Null if it could not be parsed.
* @param sampleRateHz The sample rate, in hertz. * @param sampleRateHz The sample rate, in hertz.
* @param resolvedMimeType The known mime type of the [Song] after it's file format was determined. * @param resolvedMimeType The known mime type of the [Song] after it's file format was determined.
@ -44,6 +45,7 @@ data class AudioInfo(
interface Provider { interface Provider {
/** /**
* Extract the [AudioInfo] of a given [Song]. * Extract the [AudioInfo] of a given [Song].
*
* @param song The [Song] to read. * @param song The [Song] to read.
* @return The [AudioInfo] of the [Song], if possible to obtain. * @return The [AudioInfo] of the [Song], if possible to obtain.
*/ */
@ -53,6 +55,7 @@ data class AudioInfo(
/** /**
* A framework-backed implementation of [AudioInfo.Provider]. * A framework-backed implementation of [AudioInfo.Provider].
*
* @param context [Context] required to read audio files. * @param context [Context] required to read audio files.
*/ */
class AudioInfoProviderImpl @Inject constructor(@ApplicationContext private val context: Context) : class AudioInfoProviderImpl @Inject constructor(@ApplicationContext private val context: Context) :

View file

@ -44,10 +44,11 @@ class Date private constructor(private val tokens: List<Int>) : Comparable<Date>
/** /**
* Resolve this instance into a human-readable date. * Resolve this instance into a human-readable date.
*
* @param context [Context] required to get human-readable names. * @param context [Context] required to get human-readable names.
* @return If the [Date] has a valid month and year value, a more fine-grained date (ex. "Jan * @return If the [Date] has a valid month and year value, a more fine-grained date (ex. "Jan
* 2020") will be returned. Otherwise, a plain year value (ex. "2020") is returned. Dates will * 2020") will be returned. Otherwise, a plain year value (ex. "2020") is returned. Dates will
* be properly localized. * be properly localized.
*/ */
fun resolveDate(context: Context): String { fun resolveDate(context: Context): String {
if (month != null) { if (month != null) {
@ -115,6 +116,7 @@ class Date private constructor(private val tokens: List<Int>) : Comparable<Date>
* A range of [Date]s. This is used in contexts where the [Date] of an item is derived from * A range of [Date]s. This is used in contexts where the [Date] of an item is derived from
* several sub-items and thus can have a "range" of release dates. Use [from] to create an * several sub-items and thus can have a "range" of release dates. Use [from] to create an
* instance. * instance.
*
* @author Alexander Capehart * @author Alexander Capehart
*/ */
class Range class Range
@ -127,10 +129,11 @@ class Date private constructor(private val tokens: List<Int>) : Comparable<Date>
/** /**
* Resolve this instance into a human-readable date range. * Resolve this instance into a human-readable date range.
*
* @param context [Context] required to get human-readable names. * @param context [Context] required to get human-readable names.
* @return If the date has a maximum value, then a `min - max` formatted string will be * @return If the date has a maximum value, then a `min - max` formatted string will be
* returned with the formatted [Date]s of the minimum and maximum dates respectively. * returned with the formatted [Date]s of the minimum and maximum dates respectively.
* Otherwise, the formatted name of the minimum [Date] will be returned. * Otherwise, the formatted name of the minimum [Date] will be returned.
*/ */
fun resolveDate(context: Context) = fun resolveDate(context: Context) =
if (min != max) { if (min != max) {
@ -149,9 +152,10 @@ class Date private constructor(private val tokens: List<Int>) : Comparable<Date>
companion object { companion object {
/** /**
* Create a [Range] from the given list of [Date]s. * Create a [Range] from the given list of [Date]s.
*
* @param dates The [Date]s to use. * @param dates The [Date]s to use.
* @return A [Range] based on the minimum and maximum [Date]s. If there are no [Date]s, * @return A [Range] based on the minimum and maximum [Date]s. If there are no [Date]s,
* null is returned. * null is returned.
*/ */
fun from(dates: List<Date>): Range? { fun from(dates: List<Date>): Range? {
if (dates.isEmpty()) { if (dates.isEmpty()) {
@ -186,6 +190,7 @@ class Date private constructor(private val tokens: List<Int>) : Comparable<Date>
/** /**
* Create a [Date] from a year component. * Create a [Date] from a year component.
*
* @param year The year component. * @param year The year component.
* @return A new [Date] of the given component, or null if the component is invalid. * @return A new [Date] of the given component, or null if the component is invalid.
*/ */
@ -204,38 +209,41 @@ class Date private constructor(private val tokens: List<Int>) : Comparable<Date>
/** /**
* Create a [Date] from a date component. * Create a [Date] from a date component.
*
* @param year The year component. * @param year The year component.
* @param month The month component. * @param month The month component.
* @param day The day component. * @param day The day component.
* @return A new [Date] consisting of the given components. May have reduced precision if * @return A new [Date] consisting of the given components. May have reduced precision if
* the components were partially invalid, and will be null if all components are invalid. * the components were partially invalid, and will be null if all components are invalid.
*/ */
fun from(year: Int, month: Int, day: Int) = fromTokens(listOf(year, month, day)) fun from(year: Int, month: Int, day: Int) = fromTokens(listOf(year, month, day))
/** /**
* Create [Date] from a datetime component. * Create [Date] from a datetime component.
*
* @param year The year component. * @param year The year component.
* @param month The month component. * @param month The month component.
* @param day The day component. * @param day The day component.
* @param hour The hour component * @param hour The hour component
* @return A new [Date] consisting of the given components. May have reduced precision if * @return A new [Date] consisting of the given components. May have reduced precision if
* the components were partially invalid, and will be null if all components are invalid. * the components were partially invalid, and will be null if all components are invalid.
*/ */
fun from(year: Int, month: Int, day: Int, hour: Int, minute: Int) = fun from(year: Int, month: Int, day: Int, hour: Int, minute: Int) =
fromTokens(listOf(year, month, day, hour, minute)) fromTokens(listOf(year, month, day, hour, minute))
/** /**
* Create a [Date] from a [String] timestamp. * Create a [Date] from a [String] timestamp.
*
* @param timestamp The ISO-8601 timestamp to parse. Can have reduced precision. * @param timestamp The ISO-8601 timestamp to parse. Can have reduced precision.
* @return A new [Date] consisting of the given components. May have reduced precision if * @return A new [Date] consisting of the given components. May have reduced precision if
* the components were partially invalid, and will be null if all components are invalid or * the components were partially invalid, and will be null if all components are invalid
* if the timestamp is invalid. * or if the timestamp is invalid.
*/ */
fun from(timestamp: String): Date? { fun from(timestamp: String): Date? {
val tokens = val tokens =
// Match the input with the timestamp regex. If there is no match, see if we can // Match the input with the timestamp regex. If there is no match, see if we can
// fall back to some kind of year value. // fall back to some kind of year value.
(ISO8601_REGEX.matchEntire(timestamp) (ISO8601_REGEX.matchEntire(timestamp)
?: return timestamp.toIntOrNull()?.let(Companion::from)) ?: return timestamp.toIntOrNull()?.let(Companion::from))
.groupValues .groupValues
// Filter to the specific tokens we want and convert them to integer tokens. // Filter to the specific tokens we want and convert them to integer tokens.
@ -245,9 +253,10 @@ class Date private constructor(private val tokens: List<Int>) : Comparable<Date>
/** /**
* Create a [Date] from the given non-validated tokens. * Create a [Date] from the given non-validated tokens.
*
* @param tokens The tokens to use for each date component, in order of precision. * @param tokens The tokens to use for each date component, in order of precision.
* @return A new [Date] consisting of the given components. May have reduced precision if * @return A new [Date] consisting of the given components. May have reduced precision if
* the components were partially invalid, and will be null if all components are invalid. * the components were partially invalid, and will be null if all components are invalid.
*/ */
private fun fromTokens(tokens: List<Int>): Date? { private fun fromTokens(tokens: List<Int>): Date? {
val validated = mutableListOf<Int>() val validated = mutableListOf<Int>()
@ -262,6 +271,7 @@ class Date private constructor(private val tokens: List<Int>) : Comparable<Date>
/** /**
* Validate a list of tokens provided by [src], and add the valid ones to [dst]. Will stop * Validate a list of tokens provided by [src], and add the valid ones to [dst]. Will stop
* as soon as an invalid token is found. * as soon as an invalid token is found.
*
* @param src The input tokens to validate. * @param src The input tokens to validate.
* @param dst The destination list to add valid tokens to. * @param dst The destination list to add valid tokens to.
*/ */

View file

@ -21,6 +21,7 @@ import org.oxycblt.auxio.list.Item
/** /**
* A disc identifier for a song. * A disc identifier for a song.
*
* @param number The disc number. * @param number The disc number.
* @param name The name of the disc group, if any. Null if not present. * @param name The name of the disc group, if any. Null if not present.
*/ */

View file

@ -24,6 +24,7 @@ import org.oxycblt.auxio.R
* *
* This class is derived from the MusicBrainz Release Group Type specification. It can be found at: * This class is derived from the MusicBrainz Release Group Type specification. It can be found at:
* https://musicbrainz.org/doc/Release_Group/Type * https://musicbrainz.org/doc/Release_Group/Type
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
sealed class ReleaseType { sealed class ReleaseType {
@ -38,8 +39,9 @@ sealed class ReleaseType {
/** /**
* A plain album. * A plain album.
*
* @param refinement A specification of what kind of performance this release is. If null, the * @param refinement A specification of what kind of performance this release is. If null, the
* release is considered "Plain". * release is considered "Plain".
*/ */
data class Album(override val refinement: Refinement?) : ReleaseType() { data class Album(override val refinement: Refinement?) : ReleaseType() {
override val stringRes: Int override val stringRes: Int
@ -54,8 +56,9 @@ sealed class ReleaseType {
/** /**
* A "Extended Play", or EP. Usually a smaller release consisting of 4-5 songs. * A "Extended Play", or EP. Usually a smaller release consisting of 4-5 songs.
*
* @param refinement A specification of what kind of performance this release is. If null, the * @param refinement A specification of what kind of performance this release is. If null, the
* release is considered "Plain". * release is considered "Plain".
*/ */
data class EP(override val refinement: Refinement?) : ReleaseType() { data class EP(override val refinement: Refinement?) : ReleaseType() {
override val stringRes: Int override val stringRes: Int
@ -70,8 +73,9 @@ sealed class ReleaseType {
/** /**
* A single. Usually a release consisting of 1-2 songs. * A single. Usually a release consisting of 1-2 songs.
*
* @param refinement A specification of what kind of performance this release is. If null, the * @param refinement A specification of what kind of performance this release is. If null, the
* release is considered "Plain". * release is considered "Plain".
*/ */
data class Single(override val refinement: Refinement?) : ReleaseType() { data class Single(override val refinement: Refinement?) : ReleaseType() {
override val stringRes: Int override val stringRes: Int
@ -86,8 +90,9 @@ sealed class ReleaseType {
/** /**
* A compilation. Usually consists of many songs from a variety of artists. * A compilation. Usually consists of many songs from a variety of artists.
*
* @param refinement A specification of what kind of performance this release is. If null, the * @param refinement A specification of what kind of performance this release is. If null, the
* release is considered "Plain". * release is considered "Plain".
*/ */
data class Compilation(override val refinement: Refinement?) : ReleaseType() { data class Compilation(override val refinement: Refinement?) : ReleaseType() {
override val stringRes: Int override val stringRes: Int
@ -149,9 +154,10 @@ sealed class ReleaseType {
/** /**
* Parse a [ReleaseType] from a string formatted with the MusicBrainz Release Group Type * Parse a [ReleaseType] from a string formatted with the MusicBrainz Release Group Type
* specification. * specification.
*
* @param types A list of values consisting of valid release type values. * @param types A list of values consisting of valid release type values.
* @return A [ReleaseType] consisting of the given types, or null if the types were not * @return A [ReleaseType] consisting of the given types, or null if the types were not
* valid. * valid.
*/ */
fun parse(types: List<String>): ReleaseType? { fun parse(types: List<String>): ReleaseType? {
val primary = types.getOrNull(0) ?: return null val primary = types.getOrNull(0) ?: return null
@ -170,10 +176,11 @@ sealed class ReleaseType {
/** /**
* Parse "secondary" types (i.e not [Album], [EP], or [Single]) from a string formatted with * Parse "secondary" types (i.e not [Album], [EP], or [Single]) from a string formatted with
* the MusicBrainz Release Group Type specification. * the MusicBrainz Release Group Type specification.
*
* @param index The index of the release type to parse. * @param index The index of the release type to parse.
* @param convertRefinement Code to convert a [Refinement] into a [ReleaseType] * @param convertRefinement Code to convert a [Refinement] into a [ReleaseType]
* corresponding to the callee's context. This is used in order to handle secondary times * corresponding to the callee's context. This is used in order to handle secondary times
* that are actually [Refinement]s. * that are actually [Refinement]s.
* @return A [ReleaseType] corresponding to the secondary type found at that index. * @return A [ReleaseType] corresponding to the secondary type found at that index.
*/ */
private inline fun List<String>.parseSecondaryTypes( private inline fun List<String>.parseSecondaryTypes(
@ -194,10 +201,11 @@ sealed class ReleaseType {
/** /**
* Parse "secondary" types (i.e not [Album], [EP], [Single]) that do not correspond to any * Parse "secondary" types (i.e not [Album], [EP], [Single]) that do not correspond to any
* child values. * child values.
*
* @param type The release type value to parse. * @param type The release type value to parse.
* @param convertRefinement Code to convert a [Refinement] into a [ReleaseType] * @param convertRefinement Code to convert a [Refinement] into a [ReleaseType]
* corresponding to the callee's context. This is used in order to handle secondary times * corresponding to the callee's context. This is used in order to handle secondary times
* that are actually [Refinement]s. * that are actually [Refinement]s.
*/ */
private inline fun parseSecondaryTypeImpl( private inline fun parseSecondaryTypeImpl(
type: String?, type: String?,

View file

@ -19,6 +19,7 @@ package org.oxycblt.auxio.music.metadata
/** /**
* Defines the allowed separator characters that can be used to delimit multi-value tags. * Defines the allowed separator characters that can be used to delimit multi-value tags.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
object Separators { object Separators {

View file

@ -33,6 +33,7 @@ import org.oxycblt.auxio.ui.ViewBindingDialogFragment
/** /**
* A [ViewBindingDialogFragment] that allows the user to configure the separator characters used to * A [ViewBindingDialogFragment] that allows the user to configure the separator characters used to
* split tags with multiple values. * split tags with multiple values.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint

View file

@ -43,6 +43,7 @@ interface TagExtractor {
/** /**
* Extract the metadata of songs from [incompleteSongs] and send them to [completeSongs]. Will * Extract the metadata of songs from [incompleteSongs] and send them to [completeSongs]. Will
* terminate as soon as [incompleteSongs] is closed. * terminate as soon as [incompleteSongs] is closed.
*
* @param incompleteSongs A [Channel] of incomplete songs to process. * @param incompleteSongs A [Channel] of incomplete songs to process.
* @param completeSongs A [Channel] to send completed songs to. * @param completeSongs A [Channel] to send completed songs to.
*/ */
@ -105,6 +106,7 @@ class TagExtractorImpl @Inject constructor(@ApplicationContext private val conte
/** /**
* Wraps a [TagExtractor] future and processes it into a [RawSong] when completed. * Wraps a [TagExtractor] future and processes it into a [RawSong] when completed.
*
* @param context [Context] required to open the audio file. * @param context [Context] required to open the audio file.
* @param rawSong [RawSong] to process. * @param rawSong [RawSong] to process.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
@ -121,6 +123,7 @@ private class Task(context: Context, private val rawSong: RawSong) {
/** /**
* Try to get a completed song from this [Task], if it has finished processing. * Try to get a completed song from this [Task], if it has finished processing.
*
* @return A [RawSong] instance if processing has completed, null otherwise. * @return A [RawSong] instance if processing has completed, null otherwise.
*/ */
fun get(): RawSong? { fun get(): RawSong? {
@ -156,8 +159,9 @@ private class Task(context: Context, private val rawSong: RawSong) {
/** /**
* Complete this instance's [RawSong] with ID3v2 Text Identification Frames. * Complete this instance's [RawSong] with ID3v2 Text Identification Frames.
*
* @param textFrames A mapping between ID3v2 Text Identification Frame IDs and one or more * @param textFrames A mapping between ID3v2 Text Identification Frame IDs and one or more
* values. * values.
*/ */
private fun populateWithId3v2(textFrames: Map<String, List<String>>) { private fun populateWithId3v2(textFrames: Map<String, List<String>>) {
// Song // Song
@ -220,11 +224,12 @@ private class Task(context: Context, private val rawSong: RawSong) {
/** /**
* Parses the ID3v2.3 timestamp specification into a [Date] from the given Text Identification * Parses the ID3v2.3 timestamp specification into a [Date] from the given Text Identification
* Frames. * Frames.
*
* @param textFrames A mapping between ID3v2 Text Identification Frame IDs and one or more * @param textFrames A mapping between ID3v2 Text Identification Frame IDs and one or more
* values. * values.
* @return A [Date] of a year value from TORY/TYER, a month and day value from TDAT, and a * @return A [Date] of a year value from TORY/TYER, a month and day value from TDAT, and a
* hour/minute value from TIME. No second value is included. The latter two fields may not be * hour/minute value from TIME. No second value is included. The latter two fields may not be
* included in they cannot be parsed. Will be null if a year value could not be parsed. * included in they cannot be parsed. Will be null if a year value could not be parsed.
*/ */
private fun parseId3v23Date(textFrames: Map<String, List<String>>): Date? { private fun parseId3v23Date(textFrames: Map<String, List<String>>): Date? {
// Assume that TDAT/TIME can refer to TYER or TORY depending on if TORY // Assume that TDAT/TIME can refer to TYER or TORY depending on if TORY
@ -261,6 +266,7 @@ private class Task(context: Context, private val rawSong: RawSong) {
/** /**
* Complete this instance's [RawSong] with Vorbis comments. * Complete this instance's [RawSong] with Vorbis comments.
*
* @param comments A mapping between vorbis comment names and one or more vorbis comment values. * @param comments A mapping between vorbis comment names and one or more vorbis comment values.
*/ */
private fun populateWithVorbis(comments: Map<String, List<String>>) { private fun populateWithVorbis(comments: Map<String, List<String>>) {

View file

@ -26,6 +26,7 @@ import org.oxycblt.auxio.util.nonZeroOrNull
* Parse a multi-value tag based on the user configuration. If the value is already composed of more * Parse a multi-value tag based on the user configuration. If the value is already composed of more
* than one value, nothing is done. Otherwise, this function will attempt to split it based on the * than one value, nothing is done. Otherwise, this function will attempt to split it based on the
* user's separator preferences. * user's separator preferences.
*
* @param settings [MusicSettings] required to obtain user separator configuration. * @param settings [MusicSettings] required to obtain user separator configuration.
* @return A new list of one or more [String]s. * @return A new list of one or more [String]s.
*/ */
@ -40,6 +41,7 @@ fun List<String>.parseMultiValue(settings: MusicSettings) =
/** /**
* Split a [String] by the given selector, automatically handling escaped characters that satisfy * Split a [String] by the given selector, automatically handling escaped characters that satisfy
* the selector. * the selector.
*
* @param selector A block that determines if the string should be split at a given character. * @param selector A block that determines if the string should be split at a given character.
* @return One or more [String]s split by the selector. * @return One or more [String]s split by the selector.
*/ */
@ -83,19 +85,22 @@ inline fun String.splitEscaped(selector: (Char) -> Boolean): List<String> {
/** /**
* Fix trailing whitespace or blank contents in a [String]. * Fix trailing whitespace or blank contents in a [String].
*
* @return A string with trailing whitespace remove,d or null if the [String] was all whitespace or * @return A string with trailing whitespace remove,d or null if the [String] was all whitespace or
* empty. * empty.
*/ */
fun String.correctWhitespace() = trim().ifBlank { null } fun String.correctWhitespace() = trim().ifBlank { null }
/** /**
* Fix trailing whitespace or blank contents within a list of [String]s. * Fix trailing whitespace or blank contents within a list of [String]s.
*
* @return A list of non-blank strings with trailing whitespace removed. * @return A list of non-blank strings with trailing whitespace removed.
*/ */
fun List<String>.correctWhitespace() = mapNotNull { it.correctWhitespace() } fun List<String>.correctWhitespace() = mapNotNull { it.correctWhitespace() }
/** /**
* Attempt to parse a string by the user's separator preferences. * Attempt to parse a string by the user's separator preferences.
*
* @param settings [MusicSettings] required to obtain user separator configuration. * @param settings [MusicSettings] required to obtain user separator configuration.
* @return A list of one or more [String]s that were split up by the user-defined separators. * @return A list of one or more [String]s that were split up by the user-defined separators.
*/ */
@ -109,9 +114,11 @@ private fun String.maybeParseBySeparators(settings: MusicSettings): List<String>
/** /**
* Parse an ID3v2-style position + total [String] field. These fields consist of a number and an * Parse an ID3v2-style position + total [String] field. These fields consist of a number and an
* (optional) total value delimited by a /. * (optional) total value delimited by a /.
*
* @return The position value extracted from the string field, or null if: * @return The position value extracted from the string field, or null if:
* - The position could not be parsed * - The position could not be parsed
* - The position was zeroed AND the total value was not present/zeroed * - The position was zeroed AND the total value was not present/zeroed
*
* @see transformPositionField * @see transformPositionField
*/ */
fun String.parseId3v2PositionField() = fun String.parseId3v2PositionField() =
@ -122,11 +129,13 @@ fun String.parseId3v2PositionField() =
/** /**
* Parse a vorbis-style position + total field. These fields consist of two fields for the position * Parse a vorbis-style position + total field. These fields consist of two fields for the position
* and total numbers. * and total numbers.
*
* @param pos The position value, or null if not present. * @param pos The position value, or null if not present.
* @param total The total value, if not present. * @param total The total value, if not present.
* @return The position value extracted from the field, or null if: * @return The position value extracted from the field, or null if:
* - The position could not be parsed * - The position could not be parsed
* - The position was zeroed AND the total value was not present/zeroed * - The position was zeroed AND the total value was not present/zeroed
*
* @see transformPositionField * @see transformPositionField
*/ */
fun parseVorbisPositionField(pos: String?, total: String?) = fun parseVorbisPositionField(pos: String?, total: String?) =
@ -134,6 +143,7 @@ fun parseVorbisPositionField(pos: String?, total: String?) =
/** /**
* Transform a raw position + total field into a position a way that tolerates placeholder values. * Transform a raw position + total field into a position a way that tolerates placeholder values.
*
* @param pos The position value, or null if not present. * @param pos The position value, or null if not present.
* @param total The total value, if not present. * @param total The total value, if not present.
* @return The position value extracted from the field, or null if: * @return The position value extracted from the field, or null if:
@ -151,6 +161,7 @@ fun transformPositionField(pos: Int?, total: Int?) =
* Parse a multi-value genre name using ID3 rules. This will convert any ID3v1 integer * Parse a multi-value genre name using ID3 rules. This will convert any ID3v1 integer
* representations of genre fields into their named counterparts, and split up singular ID3v2-style * representations of genre fields into their named counterparts, and split up singular ID3v2-style
* integer genre fields into one or more genres. * integer genre fields into one or more genres.
*
* @param settings [MusicSettings] required to obtain user separator configuration. * @param settings [MusicSettings] required to obtain user separator configuration.
* @return A list of one or more genre names.. * @return A list of one or more genre names..
*/ */
@ -164,6 +175,7 @@ fun List<String>.parseId3GenreNames(settings: MusicSettings) =
/** /**
* Parse a single ID3v1/ID3v2 integer genre field into their named representations. * Parse a single ID3v1/ID3v2 integer genre field into their named representations.
*
* @param settings [MusicSettings] required to obtain user separator configuration. * @param settings [MusicSettings] required to obtain user separator configuration.
* @return A list of one or more genre names. * @return A list of one or more genre names.
*/ */
@ -172,8 +184,9 @@ private fun String.parseId3MultiValueGenre(settings: MusicSettings) =
/** /**
* Parse an ID3v1 integer genre field. * Parse an ID3v1 integer genre field.
*
* @return A named genre if the field is a valid integer, "Cover" or "Remix" if the field is * @return A named genre if the field is a valid integer, "Cover" or "Remix" if the field is
* "CR"/"RX" respectively, and nothing if the field is not a valid ID3v1 integer genre. * "CR"/"RX" respectively, and nothing if the field is not a valid ID3v1 integer genre.
*/ */
private fun String.parseId3v1Genre(): String? { private fun String.parseId3v1Genre(): String? {
// ID3v1 genres are a plain integer value without formatting, so in that case // ID3v1 genres are a plain integer value without formatting, so in that case
@ -200,6 +213,7 @@ private val ID3V2_GENRE_RE = Regex("((?:\\((\\d+|RX|CR)\\))*)(.+)?")
/** /**
* Parse an ID3v2 integer genre field, which has support for multiple genre values and combined * Parse an ID3v2 integer genre field, which has support for multiple genre values and combined
* named/integer genres. * named/integer genres.
*
* @return A list of one or more genres, or null if the field is not a valid ID3v2 integer genre. * @return A list of one or more genres, or null if the field is not a valid ID3v2 integer genre.
*/ */
private fun String.parseId3v2Genre(): List<String>? { private fun String.parseId3v2Genre(): List<String>? {

View file

@ -24,6 +24,7 @@ import com.google.android.exoplayer2.metadata.vorbis.VorbisComment
/** /**
* Processing wrapper for [Metadata] that allows organized access to text-based audio tags. * Processing wrapper for [Metadata] that allows organized access to text-based audio tags.
*
* @param metadata The [Metadata] to wrap. * @param metadata The [Metadata] to wrap.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@ -79,8 +80,9 @@ class TextTags(metadata: Metadata) {
/** /**
* Copies and sanitizes a possibly invalid string outputted from ExoPlayer. * Copies and sanitizes a possibly invalid string outputted from ExoPlayer.
*
* @return A new string allocated in a memory-safe manner with any UTF-8 errors replaced with * @return A new string allocated in a memory-safe manner with any UTF-8 errors replaced with
* the Unicode replacement byte sequence. * the Unicode replacement byte sequence.
*/ */
private fun String.sanitize() = String(encodeToByteArray()) private fun String.sanitize() = String(encodeToByteArray())
} }

View file

@ -47,14 +47,16 @@ interface Library {
/** /**
* Finds a [Music] item [T] in the library by it's [Music.UID]. * Finds a [Music] item [T] in the library by it's [Music.UID].
*
* @param uid The [Music.UID] to search for. * @param uid The [Music.UID] to search for.
* @return The [T] corresponding to the given [Music.UID], or null if nothing could be found or * @return The [T] corresponding to the given [Music.UID], or null if nothing could be found or
* the [Music.UID] did not correspond to a [T]. * the [Music.UID] did not correspond to a [T].
*/ */
fun <T : Music> find(uid: Music.UID): T? fun <T : Music> find(uid: Music.UID): T?
/** /**
* Convert a [Song] from an another library into a [Song] in this [Library]. * Convert a [Song] from an another library into a [Song] in this [Library].
*
* @param song The [Song] to convert. * @param song The [Song] to convert.
* @return The analogous [Song] in this [Library], or null if it does not exist. * @return The analogous [Song] in this [Library], or null if it does not exist.
*/ */
@ -62,6 +64,7 @@ interface Library {
/** /**
* Convert a [MusicParent] from an another library into a [MusicParent] in this [Library]. * Convert a [MusicParent] from an another library into a [MusicParent] in this [Library].
*
* @param parent The [MusicParent] to convert. * @param parent The [MusicParent] to convert.
* @return The analogous [Album] in this [Library], or null if it does not exist. * @return The analogous [Album] in this [Library], or null if it does not exist.
*/ */
@ -69,6 +72,7 @@ interface Library {
/** /**
* Find a [Song] instance corresponding to the given Intent.ACTION_VIEW [Uri]. * Find a [Song] instance corresponding to the given Intent.ACTION_VIEW [Uri].
*
* @param context [Context] required to analyze the [Uri]. * @param context [Context] required to analyze the [Uri].
* @param uri [Uri] to search for. * @param uri [Uri] to search for.
* @return A [Song] corresponding to the given [Uri], or null if one could not be found. * @return A [Song] corresponding to the given [Uri], or null if one could not be found.
@ -78,6 +82,7 @@ interface Library {
companion object { companion object {
/** /**
* Create an instance of [Library]. * Create an instance of [Library].
*
* @param rawSongs [RawSong]s to create the library out of. * @param rawSongs [RawSong]s to create the library out of.
* @param settings [MusicSettings] required. * @param settings [MusicSettings] required.
*/ */
@ -117,9 +122,10 @@ private class LibraryImpl(rawSongs: List<RawSong>, settings: MusicSettings) : Li
/** /**
* Finds a [Music] item [T] in the library by it's [Music.UID]. * Finds a [Music] item [T] in the library by it's [Music.UID].
*
* @param uid The [Music.UID] to search for. * @param uid The [Music.UID] to search for.
* @return The [T] corresponding to the given [Music.UID], or null if nothing could be found or * @return The [T] corresponding to the given [Music.UID], or null if nothing could be found or
* the [Music.UID] did not correspond to a [T]. * the [Music.UID] did not correspond to a [T].
*/ */
@Suppress("UNCHECKED_CAST") override fun <T : Music> find(uid: Music.UID) = uidMap[uid] as? T @Suppress("UNCHECKED_CAST") override fun <T : Music> find(uid: Music.UID) = uidMap[uid] as? T
@ -130,21 +136,22 @@ private class LibraryImpl(rawSongs: List<RawSong>, settings: MusicSettings) : Li
override fun findSongForUri(context: Context, uri: Uri) = override fun findSongForUri(context: Context, uri: Uri) =
context.contentResolverSafe.useQuery( context.contentResolverSafe.useQuery(
uri, arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE)) { cursor -> uri, arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE)) { cursor ->
cursor.moveToFirst() cursor.moveToFirst()
// We are weirdly limited to DISPLAY_NAME and SIZE when trying to locate a // We are weirdly limited to DISPLAY_NAME and SIZE when trying to locate a
// song. Do what we can to hopefully find the song the user wanted to open. // song. Do what we can to hopefully find the song the user wanted to open.
val displayName = val displayName =
cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)) cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
val size = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE)) val size = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE))
songs.find { it.path.name == displayName && it.size == size } songs.find { it.path.name == displayName && it.size == size }
} }
/** /**
* Build a list [SongImpl]s from the given [RawSong]. * Build a list [SongImpl]s from the given [RawSong].
*
* @param rawSongs The [RawSong]s to build the [SongImpl]s from. * @param rawSongs The [RawSong]s to build the [SongImpl]s from.
* @param settings [MusicSettings] to obtain user parsing configuration. * @param settings [MusicSettings] to obtain user parsing configuration.
* @return A sorted list of [SongImpl]s derived from the [RawSong] that should be suitable for * @return A sorted list of [SongImpl]s derived from the [RawSong] that should be suitable for
* grouping. * grouping.
*/ */
private fun buildSongs(rawSongs: List<RawSong>, settings: MusicSettings) = private fun buildSongs(rawSongs: List<RawSong>, settings: MusicSettings) =
Sort(Sort.Mode.ByName, Sort.Direction.ASCENDING) Sort(Sort.Mode.ByName, Sort.Direction.ASCENDING)
@ -152,11 +159,12 @@ private class LibraryImpl(rawSongs: List<RawSong>, settings: MusicSettings) : Li
/** /**
* Build a list of [Album]s from the given [Song]s. * Build a list of [Album]s from the given [Song]s.
*
* @param songs The [Song]s to build [Album]s from. These will be linked with their respective * @param songs The [Song]s to build [Album]s from. These will be linked with their respective
* [Album]s when created. * [Album]s when created.
* @param settings [MusicSettings] to obtain user parsing configuration. * @param settings [MusicSettings] to obtain user parsing configuration.
* @return A non-empty list of [Album]s. These [Album]s will be incomplete and must be linked * @return A non-empty list of [Album]s. These [Album]s will be incomplete and must be linked
* with parent [Artist] instances in order to be usable. * with parent [Artist] instances in order to be usable.
*/ */
private fun buildAlbums(songs: List<SongImpl>, settings: MusicSettings): List<AlbumImpl> { private fun buildAlbums(songs: List<SongImpl>, settings: MusicSettings): List<AlbumImpl> {
// Group songs by their singular raw album, then map the raw instances and their // Group songs by their singular raw album, then map the raw instances and their
@ -171,15 +179,16 @@ private class LibraryImpl(rawSongs: List<RawSong>, settings: MusicSettings) : Li
* Group up [Song]s and [Album]s into [Artist] instances. Both of these items are required as * Group up [Song]s and [Album]s into [Artist] instances. Both of these items are required as
* they group into [Artist] instances much differently, with [Song]s being grouped primarily by * they group into [Artist] instances much differently, with [Song]s being grouped primarily by
* artist names, and [Album]s being grouped primarily by album artist names. * artist names, and [Album]s being grouped primarily by album artist names.
*
* @param songs The [Song]s to build [Artist]s from. One [Song] can result in the creation of * @param songs The [Song]s to build [Artist]s from. One [Song] can result in the creation of
* one or more [Artist] instances. These will be linked with their respective [Artist]s when * one or more [Artist] instances. These will be linked with their respective [Artist]s when
* created. * created.
* @param albums The [Album]s to build [Artist]s from. One [Album] can result in the creation of * @param albums The [Album]s to build [Artist]s from. One [Album] can result in the creation of
* one or more [Artist] instances. These will be linked with their respective [Artist]s when * one or more [Artist] instances. These will be linked with their respective [Artist]s when
* created. * created.
* @param settings [MusicSettings] to obtain user parsing configuration. * @param settings [MusicSettings] to obtain user parsing configuration.
* @return A non-empty list of [Artist]s. These [Artist]s will consist of the combined groupings * @return A non-empty list of [Artist]s. These [Artist]s will consist of the combined groupings
* of [Song]s and [Album]s. * of [Song]s and [Album]s.
*/ */
private fun buildArtists( private fun buildArtists(
songs: List<SongImpl>, songs: List<SongImpl>,
@ -210,9 +219,10 @@ private class LibraryImpl(rawSongs: List<RawSong>, settings: MusicSettings) : Li
/** /**
* Group up [Song]s into [Genre] instances. * Group up [Song]s into [Genre] instances.
*
* @param [songs] The [Song]s to build [Genre]s from. One [Song] can result in the creation of * @param [songs] The [Song]s to build [Genre]s from. One [Song] can result in the creation of
* one or more [Genre] instances. These will be linked with their respective [Genre]s when * one or more [Genre] instances. These will be linked with their respective [Genre]s when
* created. * created.
* @param settings [MusicSettings] to obtain user parsing configuration. * @param settings [MusicSettings] to obtain user parsing configuration.
* @return A non-empty list of [Genre]s. * @return A non-empty list of [Genre]s.
*/ */

View file

@ -46,14 +46,15 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
/** /**
* Library-backed implementation of [Song]. * Library-backed implementation of [Song].
*
* @param rawSong The [RawSong] to derive the member data from. * @param rawSong The [RawSong] to derive the member data from.
* @param musicSettings [MusicSettings] to for user parsing configuration. * @param musicSettings [MusicSettings] to for user parsing configuration.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class SongImpl(rawSong: RawSong, musicSettings: MusicSettings) : Song { class SongImpl(rawSong: RawSong, musicSettings: MusicSettings) : Song {
override val uid = override val uid =
// Attempt to use a MusicBrainz ID first before falling back to a hashed UID. // Attempt to use a MusicBrainz ID first before falling back to a hashed UID.
rawSong.musicBrainzId?.toUuidOrNull()?.let { Music.UID.musicBrainz(MusicMode.SONGS, it) } rawSong.musicBrainzId?.toUuidOrNull()?.let { Music.UID.musicBrainz(MusicMode.SONGS, it) }
?: Music.UID.auxio(MusicMode.SONGS) { ?: Music.UID.auxio(MusicMode.SONGS) {
// Song UIDs are based on the raw data without parsing so that they remain // Song UIDs are based on the raw data without parsing so that they remain
// consistent across music setting changes. Parents are not held up to the // consistent across music setting changes. Parents are not held up to the
@ -164,6 +165,7 @@ class SongImpl(rawSong: RawSong, musicSettings: MusicSettings) : Song {
/** /**
* Links this [Song] with a parent [Album]. * Links this [Song] with a parent [Album].
*
* @param album The parent [Album] to link to. * @param album The parent [Album] to link to.
*/ */
fun link(album: AlbumImpl) { fun link(album: AlbumImpl) {
@ -172,6 +174,7 @@ class SongImpl(rawSong: RawSong, musicSettings: MusicSettings) : Song {
/** /**
* Links this [Song] with a parent [Artist]. * Links this [Song] with a parent [Artist].
*
* @param artist The parent [Artist] to link to. * @param artist The parent [Artist] to link to.
*/ */
fun link(artist: ArtistImpl) { fun link(artist: ArtistImpl) {
@ -180,6 +183,7 @@ class SongImpl(rawSong: RawSong, musicSettings: MusicSettings) : Song {
/** /**
* Links this [Song] with a parent [Genre]. * Links this [Song] with a parent [Genre].
*
* @param genre The parent [Genre] to link to. * @param genre The parent [Genre] to link to.
*/ */
fun link(genre: GenreImpl) { fun link(genre: GenreImpl) {
@ -188,6 +192,7 @@ class SongImpl(rawSong: RawSong, musicSettings: MusicSettings) : Song {
/** /**
* Perform final validation and organization on this instance. * Perform final validation and organization on this instance.
*
* @return This instance upcasted to [Song]. * @return This instance upcasted to [Song].
*/ */
fun finalize(): Song { fun finalize(): Song {
@ -218,10 +223,11 @@ class SongImpl(rawSong: RawSong, musicSettings: MusicSettings) : Song {
/** /**
* Library-backed implementation of [Album]. * Library-backed implementation of [Album].
*
* @param rawAlbum The [RawAlbum] to derive the member data from. * @param rawAlbum The [RawAlbum] to derive the member data from.
* @param musicSettings [MusicSettings] to for user parsing configuration. * @param musicSettings [MusicSettings] to for user parsing configuration.
* @param songs The [Song]s that are a part of this [Album]. These items will be linked to this * @param songs The [Song]s that are a part of this [Album]. These items will be linked to this
* [Album]. * [Album].
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class AlbumImpl( class AlbumImpl(
@ -230,8 +236,8 @@ class AlbumImpl(
override val songs: List<SongImpl> override val songs: List<SongImpl>
) : Album { ) : Album {
override val uid = override val uid =
// Attempt to use a MusicBrainz ID first before falling back to a hashed UID. // Attempt to use a MusicBrainz ID first before falling back to a hashed UID.
rawAlbum.musicBrainzId?.let { Music.UID.musicBrainz(MusicMode.ALBUMS, it) } rawAlbum.musicBrainzId?.let { Music.UID.musicBrainz(MusicMode.ALBUMS, it) }
?: Music.UID.auxio(MusicMode.ALBUMS) { ?: Music.UID.auxio(MusicMode.ALBUMS) {
// Hash based on only names despite the presence of a date to increase stability. // Hash based on only names despite the presence of a date to increase stability.
// I don't know if there is any situation where an artist will have two albums with // I don't know if there is any situation where an artist will have two albums with
@ -286,6 +292,7 @@ class AlbumImpl(
/** /**
* Links this [Album] with a parent [Artist]. * Links this [Album] with a parent [Artist].
*
* @param artist The parent [Artist] to link to. * @param artist The parent [Artist] to link to.
*/ */
fun link(artist: ArtistImpl) { fun link(artist: ArtistImpl) {
@ -294,6 +301,7 @@ class AlbumImpl(
/** /**
* Perform final validation and organization on this instance. * Perform final validation and organization on this instance.
*
* @return This instance upcasted to [Album]. * @return This instance upcasted to [Album].
*/ */
fun finalize(): Album { fun finalize(): Album {
@ -313,11 +321,12 @@ class AlbumImpl(
/** /**
* Library-backed implementation of [Artist]. * Library-backed implementation of [Artist].
*
* @param rawArtist The [RawArtist] to derive the member data from. * @param rawArtist The [RawArtist] to derive the member data from.
* @param musicSettings [MusicSettings] to for user parsing configuration. * @param musicSettings [MusicSettings] to for user parsing configuration.
* @param songAlbums A list of the [Song]s and [Album]s that are a part of this [Artist] , either * @param songAlbums A list of the [Song]s and [Album]s that are a part of this [Artist] , either
* through artist or album artist tags. Providing [Song]s to the artist is optional. These instances * through artist or album artist tags. Providing [Song]s to the artist is optional. These
* will be linked to this [Artist]. * instances will be linked to this [Artist].
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class ArtistImpl( class ArtistImpl(
@ -326,8 +335,8 @@ class ArtistImpl(
songAlbums: List<Music> songAlbums: List<Music>
) : Artist { ) : Artist {
override val uid = override val uid =
// Attempt to use a MusicBrainz ID first before falling back to a hashed UID. // Attempt to use a MusicBrainz ID first before falling back to a hashed UID.
rawArtist.musicBrainzId?.let { Music.UID.musicBrainz(MusicMode.ARTISTS, it) } rawArtist.musicBrainzId?.let { Music.UID.musicBrainz(MusicMode.ARTISTS, it) }
?: Music.UID.auxio(MusicMode.ARTISTS) { update(rawArtist.name) } ?: Music.UID.auxio(MusicMode.ARTISTS) { update(rawArtist.name) }
override val rawName = rawArtist.name override val rawName = rawArtist.name
override val rawSortName = rawArtist.sortName override val rawSortName = rawArtist.sortName
@ -379,14 +388,16 @@ class ArtistImpl(
* Returns the original position of this [Artist]'s [RawArtist] within the given [RawArtist] * Returns the original position of this [Artist]'s [RawArtist] within the given [RawArtist]
* list. This can be used to create a consistent ordering within child [Artist] lists based on * list. This can be used to create a consistent ordering within child [Artist] lists based on
* the original tag order. * the original tag order.
*
* @param rawArtists The [RawArtist] instances to check. It is assumed that this [Artist]'s * @param rawArtists The [RawArtist] instances to check. It is assumed that this [Artist]'s
* [RawArtist] will be within the list. * [RawArtist] will be within the list.
* @return The index of the [Artist]'s [RawArtist] within the list. * @return The index of the [Artist]'s [RawArtist] within the list.
*/ */
fun getOriginalPositionIn(rawArtists: List<RawArtist>) = rawArtists.indexOf(rawArtist) fun getOriginalPositionIn(rawArtists: List<RawArtist>) = rawArtists.indexOf(rawArtist)
/** /**
* Perform final validation and organization on this instance. * Perform final validation and organization on this instance.
*
* @return This instance upcasted to [Artist]. * @return This instance upcasted to [Artist].
*/ */
fun finalize(): Artist { fun finalize(): Artist {
@ -400,6 +411,7 @@ class ArtistImpl(
} }
/** /**
* Library-backed implementation of [Genre]. * Library-backed implementation of [Genre].
*
* @param rawGenre [RawGenre] to derive the member data from. * @param rawGenre [RawGenre] to derive the member data from.
* @param musicSettings [MusicSettings] to for user parsing configuration. * @param musicSettings [MusicSettings] to for user parsing configuration.
* @param songs Child [SongImpl]s of this instance. * @param songs Child [SongImpl]s of this instance.
@ -450,14 +462,16 @@ class GenreImpl(
* Returns the original position of this [Genre]'s [RawGenre] within the given [RawGenre] list. * Returns the original position of this [Genre]'s [RawGenre] within the given [RawGenre] list.
* This can be used to create a consistent ordering within child [Genre] lists based on the * This can be used to create a consistent ordering within child [Genre] lists based on the
* original tag order. * original tag order.
*
* @param rawGenres The [RawGenre] instances to check. It is assumed that this [Genre] 's * @param rawGenres The [RawGenre] instances to check. It is assumed that this [Genre] 's
* [RawGenre] will be within the list. * [RawGenre] will be within the list.
* @return The index of the [Genre]'s [RawGenre] within the list. * @return The index of the [Genre]'s [RawGenre] within the list.
*/ */
fun getOriginalPositionIn(rawGenres: List<RawGenre>) = rawGenres.indexOf(rawGenre) fun getOriginalPositionIn(rawGenres: List<RawGenre>) = rawGenres.indexOf(rawGenre)
/** /**
* Perform final validation and organization on this instance. * Perform final validation and organization on this instance.
*
* @return This instance upcasted to [Genre]. * @return This instance upcasted to [Genre].
*/ */
fun finalize(): Music { fun finalize(): Music {
@ -468,6 +482,7 @@ class GenreImpl(
/** /**
* Update a [MessageDigest] with a lowercase [String]. * Update a [MessageDigest] with a lowercase [String].
*
* @param string The [String] to hash. If null, it will not be hashed. * @param string The [String] to hash. If null, it will not be hashed.
*/ */
@VisibleForTesting @VisibleForTesting
@ -481,6 +496,7 @@ fun MessageDigest.update(string: String?) {
/** /**
* Update a [MessageDigest] with the string representation of a [Date]. * Update a [MessageDigest] with the string representation of a [Date].
*
* @param date The [Date] to hash. If null, nothing will be done. * @param date The [Date] to hash. If null, nothing will be done.
*/ */
@VisibleForTesting @VisibleForTesting
@ -494,6 +510,7 @@ fun MessageDigest.update(date: Date?) {
/** /**
* Update a [MessageDigest] with the lowercase versions of all of the input [String]s. * Update a [MessageDigest] with the lowercase versions of all of the input [String]s.
*
* @param strings The [String]s to hash. If a [String] is null, it will not be hashed. * @param strings The [String]s to hash. If a [String] is null, it will not be hashed.
*/ */
@VisibleForTesting @VisibleForTesting
@ -503,6 +520,7 @@ fun MessageDigest.update(strings: List<String?>) {
/** /**
* Update a [MessageDigest] with the little-endian bytes of a [Int]. * Update a [MessageDigest] with the little-endian bytes of a [Int].
*
* @param n The [Int] to write. If null, nothing will be done. * @param n The [Int] to write. If null, nothing will be done.
*/ */
@VisibleForTesting @VisibleForTesting
@ -520,6 +538,7 @@ private val COLLATOR: Collator = Collator.getInstance().apply { strength = Colla
/** /**
* Provided implementation to create a [CollationKey] in the way described by [Music.collationKey]. * Provided implementation to create a [CollationKey] in the way described by [Music.collationKey].
* This should be used in all overrides of all [CollationKey]. * This should be used in all overrides of all [CollationKey].
*
* @param musicSettings [MusicSettings] required for user parsing configuration. * @param musicSettings [MusicSettings] required for user parsing configuration.
* @return A [CollationKey] that follows the specification described by [Music.collationKey]. * @return A [CollationKey] that follows the specification described by [Music.collationKey].
*/ */

View file

@ -24,6 +24,7 @@ import org.oxycblt.auxio.music.storage.Directory
/** /**
* Raw information about a [SongImpl] obtained from the filesystem/Extractor instances. * Raw information about a [SongImpl] obtained from the filesystem/Extractor instances.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class RawSong( class RawSong(
@ -88,6 +89,7 @@ class RawSong(
/** /**
* Raw information about an [AlbumImpl] obtained from the component [SongImpl] instances. * Raw information about an [AlbumImpl] obtained from the component [SongImpl] instances.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class RawAlbum( class RawAlbum(
@ -134,6 +136,7 @@ class RawAlbum(
/** /**
* Raw information about an [ArtistImpl] obtained from the component [SongImpl] and [AlbumImpl] * Raw information about an [ArtistImpl] obtained from the component [SongImpl] and [AlbumImpl]
* instances. * instances.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class RawArtist( class RawArtist(
@ -175,6 +178,7 @@ class RawArtist(
/** /**
* Raw information about a [GenreImpl] obtained from the component [SongImpl] instances. * Raw information about a [GenreImpl] obtained from the component [SongImpl] instances.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class RawGenre( class RawGenre(

View file

@ -27,6 +27,7 @@ import org.oxycblt.auxio.util.inflater
/** /**
* [RecyclerView.Adapter] that manages a list of [Directory] instances. * [RecyclerView.Adapter] that manages a list of [Directory] instances.
*
* @param listener A [DirectoryAdapter.Listener] to bind interactions to. * @param listener A [DirectoryAdapter.Listener] to bind interactions to.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@ -48,6 +49,7 @@ class DirectoryAdapter(private val listener: Listener) :
/** /**
* Add a [Directory] to the end of the list. * Add a [Directory] to the end of the list.
*
* @param dir The [Directory] to add. * @param dir The [Directory] to add.
*/ */
fun add(dir: Directory) { fun add(dir: Directory) {
@ -61,6 +63,7 @@ class DirectoryAdapter(private val listener: Listener) :
/** /**
* Add a list of [Directory] instances to the end of the list. * Add a list of [Directory] instances to the end of the list.
*
* @param dirs The [Directory instances to add. * @param dirs The [Directory instances to add.
*/ */
fun addAll(dirs: List<Directory>) { fun addAll(dirs: List<Directory>) {
@ -71,6 +74,7 @@ class DirectoryAdapter(private val listener: Listener) :
/** /**
* Remove a [Directory] from the list. * Remove a [Directory] from the list.
*
* @param dir The [Directory] to remove. Must exist in the list. * @param dir The [Directory] to remove. Must exist in the list.
*/ */
fun remove(dir: Directory) { fun remove(dir: Directory) {
@ -87,12 +91,14 @@ class DirectoryAdapter(private val listener: Listener) :
/** /**
* A [RecyclerView.Recycler] that displays a [Directory]. Use [from] to create an instance. * A [RecyclerView.Recycler] that displays a [Directory]. Use [from] to create an instance.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class MusicDirViewHolder private constructor(private val binding: ItemMusicDirBinding) : class MusicDirViewHolder private constructor(private val binding: ItemMusicDirBinding) :
DialogRecyclerView.ViewHolder(binding.root) { DialogRecyclerView.ViewHolder(binding.root) {
/** /**
* Bind new data to this instance. * Bind new data to this instance.
*
* @param dir The new [Directory] to bind. * @param dir The new [Directory] to bind.
* @param listener A [DirectoryAdapter.Listener] to bind interactions to. * @param listener A [DirectoryAdapter.Listener] to bind interactions to.
*/ */
@ -104,6 +110,7 @@ class MusicDirViewHolder private constructor(private val binding: ItemMusicDirBi
companion object { companion object {
/** /**
* Create a new instance. * Create a new instance.
*
* @param parent The parent to inflate this instance from. * @param parent The parent to inflate this instance from.
* @return A new instance. * @return A new instance.
*/ */

View file

@ -28,6 +28,7 @@ import org.oxycblt.auxio.R
/** /**
* A full absolute path to a file. Only intended for display purposes. For accessing files, URIs are * A full absolute path to a file. Only intended for display purposes. For accessing files, URIs are
* preferred in all cases due to scoped storage limitations. * preferred in all cases due to scoped storage limitations.
*
* @param name The name of the file. * @param name The name of the file.
* @param parent The parent [Directory] of the file. * @param parent The parent [Directory] of the file.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
@ -36,6 +37,7 @@ data class Path(val name: String, val parent: Directory)
/** /**
* A volume-aware relative path to a directory. * A volume-aware relative path to a directory.
*
* @param volume The [StorageVolume] that the [Directory] is contained in. * @param volume The [StorageVolume] that the [Directory] is contained in.
* @param relativePath The relative path from within the [StorageVolume] to the [Directory]. * @param relativePath The relative path from within the [StorageVolume] to the [Directory].
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
@ -43,6 +45,7 @@ data class Path(val name: String, val parent: Directory)
class Directory private constructor(val volume: StorageVolume, val relativePath: String) { class Directory private constructor(val volume: StorageVolume, val relativePath: String) {
/** /**
* Resolve the [Directory] instance into a human-readable path name. * Resolve the [Directory] instance into a human-readable path name.
*
* @param context [Context] required to obtain volume descriptions. * @param context [Context] required to obtain volume descriptions.
* @return A human-readable path. * @return A human-readable path.
* @see StorageVolume.getDescription * @see StorageVolume.getDescription
@ -55,8 +58,9 @@ class Directory private constructor(val volume: StorageVolume, val relativePath:
* violation of the document tree URI contract, but it's also the only one can sensibly work * violation of the document tree URI contract, but it's also the only one can sensibly work
* with these uris in the UI, and it doesn't exactly matter since we never write or read to * with these uris in the UI, and it doesn't exactly matter since we never write or read to
* directory. * directory.
*
* @return A URI [String] abiding by the document tree specification, or null if the [Directory] * @return A URI [String] abiding by the document tree specification, or null if the [Directory]
* is not valid. * is not valid.
*/ */
fun toDocumentTreeUri() = fun toDocumentTreeUri() =
// Document tree URIs consist of a prefixed volume name followed by a relative path. // Document tree URIs consist of a prefixed volume name followed by a relative path.
@ -84,9 +88,10 @@ class Directory private constructor(val volume: StorageVolume, val relativePath:
/** /**
* Create a new directory instance from the given components. * Create a new directory instance from the given components.
*
* @param volume The [StorageVolume] that the [Directory] is contained in. * @param volume The [StorageVolume] that the [Directory] is contained in.
* @param relativePath The relative path from within the [StorageVolume] to the [Directory]. * @param relativePath The relative path from within the [StorageVolume] to the [Directory].
* Will be stripped of any trailing separators for a consistent internal representation. * Will be stripped of any trailing separators for a consistent internal representation.
* @return A new [Directory] created from the components. * @return A new [Directory] created from the components.
*/ */
fun from(volume: StorageVolume, relativePath: String) = fun from(volume: StorageVolume, relativePath: String) =
@ -97,8 +102,9 @@ class Directory private constructor(val volume: StorageVolume, val relativePath:
* Create a new directory from a document tree URI. This is a huge violation of the document * Create a new directory from a document tree URI. This is a huge violation of the document
* tree URI contract, but it's also the only one can sensibly work with these uris in the * tree URI contract, but it's also the only one can sensibly work with these uris in the
* UI, and it doesn't exactly matter since we never write or read directory. * UI, and it doesn't exactly matter since we never write or read directory.
*
* @param storageManager [StorageManager] in order to obtain the [StorageVolume] specified * @param storageManager [StorageManager] in order to obtain the [StorageVolume] specified
* in the given URI. * in the given URI.
* @param uri The URI string to parse into a [Directory]. * @param uri The URI string to parse into a [Directory].
* @return A new [Directory] parsed from the URI, or null if the URI is not valid. * @return A new [Directory] parsed from the URI, or null if the URI is not valid.
*/ */
@ -123,26 +129,29 @@ class Directory private constructor(val volume: StorageVolume, val relativePath:
/** /**
* Represents the configuration for specific directories to filter to/from when loading music. * Represents the configuration for specific directories to filter to/from when loading music.
*
* @param dirs A list of [Directory] instances. How these are interpreted depends on [shouldInclude] * @param dirs A list of [Directory] instances. How these are interpreted depends on [shouldInclude]
* @param shouldInclude True if the library should only load from the [Directory] instances, false * @param shouldInclude True if the library should only load from the [Directory] instances, false
* if the library should not load from the [Directory] instances. * if the library should not load from the [Directory] instances.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
data class MusicDirectories(val dirs: List<Directory>, val shouldInclude: Boolean) data class MusicDirectories(val dirs: List<Directory>, val shouldInclude: Boolean)
/** /**
* A mime type of a file. Only intended for display. * A mime type of a file. Only intended for display.
*
* @param fromExtension The mime type obtained by analyzing the file extension. * @param fromExtension The mime type obtained by analyzing the file extension.
* @param fromFormat The mime type obtained by analyzing the file format. Null if could not be * @param fromFormat The mime type obtained by analyzing the file format. Null if could not be
* obtained. * obtained.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
data class MimeType(val fromExtension: String, val fromFormat: String?) { data class MimeType(val fromExtension: String, val fromFormat: String?) {
/** /**
* Resolve the mime type into a human-readable format name, such as "Ogg Vorbis". * Resolve the mime type into a human-readable format name, such as "Ogg Vorbis".
*
* @param context [Context] required to obtain human-readable strings. * @param context [Context] required to obtain human-readable strings.
* @return A human-readable name for this mime type. Will first try [fromFormat], then falling * @return A human-readable name for this mime type. Will first try [fromFormat], then falling
* back to [fromExtension], and then null if that fails. * back to [fromExtension], and then null if that fails.
*/ */
fun resolveName(context: Context): String? { fun resolveName(context: Context): String? {
// We try our best to produce a more readable name for the common audio formats. // We try our best to produce a more readable name for the common audio formats.

View file

@ -42,22 +42,25 @@ import org.oxycblt.auxio.util.logD
* music extraction process and primarily intended for redundancy for files not natively supported * music extraction process and primarily intended for redundancy for files not natively supported
* by other extractors. Solely relying on this is not recommended, as it often produces bad * by other extractors. Solely relying on this is not recommended, as it often produces bad
* metadata. * metadata.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface MediaStoreExtractor { interface MediaStoreExtractor {
/** /**
* Query the media database. * Query the media database.
*
* @return A new [Query] returned from the media database. * @return A new [Query] returned from the media database.
*/ */
suspend fun query(): Query suspend fun query(): Query
/** /**
* Consume the [Cursor] loaded after [query]. * Consume the [Cursor] loaded after [query].
*
* @param query The [Query] to consume. * @param query The [Query] to consume.
* @param cache A [Cache] used to avoid extracting metadata for cached songs, or null if no * @param cache A [Cache] used to avoid extracting metadata for cached songs, or null if no
* [Cache] was available. * [Cache] was available.
* @param incompleteSongs A channel where songs that could not be retrieved from the [Cache] * @param incompleteSongs A channel where songs that could not be retrieved from the [Cache]
* should be sent to. * should be sent to.
* @param completeSongs A channel where completed songs should be sent to. * @param completeSongs A channel where completed songs should be sent to.
*/ */
suspend fun consume( suspend fun consume(
@ -79,6 +82,7 @@ interface MediaStoreExtractor {
companion object { companion object {
/** /**
* Create a framework-backed instance. * Create a framework-backed instance.
*
* @param context [Context] required. * @param context [Context] required.
* @param musicSettings [MusicSettings] required. * @param musicSettings [MusicSettings] required.
* @return A new [MediaStoreExtractor] that will work best on the device's API level. * @return A new [MediaStoreExtractor] that will work best on the device's API level.
@ -158,27 +162,28 @@ private abstract class BaseMediaStoreExtractor(
context.contentResolverSafe.useQuery( context.contentResolverSafe.useQuery(
MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI,
arrayOf(MediaStore.Audio.Genres._ID, MediaStore.Audio.Genres.NAME)) { genreCursor -> arrayOf(MediaStore.Audio.Genres._ID, MediaStore.Audio.Genres.NAME)) { genreCursor ->
val idIndex = genreCursor.getColumnIndexOrThrow(MediaStore.Audio.Genres._ID) val idIndex = genreCursor.getColumnIndexOrThrow(MediaStore.Audio.Genres._ID)
val nameIndex = genreCursor.getColumnIndexOrThrow(MediaStore.Audio.Genres.NAME) val nameIndex = genreCursor.getColumnIndexOrThrow(MediaStore.Audio.Genres.NAME)
while (genreCursor.moveToNext()) { while (genreCursor.moveToNext()) {
val id = genreCursor.getLong(idIndex) val id = genreCursor.getLong(idIndex)
val name = genreCursor.getStringOrNull(nameIndex) ?: continue val name = genreCursor.getStringOrNull(nameIndex) ?: continue
context.contentResolverSafe.useQuery( context.contentResolverSafe.useQuery(
MediaStore.Audio.Genres.Members.getContentUri(VOLUME_EXTERNAL, id), MediaStore.Audio.Genres.Members.getContentUri(VOLUME_EXTERNAL, id),
arrayOf(MediaStore.Audio.Genres.Members._ID)) { cursor -> arrayOf(MediaStore.Audio.Genres.Members._ID)) { cursor ->
val songIdIndex = val songIdIndex =
cursor.getColumnIndexOrThrow(MediaStore.Audio.Genres.Members._ID) cursor.getColumnIndexOrThrow(MediaStore.Audio.Genres.Members._ID)
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
// Assume that a song can't inhabit multiple genre entries, as I doubt // Assume that a song can't inhabit multiple genre entries, as I
// MediaStore is actually aware that songs can have multiple genres. // doubt
genreNamesMap[cursor.getLong(songIdIndex)] = name // MediaStore is actually aware that songs can have multiple genres.
} genreNamesMap[cursor.getLong(songIdIndex)] = name
}
}
} }
} }
}
logD("Finished initialization in ${System.currentTimeMillis() - start}ms") logD("Finished initialization in ${System.currentTimeMillis() - start}ms")
return wrapQuery(cursor, genreNamesMap) return wrapQuery(cursor, genreNamesMap)
@ -232,15 +237,17 @@ private abstract class BaseMediaStoreExtractor(
/** /**
* The companion template to add to the projection's selector whenever arguments are added by * The companion template to add to the projection's selector whenever arguments are added by
* [addDirToSelector]. * [addDirToSelector].
*
* @see addDirToSelector * @see addDirToSelector
*/ */
protected abstract val dirSelectorTemplate: String protected abstract val dirSelectorTemplate: String
/** /**
* Add a [Directory] to the given list of projection selector arguments. * Add a [Directory] to the given list of projection selector arguments.
*
* @param dir The [Directory] to add. * @param dir The [Directory] to add.
* @param args The destination list to append selector arguments to that are analogous to the * @param args The destination list to append selector arguments to that are analogous to the
* given [Directory]. * given [Directory].
* @return true if the [Directory] was added, false otherwise. * @return true if the [Directory] was added, false otherwise.
* @see dirSelectorTemplate * @see dirSelectorTemplate
*/ */
@ -431,6 +438,7 @@ private class Api21MediaStoreExtractor(context: Context, musicSettings: MusicSet
/** /**
* A [BaseMediaStoreExtractor] that implements common behavior supported from API 29 onwards. * A [BaseMediaStoreExtractor] that implements common behavior supported from API 29 onwards.
*
* @param context [Context] required to query the media database. * @param context [Context] required to query the media database.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@ -494,8 +502,8 @@ private abstract class BaseApi29MediaStoreExtractor(
/** /**
* A [BaseMediaStoreExtractor] that completes the music loading process in a way compatible with at * A [BaseMediaStoreExtractor] that completes the music loading process in a way compatible with at
* API * API 29.
* 29. *
* @param context [Context] required to query the media database. * @param context [Context] required to query the media database.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@ -535,6 +543,7 @@ private class Api29MediaStoreExtractor(context: Context, musicSettings: MusicSet
/** /**
* A [BaseMediaStoreExtractor] that completes the music loading process in a way compatible from API * A [BaseMediaStoreExtractor] that completes the music loading process in a way compatible from API
* 30 onwards. * 30 onwards.
*
* @param context [Context] required to query the media database. * @param context [Context] required to query the media database.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@ -584,8 +593,9 @@ private class Api30MediaStoreExtractor(context: Context, musicSettings: MusicSet
* Unpack the track number from a combined track + disc [Int] field. These fields appear within * Unpack the track number from a combined track + disc [Int] field. These fields appear within
* MediaStore's TRACK column, and combine the track and disc value into a single field where the * MediaStore's TRACK column, and combine the track and disc value into a single field where the
* disc number is the 4th+ digit. * disc number is the 4th+ digit.
*
* @return The track number extracted from the combined integer value, or null if the value was * @return The track number extracted from the combined integer value, or null if the value was
* zero. * zero.
*/ */
private fun Int.unpackTrackNo() = transformPositionField(mod(1000), null) private fun Int.unpackTrackNo() = transformPositionField(mod(1000), null)
@ -593,6 +603,7 @@ private fun Int.unpackTrackNo() = transformPositionField(mod(1000), null)
* Unpack the disc number from a combined track + disc [Int] field. These fields appear within * Unpack the disc number from a combined track + disc [Int] field. These fields appear within
* MediaStore's TRACK column, and combine the track and disc value into a single field where the * MediaStore's TRACK column, and combine the track and disc value into a single field where the
* disc number is the 4th+ digit. * disc number is the 4th+ digit.
*
* @return The disc number extracted from the combined integer field, or null if the value was zero. * @return The disc number extracted from the combined integer field, or null if the value was zero.
*/ */
private fun Int.unpackDiscNo() = transformPositionField(div(1000), null) private fun Int.unpackDiscNo() = transformPositionField(div(1000), null)

View file

@ -41,6 +41,7 @@ import org.oxycblt.auxio.util.showToast
/** /**
* Dialog that manages the music dirs setting. * Dialog that manages the music dirs setting.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint
@ -149,8 +150,9 @@ class MusicDirsDialog :
/** /**
* Add a Document Tree [Uri] chosen by the user to the current [MusicDirectories] instance. * Add a Document Tree [Uri] chosen by the user to the current [MusicDirectories] instance.
*
* @param uri The document tree [Uri] to add, chosen by the user. Will do nothing if the [Uri] * @param uri The document tree [Uri] to add, chosen by the user. Will do nothing if the [Uri]
* is null or not valid. * is null or not valid.
*/ */
private fun addDocumentTreeUriToDirs(uri: Uri?) { private fun addDocumentTreeUriToDirs(uri: Uri?) {
if (uri == null) { if (uri == null) {

View file

@ -42,10 +42,11 @@ val Context.contentResolverSafe: ContentResolver
/** /**
* A shortcut for querying the [ContentResolver] database. * A shortcut for querying the [ContentResolver] database.
*
* @param uri The [Uri] of content to retrieve. * @param uri The [Uri] of content to retrieve.
* @param projection A list of SQL columns to query from the database. * @param projection A list of SQL columns to query from the database.
* @param selector A SQL selection statement to filter results. Spaces where arguments should be * @param selector A SQL selection statement to filter results. Spaces where arguments should be
* filled in are represented with a "?". * filled in are represented with a "?".
* @param args The arguments used for the selector. * @param args The arguments used for the selector.
* @return A [Cursor] of the queried values, organized by the column projection. * @return A [Cursor] of the queried values, organized by the column projection.
* @throws IllegalStateException If the [ContentResolver] did not return the queried [Cursor]. * @throws IllegalStateException If the [ContentResolver] did not return the queried [Cursor].
@ -61,13 +62,14 @@ fun ContentResolver.safeQuery(
/** /**
* A shortcut for [safeQuery] with [use] applied, automatically cleaning up the [Cursor]'s resources * A shortcut for [safeQuery] with [use] applied, automatically cleaning up the [Cursor]'s resources
* when no longer used. * when no longer used.
*
* @param uri The [Uri] of content to retrieve. * @param uri The [Uri] of content to retrieve.
* @param projection A list of SQL columns to query from the database. * @param projection A list of SQL columns to query from the database.
* @param selector A SQL selection statement to filter results. Spaces where arguments should be * @param selector A SQL selection statement to filter results. Spaces where arguments should be
* filled in are represented with a "?". * filled in are represented with a "?".
* @param args The arguments used for the selector. * @param args The arguments used for the selector.
* @param block The block of code to run with the queried [Cursor]. Will not be ran if the [Cursor] * @param block The block of code to run with the queried [Cursor]. Will not be ran if the [Cursor]
* is empty. * is empty.
* @throws IllegalStateException If the [ContentResolver] did not return the queried [Cursor]. * @throws IllegalStateException If the [ContentResolver] did not return the queried [Cursor].
* @see ContentResolver.query * @see ContentResolver.query
*/ */
@ -84,6 +86,7 @@ private val EXTERNAL_COVERS_URI = Uri.parse("content://media/external/audio/albu
/** /**
* Convert a [MediaStore] Song ID into a [Uri] to it's audio file. * Convert a [MediaStore] Song ID into a [Uri] to it's audio file.
*
* @return An external storage audio file [Uri]. May not exist. * @return An external storage audio file [Uri]. May not exist.
* @see ContentUris.withAppendedId * @see ContentUris.withAppendedId
* @see MediaStore.Audio.Media.EXTERNAL_CONTENT_URI * @see MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
@ -94,6 +97,7 @@ fun Long.toAudioUri() =
/** /**
* Convert a [MediaStore] Album ID into a [Uri] to it's system-provided album cover. This cover will * Convert a [MediaStore] Album ID into a [Uri] to it's system-provided album cover. This cover will
* be fast to load, but will be lower quality. * be fast to load, but will be lower quality.
*
* @return An external storage image [Uri]. May not exist. * @return An external storage image [Uri]. May not exist.
* @see ContentUris.withAppendedId * @see ContentUris.withAppendedId
*/ */
@ -105,6 +109,7 @@ fun Long.toCoverUri() = ContentUris.withAppendedId(EXTERNAL_COVERS_URI, this)
/** /**
* Provides the analogous method to [StorageManager.getStorageVolumes] method that is usable from * Provides the analogous method to [StorageManager.getStorageVolumes] method that is usable from
* API 21 to API 23, in which the [StorageManager] API was hidden and differed greatly. * API 21 to API 23, in which the [StorageManager] API was hidden and differed greatly.
*
* @see StorageManager.getStorageVolumes * @see StorageManager.getStorageVolumes
*/ */
@Suppress("NewApi") @Suppress("NewApi")
@ -114,6 +119,7 @@ private val SM_API21_GET_VOLUME_LIST_METHOD: Method by
/** /**
* Provides the analogous method to [StorageVolume.getDirectory] method that is usable from API 21 * Provides the analogous method to [StorageVolume.getDirectory] method that is usable from API 21
* to API 23, in which the [StorageVolume] API was hidden and differed greatly. * to API 23, in which the [StorageVolume] API was hidden and differed greatly.
*
* @see StorageVolume.getDirectory * @see StorageVolume.getDirectory
*/ */
@Suppress("NewApi") @Suppress("NewApi")
@ -122,6 +128,7 @@ private val SV_API21_GET_PATH_METHOD: Method by lazyReflectedMethod(StorageVolum
/** /**
* The [StorageVolume] considered the "primary" volume by the system, obtained in a * The [StorageVolume] considered the "primary" volume by the system, obtained in a
* version-compatible manner. * version-compatible manner.
*
* @see StorageManager.getPrimaryStorageVolume * @see StorageManager.getPrimaryStorageVolume
* @see StorageVolume.isPrimary * @see StorageVolume.isPrimary
*/ */
@ -131,6 +138,7 @@ val StorageManager.primaryStorageVolumeCompat: StorageVolume
/** /**
* The list of [StorageVolume]s currently recognized by [StorageManager], in a version-compatible * The list of [StorageVolume]s currently recognized by [StorageManager], in a version-compatible
* manner. * manner.
*
* @see StorageManager.getStorageVolumes * @see StorageManager.getStorageVolumes
*/ */
val StorageManager.storageVolumesCompat: List<StorageVolume> val StorageManager.storageVolumesCompat: List<StorageVolume>
@ -145,6 +153,7 @@ val StorageManager.storageVolumesCompat: List<StorageVolume>
/** /**
* The the absolute path to this [StorageVolume]'s directory within the file-system, in a * The the absolute path to this [StorageVolume]'s directory within the file-system, in a
* version-compatible manner. Will be null if the [StorageVolume] cannot be read. * version-compatible manner. Will be null if the [StorageVolume] cannot be read.
*
* @see StorageVolume.getDirectory * @see StorageVolume.getDirectory
*/ */
val StorageVolume.directoryCompat: String? val StorageVolume.directoryCompat: String?
@ -164,6 +173,7 @@ val StorageVolume.directoryCompat: String?
/** /**
* Get the human-readable description of this volume, such as "Internal Shared Storage". * Get the human-readable description of this volume, such as "Internal Shared Storage".
*
* @param context [Context] required to obtain human-readable string resources. * @param context [Context] required to obtain human-readable string resources.
* @return A human-readable name for this volume. * @return A human-readable name for this volume.
*/ */
@ -173,6 +183,7 @@ fun StorageVolume.getDescriptionCompat(context: Context): String = getDescriptio
/** /**
* If this [StorageVolume] is considered the "Primary" volume where the Android System is kept. May * If this [StorageVolume] is considered the "Primary" volume where the Android System is kept. May
* still be a removable volume. * still be a removable volume.
*
* @see StorageVolume.isPrimary * @see StorageVolume.isPrimary
*/ */
val StorageVolume.isPrimaryCompat: Boolean val StorageVolume.isPrimaryCompat: Boolean
@ -181,6 +192,7 @@ val StorageVolume.isPrimaryCompat: Boolean
/** /**
* If this storage is "emulated", i.e intrinsic to the device, obtained in a version compatible * If this storage is "emulated", i.e intrinsic to the device, obtained in a version compatible
* manner. * manner.
*
* @see StorageVolume.isEmulated * @see StorageVolume.isEmulated
*/ */
val StorageVolume.isEmulatedCompat: Boolean val StorageVolume.isEmulatedCompat: Boolean
@ -198,6 +210,7 @@ val StorageVolume.isInternalCompat: Boolean
/** /**
* The unique identifier for this [StorageVolume], obtained in a version compatible manner. Can be * The unique identifier for this [StorageVolume], obtained in a version compatible manner. Can be
* null. * null.
*
* @see StorageVolume.getUuid * @see StorageVolume.getUuid
*/ */
val StorageVolume.uuidCompat: String? val StorageVolume.uuidCompat: String?
@ -206,6 +219,7 @@ val StorageVolume.uuidCompat: String?
/** /**
* The current state of this [StorageVolume], such as "mounted" or "read-only", obtained in a * The current state of this [StorageVolume], such as "mounted" or "read-only", obtained in a
* version compatible manner. * version compatible manner.
*
* @see StorageVolume.getState * @see StorageVolume.getState
*/ */
val StorageVolume.stateCompat: String val StorageVolume.stateCompat: String
@ -214,6 +228,7 @@ val StorageVolume.stateCompat: String
/** /**
* Returns the name of this volume that can be used to interact with [MediaStore], in a version * Returns the name of this volume that can be used to interact with [MediaStore], in a version
* compatible manner. Will be null if the volume is not scanned by [MediaStore]. * compatible manner. Will be null if the volume is not scanned by [MediaStore].
*
* @see StorageVolume.getMediaStoreVolumeName * @see StorageVolume.getMediaStoreVolumeName
*/ */
val StorageVolume.mediaStoreVolumeNameCompat: String? val StorageVolume.mediaStoreVolumeNameCompat: String?

View file

@ -68,6 +68,7 @@ interface Indexer {
* Register a [Controller] for this instance. This instance will handle any commands to start * Register a [Controller] for this instance. This instance will handle any commands to start
* the music loading process. There can be only one [Controller] at a time. Will invoke all * the music loading process. There can be only one [Controller] at a time. Will invoke all
* [Listener] methods to initialize the instance with the current state. * [Listener] methods to initialize the instance with the current state.
*
* @param controller The [Controller] to register. Will do nothing if already registered. * @param controller The [Controller] to register. Will do nothing if already registered.
*/ */
fun registerController(controller: Controller) fun registerController(controller: Controller)
@ -75,8 +76,9 @@ interface Indexer {
/** /**
* Unregister the [Controller] from this instance, prevent it from recieving any further * Unregister the [Controller] from this instance, prevent it from recieving any further
* commands. * commands.
*
* @param controller The [Controller] to unregister. Must be the current [Controller]. Does * @param controller The [Controller] to unregister. Must be the current [Controller]. Does
* nothing if invoked by another [Controller] implementation. * nothing if invoked by another [Controller] implementation.
*/ */
fun unregisterController(controller: Controller) fun unregisterController(controller: Controller)
@ -84,14 +86,16 @@ interface Indexer {
* Register the [Listener] for this instance. This can be used to receive rapid-fire updates to * Register the [Listener] for this instance. This can be used to receive rapid-fire updates to
* the current music loading state. There can be only one [Listener] at a time. Will invoke all * the current music loading state. There can be only one [Listener] at a time. Will invoke all
* [Listener] methods to initialize the instance with the current state. * [Listener] methods to initialize the instance with the current state.
*
* @param listener The [Listener] to add. * @param listener The [Listener] to add.
*/ */
fun registerListener(listener: Listener) fun registerListener(listener: Listener)
/** /**
* Unregister a [Listener] from this instance, preventing it from recieving any further updates. * Unregister a [Listener] from this instance, preventing it from recieving any further updates.
*
* @param listener The [Listener] to unregister. Must be the current [Listener]. Does nothing if * @param listener The [Listener] to unregister. Must be the current [Listener]. Does nothing if
* invoked by another [Listener] implementation. * invoked by another [Listener] implementation.
* @see Listener * @see Listener
*/ */
fun unregisterListener(listener: Listener) fun unregisterListener(listener: Listener)
@ -99,9 +103,10 @@ interface Indexer {
/** /**
* Start the indexing process. This should be done from in the background from [Controller]'s * Start the indexing process. This should be done from in the background from [Controller]'s
* context after a command has been received to start the process. * context after a command has been received to start the process.
*
* @param context [Context] required to load music. * @param context [Context] required to load music.
* @param withCache Whether to use the cache or not when loading. If false, the cache will still * @param withCache Whether to use the cache or not when loading. If false, the cache will still
* be written, but no cache entries will be loaded into the new library. * be written, but no cache entries will be loaded into the new library.
* @param scope The [CoroutineScope] to run the indexing job in. * @param scope The [CoroutineScope] to run the indexing job in.
* @return The [Job] stacking the indexing status. * @return The [Job] stacking the indexing status.
*/ */
@ -111,8 +116,9 @@ interface Indexer {
* Request that the music library should be reloaded. This should be used by components that do * Request that the music library should be reloaded. This should be used by components that do
* not manage the indexing process in order to signal that the [Indexer.Controller] should call * not manage the indexing process in order to signal that the [Indexer.Controller] should call
* [index] eventually. * [index] eventually.
*
* @param withCache Whether to use the cache when loading music. Does nothing if there is no * @param withCache Whether to use the cache when loading music. Does nothing if there is no
* [Indexer.Controller]. * [Indexer.Controller].
*/ */
fun requestReindex(withCache: Boolean) fun requestReindex(withCache: Boolean)
@ -126,6 +132,7 @@ interface Indexer {
sealed class State { sealed class State {
/** /**
* Music loading is ongoing. * Music loading is ongoing.
*
* @param indexing The current music loading progress.. * @param indexing The current music loading progress..
* @see Indexer.Indexing * @see Indexer.Indexing
*/ */
@ -133,6 +140,7 @@ interface Indexer {
/** /**
* Music loading has completed. * Music loading has completed.
*
* @param result The outcome of the music loading process. * @param result The outcome of the music loading process.
*/ */
data class Complete(val result: Result<Library>) : State() data class Complete(val result: Result<Library>) : State()
@ -140,6 +148,7 @@ interface Indexer {
/** /**
* Represents the current progress of the music loader. Usually encapsulated in a [State]. * Represents the current progress of the music loader. Usually encapsulated in a [State].
*
* @see State.Indexing * @see State.Indexing
*/ */
sealed class Indexing { sealed class Indexing {
@ -150,6 +159,7 @@ interface Indexer {
/** /**
* Music loading has a definite progress. * Music loading has a definite progress.
*
* @param current The current amount of songs that have been loaded. * @param current The current amount of songs that have been loaded.
* @param total The projected total amount of songs that will be loaded. * @param total The projected total amount of songs that will be loaded.
*/ */
@ -182,7 +192,7 @@ interface Indexer {
* Notes: * Notes:
* - Null means that no loading is going on, but no load has completed either. * - Null means that no loading is going on, but no load has completed either.
* - [State.Complete] may represent a previous load, if the current loading process was * - [State.Complete] may represent a previous load, if the current loading process was
* canceled for one reason or another. * canceled for one reason or another.
*/ */
fun onIndexerStateChanged(state: State?) fun onIndexerStateChanged(state: State?)
} }
@ -195,8 +205,9 @@ interface Indexer {
/** /**
* Called when a new music loading process was requested. Implementations should forward * Called when a new music loading process was requested. Implementations should forward
* this to [index]. * this to [index].
*
* @param withCache Whether to use the cache or not when loading. If false, the cache should * @param withCache Whether to use the cache or not when loading. If false, the cache should
* still be written, but no cache entries will be loaded into the new library. * still be written, but no cache entries will be loaded into the new library.
* @see index * @see index
*/ */
fun onStartIndexing(withCache: Boolean) fun onStartIndexing(withCache: Boolean)
@ -390,8 +401,9 @@ constructor(
* Emit a new [Indexer.State.Indexing] state. This can be used to signal the current state of * Emit a new [Indexer.State.Indexing] state. This can be used to signal the current state of
* the music loading process to external code. Assumes that the callee has already checked if * the music loading process to external code. Assumes that the callee has already checked if
* they have not been canceled and thus have the ability to emit a new state. * they have not been canceled and thus have the ability to emit a new state.
*
* @param indexing The new [Indexer.Indexing] state to emit, or null if no loading process is * @param indexing The new [Indexer.Indexing] state to emit, or null if no loading process is
* occurring. * occurring.
*/ */
@Synchronized @Synchronized
private fun emitIndexing(indexing: Indexer.Indexing?) { private fun emitIndexing(indexing: Indexer.Indexing?) {
@ -409,8 +421,9 @@ constructor(
* Emit a new [Indexer.State.Complete] state. This can be used to signal the completion of the * Emit a new [Indexer.State.Complete] state. This can be used to signal the completion of the
* music loading process to external code. Will check if the callee has not been canceled and * music loading process to external code. Will check if the callee has not been canceled and
* thus has the ability to emit a new state * thus has the ability to emit a new state
*
* @param result The new [Result] to emit, representing the outcome of the music loading * @param result The new [Result] to emit, representing the outcome of the music loading
* process. * process.
*/ */
private suspend fun emitCompletion(result: Result<Library>) { private suspend fun emitCompletion(result: Result<Library>) {
yield() yield()

View file

@ -29,6 +29,7 @@ import org.oxycblt.auxio.util.newMainPendingIntent
/** /**
* A dynamic [ForegroundServiceNotification] that shows the current music loading state. * A dynamic [ForegroundServiceNotification] that shows the current music loading state.
*
* @param context [Context] required to create the notification. * @param context [Context] required to create the notification.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@ -53,6 +54,7 @@ class IndexingNotification(private val context: Context) :
/** /**
* Update this notification with the new music loading state. * Update this notification with the new music loading state.
*
* @param indexing The new music loading state to display in the notification. * @param indexing The new music loading state to display in the notification.
* @return true if the notification updated, false otherwise * @return true if the notification updated, false otherwise
*/ */
@ -90,6 +92,7 @@ class IndexingNotification(private val context: Context) :
/** /**
* A static [ForegroundServiceNotification] that signals to the user that the app is currently * A static [ForegroundServiceNotification] that signals to the user that the app is currently
* monitoring the music library for changes. * monitoring the music library for changes.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class ObservingNotification(context: Context) : class ObservingNotification(context: Context) :

View file

@ -50,9 +50,9 @@ import org.oxycblt.auxio.util.logD
* This [Service] also handles automatic rescanning, as that is a similarly long-running background * This [Service] also handles automatic rescanning, as that is a similarly long-running background
* operation that would be unsuitable elsewhere in the app. * operation that would be unsuitable elsewhere in the app.
* *
* TODO: Unify with PlaybackService as part of the service independence project
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*
* TODO: Unify with PlaybackService as part of the service independence project
*/ */
@AndroidEntryPoint @AndroidEntryPoint
class IndexerService : Service(), Indexer.Controller, MusicSettings.Listener { class IndexerService : Service(), Indexer.Controller, MusicSettings.Listener {
@ -176,6 +176,7 @@ class IndexerService : Service(), Indexer.Controller, MusicSettings.Listener {
/** /**
* Update the current state to "Active", in which the service signals that music loading is * Update the current state to "Active", in which the service signals that music loading is
* on-going. * on-going.
*
* @param state The current music loading state. * @param state The current music loading state.
*/ */
private fun updateActiveSession(state: Indexer.Indexing) { private fun updateActiveSession(state: Indexer.Indexing) {

View file

@ -29,6 +29,7 @@ import org.oxycblt.auxio.util.inflater
/** /**
* An [RecyclerView.Adapter] that displays a list of [Artist] choices. * An [RecyclerView.Adapter] that displays a list of [Artist] choices.
*
* @param listener A [ClickableListListener] to bind interactions to. * @param listener A [ClickableListListener] to bind interactions to.
* @author OxygenCobalt. * @author OxygenCobalt.
*/ */
@ -46,6 +47,7 @@ class ArtistChoiceAdapter(private val listener: ClickableListListener<Artist>) :
/** /**
* Immediately update the [Artist] choices. * Immediately update the [Artist] choices.
*
* @param newArtists The new [Artist]s to show. * @param newArtists The new [Artist]s to show.
*/ */
fun submitList(newArtists: List<Artist>) { fun submitList(newArtists: List<Artist>) {
@ -64,6 +66,7 @@ class ArtistChoiceViewHolder(private val binding: ItemPickerChoiceBinding) :
DialogRecyclerView.ViewHolder(binding.root) { DialogRecyclerView.ViewHolder(binding.root) {
/** /**
* Bind new data to this instance. * Bind new data to this instance.
*
* @param artist The new [Artist] to bind. * @param artist The new [Artist] to bind.
* @param listener A [ClickableListListener] to bind interactions to. * @param listener A [ClickableListListener] to bind interactions to.
*/ */
@ -76,6 +79,7 @@ class ArtistChoiceViewHolder(private val binding: ItemPickerChoiceBinding) :
companion object { companion object {
/** /**
* Create a new instance. * Create a new instance.
*
* @param parent The parent to inflate this instance from. * @param parent The parent to inflate this instance from.
* @return A new instance. * @return A new instance.
*/ */

View file

@ -28,6 +28,7 @@ import org.oxycblt.auxio.ui.NavigationViewModel
/** /**
* An [ArtistPickerDialog] intended for when [Artist] navigation is ambiguous. * An [ArtistPickerDialog] intended for when [Artist] navigation is ambiguous.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint

View file

@ -35,6 +35,7 @@ import org.oxycblt.auxio.util.collectImmediately
* The base class for dialogs that implements common behavior across all [Artist] pickers. These are * The base class for dialogs that implements common behavior across all [Artist] pickers. These are
* shown whenever what to do with an item's [Artist] is ambiguous, as there are multiple [Artist]'s * shown whenever what to do with an item's [Artist] is ambiguous, as there are multiple [Artist]'s
* to choose from. * to choose from.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint

View file

@ -31,6 +31,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
/** /**
* An [ArtistPickerDialog] intended for when [Artist] playback is ambiguous. * An [ArtistPickerDialog] intended for when [Artist] playback is ambiguous.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint

View file

@ -29,6 +29,7 @@ import org.oxycblt.auxio.util.inflater
/** /**
* An [RecyclerView.Adapter] that displays a list of [Genre] choices. * An [RecyclerView.Adapter] that displays a list of [Genre] choices.
*
* @param listener A [ClickableListListener] to bind interactions to. * @param listener A [ClickableListListener] to bind interactions to.
* @author OxygenCobalt. * @author OxygenCobalt.
*/ */
@ -46,6 +47,7 @@ class GenreChoiceAdapter(private val listener: ClickableListListener<Genre>) :
/** /**
* Immediately update the [Genre] choices. * Immediately update the [Genre] choices.
*
* @param newGenres The new [Genre]s to show. * @param newGenres The new [Genre]s to show.
*/ */
fun submitList(newGenres: List<Genre>) { fun submitList(newGenres: List<Genre>) {
@ -64,6 +66,7 @@ class GenreChoiceViewHolder(private val binding: ItemPickerChoiceBinding) :
DialogRecyclerView.ViewHolder(binding.root) { DialogRecyclerView.ViewHolder(binding.root) {
/** /**
* Bind new data to this instance. * Bind new data to this instance.
*
* @param genre The new [Genre] to bind. * @param genre The new [Genre] to bind.
* @param listener A [ClickableListListener] to bind interactions to. * @param listener A [ClickableListListener] to bind interactions to.
*/ */
@ -76,6 +79,7 @@ class GenreChoiceViewHolder(private val binding: ItemPickerChoiceBinding) :
companion object { companion object {
/** /**
* Create a new instance. * Create a new instance.
*
* @param parent The parent to inflate this instance from. * @param parent The parent to inflate this instance from.
* @return A new instance. * @return A new instance.
*/ */

View file

@ -39,6 +39,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
/** /**
* A picker [ViewBindingDialogFragment] intended for when [Genre] playback is ambiguous. * A picker [ViewBindingDialogFragment] intended for when [Genre] playback is ambiguous.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint

View file

@ -29,6 +29,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
/** /**
* a [ViewModel] that manages the current music picker state. Make it so that the dialogs just * a [ViewModel] that manages the current music picker state. Make it so that the dialogs just
* contain the music themselves and then exit if the library changes. * contain the music themselves and then exit if the library changes.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@HiltViewModel @HiltViewModel
@ -62,6 +63,7 @@ class PickerViewModel @Inject constructor(private val musicRepository: MusicRepo
/** /**
* Set a new [currentItem] from it's [Music.UID]. * Set a new [currentItem] from it's [Music.UID].
*
* @param uid The [Music.UID] of the [Song] to update to. * @param uid The [Music.UID] of the [Song] to update to.
*/ */
fun setItemUid(uid: Music.UID) { fun setItemUid(uid: Music.UID) {

View file

@ -22,6 +22,7 @@ import org.oxycblt.auxio.IntegerTable
/** /**
* Represents a configuration option for what kind of "secondary" action to show in a particular UI * Represents a configuration option for what kind of "secondary" action to show in a particular UI
* context. * context.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
enum class ActionMode { enum class ActionMode {
@ -34,6 +35,7 @@ enum class ActionMode {
/** /**
* The integer representation of this instance. * The integer representation of this instance.
*
* @see fromIntCode * @see fromIntCode
*/ */
val intCode: Int val intCode: Int
@ -47,6 +49,7 @@ enum class ActionMode {
companion object { companion object {
/** /**
* Convert a [ActionMode] integer representation into an instance. * Convert a [ActionMode] integer representation into an instance.
*
* @param intCode An integer representation of a [ActionMode] * @param intCode An integer representation of a [ActionMode]
* @return The corresponding [ActionMode], or null if the [ActionMode] is invalid. * @return The corresponding [ActionMode], or null if the [ActionMode] is invalid.
* @see ActionMode.intCode * @see ActionMode.intCode

View file

@ -35,6 +35,7 @@ import org.oxycblt.auxio.util.getColorCompat
/** /**
* A [ViewBindingFragment] that shows the current playback state in a compact manner. * A [ViewBindingFragment] that shows the current playback state in a compact manner.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint

View file

@ -31,6 +31,7 @@ import org.oxycblt.auxio.util.getDimen
/** /**
* The [BaseBottomSheetBehavior] for the playback bottom sheet. This bottom sheet * The [BaseBottomSheetBehavior] for the playback bottom sheet. This bottom sheet
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class PlaybackBottomSheetBehavior<V : View>(context: Context, attributeSet: AttributeSet?) : class PlaybackBottomSheetBehavior<V : View>(context: Context, attributeSet: AttributeSet?) :

View file

@ -47,6 +47,7 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
/** /**
* A [ViewBindingFragment] more information about the currently playing song, alongside all * A [ViewBindingFragment] more information about the currently playing song, alongside all
* available controls. * available controls.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@AndroidEntryPoint @AndroidEntryPoint

View file

@ -31,6 +31,7 @@ import org.oxycblt.auxio.util.logD
/** /**
* User configuration specific to the playback system. * User configuration specific to the playback system.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface PlaybackSettings : Settings<PlaybackSettings.Listener> { interface PlaybackSettings : Settings<PlaybackSettings.Listener> {

View file

@ -21,52 +21,60 @@ import android.text.format.DateUtils
/** /**
* Convert milliseconds into deci-seconds (1/10th of a second). * Convert milliseconds into deci-seconds (1/10th of a second).
*
* @return A converted deci-second value. * @return A converted deci-second value.
*/ */
fun Long.msToDs() = floorDiv(100) fun Long.msToDs() = floorDiv(100)
/** /**
* Convert milliseconds into seconds. * Convert milliseconds into seconds.
*
* @return A converted second value. * @return A converted second value.
*/ */
fun Long.msToSecs() = floorDiv(1000) fun Long.msToSecs() = floorDiv(1000)
/** /**
* Convert deci-seconds (1/10th of a second) into milliseconds. * Convert deci-seconds (1/10th of a second) into milliseconds.
*
* @return A converted millisecond value. * @return A converted millisecond value.
*/ */
fun Long.dsToMs() = times(100) fun Long.dsToMs() = times(100)
/** /**
* Convert deci-seconds (1/10th of a second) into seconds. * Convert deci-seconds (1/10th of a second) into seconds.
*
* @return A converted second value. * @return A converted second value.
*/ */
fun Long.dsToSecs() = floorDiv(10) fun Long.dsToSecs() = floorDiv(10)
/** /**
* Convert seconds into milliseconds. * Convert seconds into milliseconds.
*
* @return A converted millisecond value. * @return A converted millisecond value.
*/ */
fun Long.secsToMs() = times(1000) fun Long.secsToMs() = times(1000)
/** /**
* Convert a millisecond value into a string duration. * Convert a millisecond value into a string duration.
*
* @param isElapsed Whether this duration is represents elapsed time. If this is false, then --:-- * @param isElapsed Whether this duration is represents elapsed time. If this is false, then --:--
* will be returned if the second value is 0. * will be returned if the second value is 0.
*/ */
fun Long.formatDurationMs(isElapsed: Boolean) = msToSecs().formatDurationSecs(isElapsed) fun Long.formatDurationMs(isElapsed: Boolean) = msToSecs().formatDurationSecs(isElapsed)
/** /**
* // * Format a deci-second value (1/10th of a second) into a string duration. * // * Format a deci-second value (1/10th of a second) into a string duration.
*
* @param isElapsed Whether this duration is represents elapsed time. If this is false, then --:-- * @param isElapsed Whether this duration is represents elapsed time. If this is false, then --:--
* will be returned if the second value is 0. * will be returned if the second value is 0.
*/ */
fun Long.formatDurationDs(isElapsed: Boolean) = dsToSecs().formatDurationSecs(isElapsed) fun Long.formatDurationDs(isElapsed: Boolean) = dsToSecs().formatDurationSecs(isElapsed)
/** /**
* Convert a second value into a string duration. * Convert a second value into a string duration.
*
* @param isElapsed Whether this duration is represents elapsed time. If this is false, then --:-- * @param isElapsed Whether this duration is represents elapsed time. If this is false, then --:--
* will be returned if the second value is 0. * will be returned if the second value is 0.
*/ */
fun Long.formatDurationSecs(isElapsed: Boolean): String { fun Long.formatDurationSecs(isElapsed: Boolean): String {
if (!isElapsed && this == 0L) { if (!isElapsed && this == 0L) {

View file

@ -33,6 +33,7 @@ import org.oxycblt.auxio.playback.state.*
/** /**
* An [ViewModel] that provides a safe UI frontend for the current playback state. * An [ViewModel] that provides a safe UI frontend for the current playback state.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@HiltViewModel @HiltViewModel
@ -76,6 +77,7 @@ constructor(
/** /**
* Flag signaling to open a picker dialog in order to resolve an ambiguous choice when playing a * Flag signaling to open a picker dialog in order to resolve an ambiguous choice when playing a
* [Song] from one of it's [Artist]s. * [Song] from one of it's [Artist]s.
*
* @see playFromArtist * @see playFromArtist
*/ */
val artistPickerSong: StateFlow<Song?> val artistPickerSong: StateFlow<Song?>
@ -163,6 +165,7 @@ constructor(
* - If [MusicMode.ALBUMS], the [Song] is played from it's [Album]. * - If [MusicMode.ALBUMS], the [Song] is played from it's [Album].
* - If [MusicMode.ARTISTS], the [Song] is played from one of it's [Artist]s. * - If [MusicMode.ARTISTS], the [Song] is played from one of it's [Artist]s.
* - If [MusicMode.GENRES], the [Song] is played from one of it's [Genre]s. * - If [MusicMode.GENRES], the [Song] is played from one of it's [Genre]s.
*
* @param song The [Song] to play. * @param song The [Song] to play.
* @param playbackMode The [MusicMode] to play from. * @param playbackMode The [MusicMode] to play from.
*/ */
@ -177,9 +180,10 @@ constructor(
/** /**
* Play a [Song] from one of it's [Artist]s. * Play a [Song] from one of it's [Artist]s.
*
* @param song The [Song] to play. * @param song The [Song] to play.
* @param artist The [Artist] to play from. Must be linked to the [Song]. If null, the user will * @param artist The [Artist] to play from. Must be linked to the [Song]. If null, the user will
* be prompted on what artist to play. Defaults to null. * be prompted on what artist to play. Defaults to null.
*/ */
fun playFromArtist(song: Song, artist: Artist? = null) { fun playFromArtist(song: Song, artist: Artist? = null) {
if (artist != null) { if (artist != null) {
@ -194,6 +198,7 @@ constructor(
/** /**
* Mark the [Artist] playback choice process as complete. This should occur when the [Artist] * Mark the [Artist] playback choice process as complete. This should occur when the [Artist]
* choice dialog is opened after this flag is detected. * choice dialog is opened after this flag is detected.
*
* @see playFromArtist * @see playFromArtist
*/ */
fun finishPlaybackArtistPicker() { fun finishPlaybackArtistPicker() {
@ -202,9 +207,10 @@ constructor(
/** /**
* PLay a [Song] from one of it's [Genre]s. * PLay a [Song] from one of it's [Genre]s.
*
* @param song The [Song] to play. * @param song The [Song] to play.
* @param genre The [Genre] to play from. Must be linked to the [Song]. If null, the user will * @param genre The [Genre] to play from. Must be linked to the [Song]. If null, the user will
* be prompted on what artist to play. Defaults to null. * be prompted on what artist to play. Defaults to null.
*/ */
fun playFromGenre(song: Song, genre: Genre? = null) { fun playFromGenre(song: Song, genre: Genre? = null) {
if (genre != null) { if (genre != null) {
@ -219,6 +225,7 @@ constructor(
/** /**
* Mark the [Genre] playback choice process as complete. This should occur when the [Genre] * Mark the [Genre] playback choice process as complete. This should occur when the [Genre]
* choice dialog is opened after this flag is detected. * choice dialog is opened after this flag is detected.
*
* @see playFromGenre * @see playFromGenre
*/ */
fun finishPlaybackGenrePicker() { fun finishPlaybackGenrePicker() {
@ -227,24 +234,28 @@ constructor(
/** /**
* Play an [Album]. * Play an [Album].
*
* @param album The [Album] to play. * @param album The [Album] to play.
*/ */
fun play(album: Album) = playImpl(null, album, false) fun play(album: Album) = playImpl(null, album, false)
/** /**
* Play an [Artist]. * Play an [Artist].
*
* @param artist The [Artist] to play. * @param artist The [Artist] to play.
*/ */
fun play(artist: Artist) = playImpl(null, artist, false) fun play(artist: Artist) = playImpl(null, artist, false)
/** /**
* Play a [Genre]. * Play a [Genre].
*
* @param genre The [Genre] to play. * @param genre The [Genre] to play.
*/ */
fun play(genre: Genre) = playImpl(null, genre, false) fun play(genre: Genre) = playImpl(null, genre, false)
/** /**
* Play a [Music] selection. * Play a [Music] selection.
*
* @param selection The selection to play. * @param selection The selection to play.
*/ */
fun play(selection: List<Music>) = fun play(selection: List<Music>) =
@ -252,24 +263,28 @@ constructor(
/** /**
* Shuffle an [Album]. * Shuffle an [Album].
*
* @param album The [Album] to shuffle. * @param album The [Album] to shuffle.
*/ */
fun shuffle(album: Album) = playImpl(null, album, true) fun shuffle(album: Album) = playImpl(null, album, true)
/** /**
* Shuffle an [Artist]. * Shuffle an [Artist].
*
* @param artist The [Artist] to shuffle. * @param artist The [Artist] to shuffle.
*/ */
fun shuffle(artist: Artist) = playImpl(null, artist, true) fun shuffle(artist: Artist) = playImpl(null, artist, true)
/** /**
* Shuffle an [Genre]. * Shuffle an [Genre].
*
* @param genre The [Genre] to shuffle. * @param genre The [Genre] to shuffle.
*/ */
fun shuffle(genre: Genre) = playImpl(null, genre, true) fun shuffle(genre: Genre) = playImpl(null, genre, true)
/** /**
* Shuffle a [Music] selection. * Shuffle a [Music] selection.
*
* @param selection The selection to shuffle. * @param selection The selection to shuffle.
*/ */
fun shuffle(selection: List<Music>) = fun shuffle(selection: List<Music>) =
@ -298,6 +313,7 @@ constructor(
/** /**
* Start the given [InternalPlayer.Action] to be completed eventually. This can be used to * Start the given [InternalPlayer.Action] to be completed eventually. This can be used to
* enqueue a playback action at startup to then occur when the music library is fully loaded. * enqueue a playback action at startup to then occur when the music library is fully loaded.
*
* @param action The [InternalPlayer.Action] to perform eventually. * @param action The [InternalPlayer.Action] to perform eventually.
*/ */
fun startAction(action: InternalPlayer.Action) { fun startAction(action: InternalPlayer.Action) {
@ -308,6 +324,7 @@ constructor(
/** /**
* Seek to the given position in the currently playing [Song]. * Seek to the given position in the currently playing [Song].
*
* @param positionDs The position to seek to, in deci-seconds (1/10th of a second). * @param positionDs The position to seek to, in deci-seconds (1/10th of a second).
*/ */
fun seekTo(positionDs: Long) { fun seekTo(positionDs: Long) {
@ -328,6 +345,7 @@ constructor(
/** /**
* Add a [Song] to the top of the queue. * Add a [Song] to the top of the queue.
*
* @param song The [Song] to add. * @param song The [Song] to add.
*/ */
fun playNext(song: Song) { fun playNext(song: Song) {
@ -336,6 +354,7 @@ constructor(
/** /**
* Add a [Album] to the top of the queue. * Add a [Album] to the top of the queue.
*
* @param album The [Album] to add. * @param album The [Album] to add.
*/ */
fun playNext(album: Album) { fun playNext(album: Album) {
@ -344,6 +363,7 @@ constructor(
/** /**
* Add a [Artist] to the top of the queue. * Add a [Artist] to the top of the queue.
*
* @param artist The [Artist] to add. * @param artist The [Artist] to add.
*/ */
fun playNext(artist: Artist) { fun playNext(artist: Artist) {
@ -352,6 +372,7 @@ constructor(
/** /**
* Add a [Genre] to the top of the queue. * Add a [Genre] to the top of the queue.
*
* @param genre The [Genre] to add. * @param genre The [Genre] to add.
*/ */
fun playNext(genre: Genre) { fun playNext(genre: Genre) {
@ -360,6 +381,7 @@ constructor(
/** /**
* Add a selection to the top of the queue. * Add a selection to the top of the queue.
*
* @param selection The [Music] selection to add. * @param selection The [Music] selection to add.
*/ */
fun playNext(selection: List<Music>) { fun playNext(selection: List<Music>) {
@ -368,6 +390,7 @@ constructor(
/** /**
* Add a [Song] to the end of the queue. * Add a [Song] to the end of the queue.
*
* @param song The [Song] to add. * @param song The [Song] to add.
*/ */
fun addToQueue(song: Song) { fun addToQueue(song: Song) {
@ -376,6 +399,7 @@ constructor(
/** /**
* Add a [Album] to the end of the queue. * Add a [Album] to the end of the queue.
*
* @param album The [Album] to add. * @param album The [Album] to add.
*/ */
fun addToQueue(album: Album) { fun addToQueue(album: Album) {
@ -384,6 +408,7 @@ constructor(
/** /**
* Add a [Artist] to the end of the queue. * Add a [Artist] to the end of the queue.
*
* @param artist The [Artist] to add. * @param artist The [Artist] to add.
*/ */
fun addToQueue(artist: Artist) { fun addToQueue(artist: Artist) {
@ -392,6 +417,7 @@ constructor(
/** /**
* Add a [Genre] to the end of the queue. * Add a [Genre] to the end of the queue.
*
* @param genre The [Genre] to add. * @param genre The [Genre] to add.
*/ */
fun addToQueue(genre: Genre) { fun addToQueue(genre: Genre) {
@ -400,6 +426,7 @@ constructor(
/** /**
* Add a selection to the end of the queue. * Add a selection to the end of the queue.
*
* @param selection The [Music] selection to add. * @param selection The [Music] selection to add.
*/ */
fun addToQueue(selection: List<Music>) { fun addToQueue(selection: List<Music>) {
@ -420,6 +447,7 @@ constructor(
/** /**
* Toggle [repeatMode] (ex. from [RepeatMode.NONE] to [RepeatMode.TRACK]) * Toggle [repeatMode] (ex. from [RepeatMode.NONE] to [RepeatMode.TRACK])
*
* @see RepeatMode.increment * @see RepeatMode.increment
*/ */
fun toggleRepeatMode() { fun toggleRepeatMode() {
@ -430,6 +458,7 @@ constructor(
/** /**
* Force-save the current playback state. * Force-save the current playback state.
*
* @param onDone Called when the save is completed with true if successful, and false otherwise. * @param onDone Called when the save is completed with true if successful, and false otherwise.
*/ */
fun savePlaybackState(onDone: (Boolean) -> Unit) { fun savePlaybackState(onDone: (Boolean) -> Unit) {
@ -440,6 +469,7 @@ constructor(
/** /**
* Clear the current playback state. * Clear the current playback state.
*
* @param onDone Called when the wipe is completed with true if successful, and false otherwise. * @param onDone Called when the wipe is completed with true if successful, and false otherwise.
*/ */
fun wipePlaybackState(onDone: (Boolean) -> Unit) { fun wipePlaybackState(onDone: (Boolean) -> Unit) {
@ -448,8 +478,9 @@ constructor(
/** /**
* Force-restore the current playback state. * Force-restore the current playback state.
*
* @param onDone Called when the restoration is completed with true if successful, and false * @param onDone Called when the restoration is completed with true if successful, and false
* otherwise. * otherwise.
*/ */
fun tryRestorePlaybackState(onDone: (Boolean) -> Unit) { fun tryRestorePlaybackState(onDone: (Boolean) -> Unit) {
viewModelScope.launch { viewModelScope.launch {
@ -468,9 +499,10 @@ constructor(
/** /**
* Convert the given selection to a list of [Song]s. * Convert the given selection to a list of [Song]s.
*
* @param selection The selection of [Music] to convert. * @param selection The selection of [Music] to convert.
* @return A [Song] list containing the child items of any [MusicParent] instances in the list * @return A [Song] list containing the child items of any [MusicParent] instances in the list
* alongside the unchanged [Song]s or the original selection. * alongside the unchanged [Song]s or the original selection.
*/ */
private fun selectionToSongs(selection: List<Music>): List<Song> { private fun selectionToSongs(selection: List<Music>): List<Song> {
return selection.flatMap { return selection.flatMap {

View file

@ -32,6 +32,7 @@ import org.oxycblt.auxio.playback.state.RepeatMode
/** /**
* Provides raw access to the database storing the persisted playback state. * Provides raw access to the database storing the persisted playback state.
*
* @author Alexander Capehart * @author Alexander Capehart
*/ */
@Database( @Database(
@ -42,12 +43,14 @@ import org.oxycblt.auxio.playback.state.RepeatMode
abstract class PersistenceDatabase : RoomDatabase() { abstract class PersistenceDatabase : RoomDatabase() {
/** /**
* Get the current [PlaybackStateDao]. * Get the current [PlaybackStateDao].
*
* @return A [PlaybackStateDao] providing control of the database's playback state tables. * @return A [PlaybackStateDao] providing control of the database's playback state tables.
*/ */
abstract fun playbackStateDao(): PlaybackStateDao abstract fun playbackStateDao(): PlaybackStateDao
/** /**
* Get the current [QueueDao]. * Get the current [QueueDao].
*
* @return A [QueueDao] providing control of the database's queue tables. * @return A [QueueDao] providing control of the database's queue tables.
*/ */
abstract fun queueDao(): QueueDao abstract fun queueDao(): QueueDao
@ -63,12 +66,14 @@ abstract class PersistenceDatabase : RoomDatabase() {
/** /**
* Provides control of the persisted playback state table. * Provides control of the persisted playback state table.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@Dao @Dao
interface PlaybackStateDao { interface PlaybackStateDao {
/** /**
* Get the previously persisted [PlaybackState]. * Get the previously persisted [PlaybackState].
*
* @return The previously persisted [PlaybackState], or null if one was not present. * @return The previously persisted [PlaybackState], or null if one was not present.
*/ */
@Query("SELECT * FROM ${PlaybackState.TABLE_NAME} WHERE id = 0") @Query("SELECT * FROM ${PlaybackState.TABLE_NAME} WHERE id = 0")
@ -79,6 +84,7 @@ interface PlaybackStateDao {
/** /**
* Insert a new [PlaybackState] into the database. * Insert a new [PlaybackState] into the database.
*
* @param state The [PlaybackState] to insert. * @param state The [PlaybackState] to insert.
*/ */
@Insert(onConflict = OnConflictStrategy.ABORT) suspend fun insertState(state: PlaybackState) @Insert(onConflict = OnConflictStrategy.ABORT) suspend fun insertState(state: PlaybackState)
@ -86,18 +92,21 @@ interface PlaybackStateDao {
/** /**
* Provides control of the persisted queue state tables. * Provides control of the persisted queue state tables.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
@Dao @Dao
interface QueueDao { interface QueueDao {
/** /**
* Get the previously persisted queue heap. * Get the previously persisted queue heap.
*
* @return A list of persisted [QueueHeapItem]s wrapping each heap item. * @return A list of persisted [QueueHeapItem]s wrapping each heap item.
*/ */
@Query("SELECT * FROM ${QueueHeapItem.TABLE_NAME}") suspend fun getHeap(): List<QueueHeapItem> @Query("SELECT * FROM ${QueueHeapItem.TABLE_NAME}") suspend fun getHeap(): List<QueueHeapItem>
/** /**
* Get the previously persisted queue mapping. * Get the previously persisted queue mapping.
*
* @return A list of persisted [QueueMappingItem]s wrapping each heap item. * @return A list of persisted [QueueMappingItem]s wrapping each heap item.
*/ */
@Query("SELECT * FROM ${QueueMappingItem.TABLE_NAME}") @Query("SELECT * FROM ${QueueMappingItem.TABLE_NAME}")
@ -111,12 +120,14 @@ interface QueueDao {
/** /**
* Insert new heap entries into the database. * Insert new heap entries into the database.
*
* @param heap The list of wrapped [QueueHeapItem]s to insert. * @param heap The list of wrapped [QueueHeapItem]s to insert.
*/ */
@Insert(onConflict = OnConflictStrategy.ABORT) suspend fun insertHeap(heap: List<QueueHeapItem>) @Insert(onConflict = OnConflictStrategy.ABORT) suspend fun insertHeap(heap: List<QueueHeapItem>)
/** /**
* Insert new mapping entries into the database. * Insert new mapping entries into the database.
*
* @param mapping The list of wrapped [QueueMappingItem] to insert. * @param mapping The list of wrapped [QueueMappingItem] to insert.
*/ */
@Insert(onConflict = OnConflictStrategy.ABORT) @Insert(onConflict = OnConflictStrategy.ABORT)

View file

@ -27,17 +27,20 @@ import org.oxycblt.auxio.util.logE
/** /**
* Manages the persisted playback state in a structured manner. * Manages the persisted playback state in a structured manner.
*
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface PersistenceRepository { interface PersistenceRepository {
/** /**
* Read the previously persisted [PlaybackStateManager.SavedState]. * Read the previously persisted [PlaybackStateManager.SavedState].
*
* @param library The [Library] required to de-serialize the [PlaybackStateManager.SavedState]. * @param library The [Library] required to de-serialize the [PlaybackStateManager.SavedState].
*/ */
suspend fun readState(library: Library): PlaybackStateManager.SavedState? suspend fun readState(library: Library): PlaybackStateManager.SavedState?
/** /**
* Persist a new [PlaybackStateManager.SavedState]. * Persist a new [PlaybackStateManager.SavedState].
*
* @param state The [PlaybackStateManager.SavedState] to persist. * @param state The [PlaybackStateManager.SavedState] to persist.
*/ */
suspend fun saveState(state: PlaybackStateManager.SavedState?): Boolean suspend fun saveState(state: PlaybackStateManager.SavedState?): Boolean

View file

@ -45,6 +45,7 @@ interface Queue {
val isShuffled: Boolean val isShuffled: Boolean
/** /**
* Resolve this queue into a more conventional list of [Song]s. * Resolve this queue into a more conventional list of [Song]s.
*
* @return A list of [Song] corresponding to the current queue mapping. * @return A list of [Song] corresponding to the current queue mapping.
*/ */
fun resolve(): List<Song> fun resolve(): List<Song>
@ -67,9 +68,10 @@ interface Queue {
/** /**
* An immutable representation of the queue state. * An immutable representation of the queue state.
*
* @param heap The heap of [Song]s that are/were used in the queue. This can be modified with * @param heap The heap of [Song]s that are/were used in the queue. This can be modified with
* null values to represent [Song]s that were "lost" from the heap without having to change * null values to represent [Song]s that were "lost" from the heap without having to change
* other values. * other values.
* @param orderedMapping The mapping of the [heap] to an ordered queue. * @param orderedMapping The mapping of the [heap] to an ordered queue.
* @param shuffledMapping The mapping of the [heap] to a shuffled queue. * @param shuffledMapping The mapping of the [heap] to a shuffled queue.
* @param index The index of the currently playing [Song] at the time of serialization. * @param index The index of the currently playing [Song] at the time of serialization.
@ -85,9 +87,10 @@ interface Queue {
/** /**
* Remaps the [heap] of this instance based on the given mapping function and copies it into * Remaps the [heap] of this instance based on the given mapping function and copies it into
* a new [SavedState]. * a new [SavedState].
*
* @param transform Code to remap the existing [Song] heap into a new [Song] heap. This * @param transform Code to remap the existing [Song] heap into a new [Song] heap. This
* **MUST** be the same size as the original heap. [Song] instances that could not be * **MUST** be the same size as the original heap. [Song] instances that could not be
* converted should be replaced with null in the new heap. * converted should be replaced with null in the new heap.
* @throws IllegalStateException If the invariant specified by [transform] is violated. * @throws IllegalStateException If the invariant specified by [transform] is violated.
*/ */
inline fun remap(transform: (Song?) -> Song?) = inline fun remap(transform: (Song?) -> Song?) =
@ -121,6 +124,7 @@ class EditableQueue : Queue {
/** /**
* Go to a particular index in the queue. * Go to a particular index in the queue.
*
* @param to The index of the [Song] to start playing, in the current queue mapping. * @param to The index of the [Song] to start playing, in the current queue mapping.
* @return true if the queue jumped to that position, false otherwise. * @return true if the queue jumped to that position, false otherwise.
*/ */
@ -134,11 +138,12 @@ class EditableQueue : Queue {
/** /**
* Start a new queue configuration. * Start a new queue configuration.
*
* @param play The [Song] to play, or null to start from a random position. * @param play The [Song] to play, or null to start from a random position.
* @param queue The queue of [Song]s to play. Must contain [play]. This list will become the * @param queue The queue of [Song]s to play. Must contain [play]. This list will become the
* heap internally. * heap internally.
* @param shuffled Whether to shuffle the queue or not. This changes the interpretation of * @param shuffled Whether to shuffle the queue or not. This changes the interpretation of
* [queue]. * [queue].
*/ */
fun start(play: Song?, queue: List<Song>, shuffled: Boolean) { fun start(play: Song?, queue: List<Song>, shuffled: Boolean) {
heap = queue.toMutableList() heap = queue.toMutableList()
@ -152,6 +157,7 @@ class EditableQueue : Queue {
/** /**
* Re-order the queue. * Re-order the queue.
*
* @param shuffled Whether the queue should be shuffled or not. * @param shuffled Whether the queue should be shuffled or not.
*/ */
fun reorder(shuffled: Boolean) { fun reorder(shuffled: Boolean) {
@ -185,10 +191,11 @@ class EditableQueue : Queue {
/** /**
* Add [Song]s to the top of the queue. Will start playback if nothing is playing. * Add [Song]s to the top of the queue. Will start playback if nothing is playing.
*
* @param songs The [Song]s to add. * @param songs The [Song]s to add.
* @return [Queue.ChangeResult.MAPPING] if added to an existing queue, or * @return [Queue.ChangeResult.MAPPING] if added to an existing queue, or
* [Queue.ChangeResult.SONG] if there was no prior playback and these enqueued [Song]s start new * [Queue.ChangeResult.SONG] if there was no prior playback and these enqueued [Song]s start
* playback. * new playback.
*/ */
fun playNext(songs: List<Song>): Queue.ChangeResult { fun playNext(songs: List<Song>): Queue.ChangeResult {
if (orderedMapping.isEmpty()) { if (orderedMapping.isEmpty()) {
@ -214,10 +221,11 @@ class EditableQueue : Queue {
/** /**
* Add [Song]s to the end of the queue. Will start playback if nothing is playing. * Add [Song]s to the end of the queue. Will start playback if nothing is playing.
*
* @param songs The [Song]s to add. * @param songs The [Song]s to add.
* @return [Queue.ChangeResult.MAPPING] if added to an existing queue, or * @return [Queue.ChangeResult.MAPPING] if added to an existing queue, or
* [Queue.ChangeResult.SONG] if there was no prior playback and these enqueued [Song]s start new * [Queue.ChangeResult.SONG] if there was no prior playback and these enqueued [Song]s start
* playback. * new playback.
*/ */
fun addToQueue(songs: List<Song>): Queue.ChangeResult { fun addToQueue(songs: List<Song>): Queue.ChangeResult {
if (orderedMapping.isEmpty()) { if (orderedMapping.isEmpty()) {
@ -238,11 +246,12 @@ class EditableQueue : Queue {
/** /**
* Move a [Song] at the given position to a new position. * Move a [Song] at the given position to a new position.
*
* @param src The position of the [Song] to move. * @param src The position of the [Song] to move.
* @param dst The destination position of the [Song]. * @param dst The destination position of the [Song].
* @return [Queue.ChangeResult.MAPPING] if the move occurred after the current index, * @return [Queue.ChangeResult.MAPPING] if the move occurred after the current index,
* [Queue.ChangeResult.INDEX] if the move occurred before or at the current index, requiring it * [Queue.ChangeResult.INDEX] if the move occurred before or at the current index, requiring
* to be mutated. * it to be mutated.
*/ */
fun move(src: Int, dst: Int): Queue.ChangeResult { fun move(src: Int, dst: Int): Queue.ChangeResult {
if (shuffledMapping.isNotEmpty()) { if (shuffledMapping.isNotEmpty()) {
@ -273,10 +282,11 @@ class EditableQueue : Queue {
/** /**
* Remove a [Song] at the given position. * Remove a [Song] at the given position.
*
* @param at The position of the [Song] to remove. * @param at The position of the [Song] to remove.
* @return [Queue.ChangeResult.MAPPING] if the removed [Song] was after the current index, * @return [Queue.ChangeResult.MAPPING] if the removed [Song] was after the current index,
* [Queue.ChangeResult.INDEX] if the removed [Song] was before the current index, and * [Queue.ChangeResult.INDEX] if the removed [Song] was before the current index, and
* [Queue.ChangeResult.SONG] if the currently playing [Song] was removed. * [Queue.ChangeResult.SONG] if the currently playing [Song] was removed.
*/ */
fun remove(at: Int): Queue.ChangeResult { fun remove(at: Int): Queue.ChangeResult {
if (shuffledMapping.isNotEmpty()) { if (shuffledMapping.isNotEmpty()) {
@ -311,6 +321,7 @@ class EditableQueue : Queue {
/** /**
* Convert the current state of this instance into a [Queue.SavedState]. * Convert the current state of this instance into a [Queue.SavedState].
*
* @return A new [Queue.SavedState] reflecting the exact state of the queue when called. * @return A new [Queue.SavedState] reflecting the exact state of the queue when called.
*/ */
fun toSavedState() = fun toSavedState() =
@ -321,6 +332,7 @@ class EditableQueue : Queue {
/** /**
* Update this instance from the given [Queue.SavedState]. * Update this instance from the given [Queue.SavedState].
*
* @param savedState A [Queue.SavedState] with a valid queue representation. * @param savedState A [Queue.SavedState] with a valid queue representation.
*/ */
fun applySavedState(savedState: Queue.SavedState) { fun applySavedState(savedState: Queue.SavedState) {

Some files were not shown because too many files have changed in this diff Show more