all: reformat code

Simultaniously reformat code using ktlint and ktfmt.
This commit is contained in:
Alexander Capehart 2022-09-08 20:57:46 -06:00
parent 28d28287fe
commit 09823d7829
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
55 changed files with 385 additions and 236 deletions

View file

@ -120,7 +120,14 @@ dependencies {
spotless { spotless {
kotlin { kotlin {
target "src/**/*.kt" target "src/**/*.kt"
ktfmt("0.37").dropboxStyle()
// ktlint does checking, while ktfmt actually does formatting
ktlint()
ktfmt().dropboxStyle()
licenseHeaderFile("NOTICE") licenseHeaderFile("NOTICE")
} }
} }
afterEvaluate {
preDebugBuild.dependsOn spotlessApply
}

View file

@ -21,51 +21,70 @@ package org.oxycblt.auxio
object IntegerTable { object IntegerTable {
/** SongViewHolder */ /** SongViewHolder */
const val VIEW_TYPE_SONG = 0xA000 const val VIEW_TYPE_SONG = 0xA000
/** AlbumViewHolder */ /** AlbumViewHolder */
const val VIEW_TYPE_ALBUM = 0xA001 const val VIEW_TYPE_ALBUM = 0xA001
/** ArtistViewHolder */ /** ArtistViewHolder */
const val VIEW_TYPE_ARTIST = 0xA002 const val VIEW_TYPE_ARTIST = 0xA002
/** GenreViewHolder */ /** GenreViewHolder */
const val VIEW_TYPE_GENRE = 0xA003 const val VIEW_TYPE_GENRE = 0xA003
/** HeaderViewHolder */ /** HeaderViewHolder */
const val VIEW_TYPE_HEADER = 0xA004 const val VIEW_TYPE_HEADER = 0xA004
/** SortHeaderViewHolder */ /** SortHeaderViewHolder */
const val VIEW_TYPE_SORT_HEADER = 0xA005 const val VIEW_TYPE_SORT_HEADER = 0xA005
/** AlbumDetailViewHolder */ /** AlbumDetailViewHolder */
const val VIEW_TYPE_ALBUM_DETAIL = 0xA006 const val VIEW_TYPE_ALBUM_DETAIL = 0xA006
/** AlbumSongViewHolder */ /** AlbumSongViewHolder */
const val VIEW_TYPE_ALBUM_SONG = 0xA007 const val VIEW_TYPE_ALBUM_SONG = 0xA007
/** ArtistDetailViewHolder */ /** ArtistDetailViewHolder */
const val VIEW_TYPE_ARTIST_DETAIL = 0xA008 const val VIEW_TYPE_ARTIST_DETAIL = 0xA008
/** ArtistAlbumViewHolder */ /** ArtistAlbumViewHolder */
const val VIEW_TYPE_ARTIST_ALBUM = 0xA009 const val VIEW_TYPE_ARTIST_ALBUM = 0xA009
/** ArtistSongViewHolder */ /** ArtistSongViewHolder */
const val VIEW_TYPE_ARTIST_SONG = 0xA00A const val VIEW_TYPE_ARTIST_SONG = 0xA00A
/** GenreDetailViewHolder */ /** GenreDetailViewHolder */
const val VIEW_TYPE_GENRE_DETAIL = 0xA00B const val VIEW_TYPE_GENRE_DETAIL = 0xA00B
/** DiscHeaderViewHolder */ /** DiscHeaderViewHolder */
const val VIEW_TYPE_DISC_HEADER = 0xA00C const val VIEW_TYPE_DISC_HEADER = 0xA00C
/** "Music playback" notification code */ /** "Music playback" notification code */
const val PLAYBACK_NOTIFICATION_CODE = 0xA0A0 const val PLAYBACK_NOTIFICATION_CODE = 0xA0A0
/** "Music loading" notification code */ /** "Music loading" notification code */
const val INDEXER_NOTIFICATION_CODE = 0xA0A1 const val INDEXER_NOTIFICATION_CODE = 0xA0A1
/** Intent request code */ /** Intent request code */
const val REQUEST_CODE = 0xA0C0 const val REQUEST_CODE = 0xA0C0
/** RepeatMode.NONE */ /** RepeatMode.NONE */
const val REPEAT_MODE_NONE = 0xA100 const val REPEAT_MODE_NONE = 0xA100
/** RepeatMode.ALL */ /** RepeatMode.ALL */
const val REPEAT_MODE_ALL = 0xA101 const val REPEAT_MODE_ALL = 0xA101
/** RepeatMode.TRACK */ /** RepeatMode.TRACK */
const val REPEAT_MODE_TRACK = 0xA102 const val REPEAT_MODE_TRACK = 0xA102
/** PlaybackMode.IN_GENRE */ /** PlaybackMode.IN_GENRE */
const val PLAYBACK_MODE_IN_GENRE = 0xA103 const val PLAYBACK_MODE_IN_GENRE = 0xA103
/** PlaybackMode.IN_ARTIST */ /** PlaybackMode.IN_ARTIST */
const val PLAYBACK_MODE_IN_ARTIST = 0xA104 const val PLAYBACK_MODE_IN_ARTIST = 0xA104
/** PlaybackMode.IN_ALBUM */ /** PlaybackMode.IN_ALBUM */
const val PLAYBACK_MODE_IN_ALBUM = 0xA105 const val PLAYBACK_MODE_IN_ALBUM = 0xA105
/** PlaybackMode.ALL_SONGS */ /** PlaybackMode.ALL_SONGS */
const val PLAYBACK_MODE_ALL_SONGS = 0xA106 const val PLAYBACK_MODE_ALL_SONGS = 0xA106
@ -73,10 +92,13 @@ object IntegerTable {
// const val DISPLAY_MODE_NONE = 0xA107 // const val DISPLAY_MODE_NONE = 0xA107
/** DisplayMode.SHOW_GENRES */ /** DisplayMode.SHOW_GENRES */
const val DISPLAY_MODE_SHOW_GENRES = 0xA108 const val DISPLAY_MODE_SHOW_GENRES = 0xA108
/** DisplayMode.SHOW_ARTISTS */ /** DisplayMode.SHOW_ARTISTS */
const val DISPLAY_MODE_SHOW_ARTISTS = 0xA109 const val DISPLAY_MODE_SHOW_ARTISTS = 0xA109
/** DisplayMode.SHOW_ALBUMS */ /** DisplayMode.SHOW_ALBUMS */
const val DISPLAY_MODE_SHOW_ALBUMS = 0xA10A const val DISPLAY_MODE_SHOW_ALBUMS = 0xA10A
/** DisplayMode.SHOW_SONGS */ /** DisplayMode.SHOW_SONGS */
const val DISPLAY_MODE_SHOW_SONGS = 0xA10B const val DISPLAY_MODE_SHOW_SONGS = 0xA10B
@ -85,20 +107,28 @@ object IntegerTable {
/** Sort.ByName */ /** Sort.ByName */
const val SORT_BY_NAME = 0xA10C const val SORT_BY_NAME = 0xA10C
/** Sort.ByArtist */ /** Sort.ByArtist */
const val SORT_BY_ARTIST = 0xA10D const val SORT_BY_ARTIST = 0xA10D
/** Sort.ByAlbum */ /** Sort.ByAlbum */
const val SORT_BY_ALBUM = 0xA10E const val SORT_BY_ALBUM = 0xA10E
/** Sort.ByYear */ /** Sort.ByYear */
const val SORT_BY_YEAR = 0xA10F const val SORT_BY_YEAR = 0xA10F
/** Sort.ByDuration */ /** Sort.ByDuration */
const val SORT_BY_DURATION = 0xA114 const val SORT_BY_DURATION = 0xA114
/** Sort.ByCount */ /** Sort.ByCount */
const val SORT_BY_COUNT = 0xA115 const val SORT_BY_COUNT = 0xA115
/** Sort.ByDisc */ /** Sort.ByDisc */
const val SORT_BY_DISC = 0xA116 const val SORT_BY_DISC = 0xA116
/** Sort.ByTrack */ /** Sort.ByTrack */
const val SORT_BY_TRACK = 0xA117 const val SORT_BY_TRACK = 0xA117
/** Sort.ByDateAdded */ /** Sort.ByDateAdded */
const val SORT_BY_DATE_ADDED = 0xA118 const val SORT_BY_DATE_ADDED = 0xA118
@ -106,15 +136,19 @@ object IntegerTable {
// const val REPLAY_GAIN_MODE_OFF = 0xA110 // const val REPLAY_GAIN_MODE_OFF = 0xA110
/** ReplayGainMode.Track */ /** ReplayGainMode.Track */
const val REPLAY_GAIN_MODE_TRACK = 0xA111 const val REPLAY_GAIN_MODE_TRACK = 0xA111
/** ReplayGainMode.Album */ /** ReplayGainMode.Album */
const val REPLAY_GAIN_MODE_ALBUM = 0xA112 const val REPLAY_GAIN_MODE_ALBUM = 0xA112
/** ReplayGainMode.Dynamic */ /** ReplayGainMode.Dynamic */
const val REPLAY_GAIN_MODE_DYNAMIC = 0xA113 const val REPLAY_GAIN_MODE_DYNAMIC = 0xA113
/** BarAction.Next */ /** BarAction.Next */
const val BAR_ACTION_NEXT = 0xA119 const val BAR_ACTION_NEXT = 0xA119
/** BarAction.Repeat */ /** BarAction.Repeat */
const val BAR_ACTION_REPEAT = 0xA11A const val BAR_ACTION_REPEAT = 0xA11A
/** BarAction.Shuffle */ /** BarAction.Shuffle */
const val BAR_ACTION_SHUFFLE = 0xA11B const val BAR_ACTION_SHUFFLE = 0xA11B
} }

View file

@ -42,7 +42,15 @@ import org.oxycblt.auxio.playback.queue.QueueSheetBehavior
import org.oxycblt.auxio.ui.MainNavigationAction import org.oxycblt.auxio.ui.MainNavigationAction
import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.ui.fragment.ViewBindingFragment import org.oxycblt.auxio.ui.fragment.ViewBindingFragment
import org.oxycblt.auxio.util.* import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.coordinatorLayoutBehavior
import org.oxycblt.auxio.util.getAttrColorCompat
import org.oxycblt.auxio.util.getDimen
import org.oxycblt.auxio.util.systemBarInsetsCompat
import org.oxycblt.auxio.util.unlikelyToBeNull
/** /**
* 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

View file

@ -28,7 +28,14 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.* import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MimeType
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.ReleaseType
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.recycler.Header import org.oxycblt.auxio.ui.recycler.Header

View file

@ -26,10 +26,10 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogSongDetailBinding import org.oxycblt.auxio.databinding.DialogSongDetailBinding
import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment
import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.music.formatDurationMs
/** /**
* A dialog displayed when "View properties" is selected on a song, showing more information about * A dialog displayed when "View properties" is selected on a song, showing more information about

View file

@ -29,12 +29,12 @@ import org.oxycblt.auxio.databinding.ItemDiscHeaderBinding
import org.oxycblt.auxio.detail.DiscHeader import org.oxycblt.auxio.detail.DiscHeader
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.ui.recycler.IndicatorAdapter import org.oxycblt.auxio.ui.recycler.IndicatorAdapter
import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.ui.recycler.MenuItemListener import org.oxycblt.auxio.ui.recycler.MenuItemListener
import org.oxycblt.auxio.ui.recycler.SimpleItemCallback import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.util.getPlural import org.oxycblt.auxio.util.getPlural
import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.inflater

View file

@ -143,10 +143,8 @@ private class ArtistDetailViewHolder private constructor(private val binding: It
} }
} }
private class ArtistAlbumViewHolder private class ArtistAlbumViewHolder private constructor(private val binding: ItemParentBinding) :
private constructor( IndicatorAdapter.ViewHolder(binding.root) {
private val binding: ItemParentBinding,
) : IndicatorAdapter.ViewHolder(binding.root) {
fun bind(item: Album, listener: MenuItemListener) { fun bind(item: Album, listener: MenuItemListener) {
binding.parentImage.bind(item) binding.parentImage.bind(item)
binding.parentName.text = item.resolveName(binding.context) binding.parentName.text = item.resolveName(binding.context)
@ -178,10 +176,8 @@ private constructor(
} }
} }
private class ArtistSongViewHolder private class ArtistSongViewHolder private constructor(private val binding: ItemSongBinding) :
private constructor( IndicatorAdapter.ViewHolder(binding.root) {
private val binding: ItemSongBinding,
) : IndicatorAdapter.ViewHolder(binding.root) {
fun bind(item: Song, listener: MenuItemListener) { fun bind(item: Song, listener: MenuItemListener) {
binding.songAlbumCover.bind(item) binding.songAlbumCover.bind(item)
binding.songName.text = item.resolveName(binding.context) binding.songName.text = item.resolveName(binding.context)

View file

@ -83,8 +83,7 @@ abstract class DetailAdapter<L : DetailAdapter.Listener>(
return item is Header || item is SortHeader return item is Header || item is SortHeader
} }
@Suppress("LeakingThis") @Suppress("LeakingThis") protected val differ = AsyncListDiffer(this, diffCallback)
protected val differ = AsyncListDiffer(this, diffCallback)
override val currentList: List<Item> override val currentList: List<Item>
get() = differ.currentList get() = differ.currentList

View file

@ -25,11 +25,11 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.ItemDetailBinding import org.oxycblt.auxio.databinding.ItemDetailBinding
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.ui.recycler.SimpleItemCallback import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
import org.oxycblt.auxio.ui.recycler.SongViewHolder import org.oxycblt.auxio.ui.recycler.SongViewHolder
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.util.getPlural import org.oxycblt.auxio.util.getPlural
import org.oxycblt.auxio.util.inflater import org.oxycblt.auxio.util.inflater

View file

@ -45,7 +45,12 @@ import org.oxycblt.auxio.home.list.AlbumListFragment
import org.oxycblt.auxio.home.list.ArtistListFragment import org.oxycblt.auxio.home.list.ArtistListFragment
import org.oxycblt.auxio.home.list.GenreListFragment import org.oxycblt.auxio.home.list.GenreListFragment
import org.oxycblt.auxio.home.list.SongListFragment import org.oxycblt.auxio.home.list.SongListFragment
import org.oxycblt.auxio.music.* import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicViewModel
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.system.Indexer import org.oxycblt.auxio.music.system.Indexer
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.DisplayMode
@ -53,7 +58,12 @@ import org.oxycblt.auxio.ui.MainNavigationAction
import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.fragment.ViewBindingFragment import org.oxycblt.auxio.ui.fragment.ViewBindingFragment
import org.oxycblt.auxio.util.* import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.collect
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.getColorCompat
import org.oxycblt.auxio.util.lazyReflectedField
import org.oxycblt.auxio.util.logD
/** /**
* The main "Launching Point" fragment of Auxio, allowing navigation to the detail views for each * The main "Launching Point" fragment of Auxio, allowing navigation to the detail views for each

View file

@ -21,11 +21,13 @@ import android.os.Bundle
import android.text.format.DateUtils import android.text.format.DateUtils
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import java.util.* import java.util.Formatter
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.music.secsToMs
import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.recycler.AlbumViewHolder import org.oxycblt.auxio.ui.recycler.AlbumViewHolder
@ -34,8 +36,6 @@ import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.ui.recycler.MenuItemListener import org.oxycblt.auxio.ui.recycler.MenuItemListener
import org.oxycblt.auxio.ui.recycler.SyncListDiffer import org.oxycblt.auxio.ui.recycler.SyncListDiffer
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.music.secsToMs
/** /**
* A [HomeListFragment] for showing a list of [Album]s. * A [HomeListFragment] for showing a list of [Album]s.

View file

@ -24,6 +24,7 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.recycler.ArtistViewHolder import org.oxycblt.auxio.ui.recycler.ArtistViewHolder
@ -32,7 +33,6 @@ import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.ui.recycler.MenuItemListener import org.oxycblt.auxio.ui.recycler.MenuItemListener
import org.oxycblt.auxio.ui.recycler.SyncListDiffer import org.oxycblt.auxio.ui.recycler.SyncListDiffer
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.music.formatDurationMs
/** /**
* A [HomeListFragment] for showing a list of [Artist]s. * A [HomeListFragment] for showing a list of [Artist]s.

View file

@ -24,6 +24,7 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.recycler.GenreViewHolder import org.oxycblt.auxio.ui.recycler.GenreViewHolder
@ -32,7 +33,6 @@ import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.ui.recycler.MenuItemListener import org.oxycblt.auxio.ui.recycler.MenuItemListener
import org.oxycblt.auxio.ui.recycler.SyncListDiffer import org.oxycblt.auxio.ui.recycler.SyncListDiffer
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.music.formatDurationMs
/** /**
* A [HomeListFragment] for showing a list of [Genre]s. * A [HomeListFragment] for showing a list of [Genre]s.

View file

@ -26,6 +26,8 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.music.secsToMs
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.Sort
@ -36,8 +38,6 @@ import org.oxycblt.auxio.ui.recycler.SongViewHolder
import org.oxycblt.auxio.ui.recycler.SyncListDiffer import org.oxycblt.auxio.ui.recycler.SyncListDiffer
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.music.secsToMs
/** /**
* A [HomeListFragment] for showing a list of [Song]s. * A [HomeListFragment] for showing a list of [Song]s.

View file

@ -17,6 +17,10 @@
package org.oxycblt.auxio.home.tabs package org.oxycblt.auxio.home.tabs
import org.oxycblt.auxio.home.tabs.Tab.Companion.fromSequence
import org.oxycblt.auxio.home.tabs.Tab.Companion.toSequence
import org.oxycblt.auxio.home.tabs.Tab.Invisible
import org.oxycblt.auxio.home.tabs.Tab.Visible
import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.util.logE import org.oxycblt.auxio.util.logE
@ -50,6 +54,7 @@ sealed class Tab(open val mode: DisplayMode) {
companion object { companion object {
/** The length a well-formed tab sequence should be */ /** The length a well-formed tab sequence should be */
private const val SEQUENCE_LEN = 4 private const val SEQUENCE_LEN = 4
/** The default tab sequence, represented in integer form */ /** The default tab sequence, represented in integer form */
const val SEQUENCE_DEFAULT = 0b1000_1001_1010_1011_0100 const val SEQUENCE_DEFAULT = 0b1000_1001_1010_1011_0100

View file

@ -82,7 +82,7 @@ class ArtistImageFetcher
private constructor( private constructor(
private val context: Context, private val context: Context,
private val size: Size, private val size: Size,
private val artist: Artist, private val artist: Artist
) : BaseFetcher() { ) : BaseFetcher() {
override suspend fun fetch(): FetchResult? { override suspend fun fetch(): FetchResult? {
val albums = Sort(Sort.Mode.ByName, true).albums(artist.albums) val albums = Sort(Sort.Mode.ByName, true).albums(artist.albums)
@ -104,7 +104,7 @@ class GenreImageFetcher
private constructor( private constructor(
private val context: Context, private val context: Context,
private val size: Size, private val size: Size,
private val genre: Genre, private val genre: Genre
) : BaseFetcher() { ) : BaseFetcher() {
override suspend fun fetch(): FetchResult? { override suspend fun fetch(): FetchResult? {
// Genre logic is the most complicated, as we want to ensure album cover variation (i.e // Genre logic is the most complicated, as we want to ensure album cover variation (i.e

View file

@ -30,6 +30,7 @@ import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.Date.Companion.from
import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.util.inRangeOrNull import org.oxycblt.auxio.util.inRangeOrNull
@ -53,14 +54,16 @@ sealed class Music : Item {
* fast-scrolling. * fast-scrolling.
*/ */
val sortName: String? val sortName: String?
get() = rawSortName ?: rawName?.run { get() =
when { rawSortName
length > 5 && startsWith("the ", ignoreCase = true) -> substring(4) ?: rawName?.run {
length > 4 && startsWith("an ", ignoreCase = true) -> substring(3) when {
length > 3 && startsWith("a ", ignoreCase = true) -> substring(2) length > 5 && startsWith("the ", ignoreCase = true) -> substring(4)
else -> this length > 4 && startsWith("an ", ignoreCase = true) -> substring(3)
} length > 3 && startsWith("a ", ignoreCase = true) -> substring(2)
} else -> this
}
}
/** /**
* Resolve a name from it's raw form to a form suitable to be shown in a ui. Ex. "unknown" would * Resolve a name from it's raw form to a form suitable to be shown in a ui. Ex. "unknown" would
@ -185,6 +188,7 @@ class Song constructor(raw: Raw) : Music() {
val disc = raw.disc val disc = raw.disc
private var _album: Album? = null private var _album: Album? = null
/** The album of this song. */ /** The album of this song. */
val album: Album val album: Album
get() = unlikelyToBeNull(_album) get() = unlikelyToBeNull(_album)
@ -212,6 +216,7 @@ class Song constructor(raw: Raw) : Music() {
artistName ?: album.artist.resolveName(context) artistName ?: album.artist.resolveName(context)
private val _genres: MutableList<Genre> = mutableListOf() private val _genres: MutableList<Genre> = mutableListOf()
/** /**
* The genres of this song. Most often one, but there could be multiple. There will always be at * The genres of this song. Most often one, but there could be multiple. There will always be at
* least one genre, even if it is an "unknown genre" instance. * least one genre, even if it is an "unknown genre" instance.
@ -327,6 +332,7 @@ class Album constructor(raw: Raw, override val songs: List<Song>) : MusicParent(
val durationMs = songs.sumOf { it.durationMs } val durationMs = songs.sumOf { it.durationMs }
private var _artist: Artist? = null private var _artist: Artist? = null
/** The parent artist of this album. */ /** The parent artist of this album. */
val artist: Artist val artist: Artist
get() = unlikelyToBeNull(_artist) get() = unlikelyToBeNull(_artist)
@ -634,9 +640,8 @@ class Date private constructor(private val tokens: List<Int>) : Comparable<Date>
fun from(timestamp: String): Date? { fun from(timestamp: String): Date? {
val groups = val groups =
(ISO8601_REGEX.matchEntire(timestamp) ?: return null) (ISO8601_REGEX.matchEntire(timestamp) ?: return null)
.groupValues.mapIndexedNotNull { index, s -> .groupValues
if (index % 2 != 0) s.toIntOrNull() else null .mapIndexedNotNull { index, s -> if (index % 2 != 0) s.toIntOrNull() else null }
}
return fromTokens(groups) return fromTokens(groups)
} }

View file

@ -20,6 +20,8 @@ package org.oxycblt.auxio.music
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.provider.OpenableColumns import android.provider.OpenableColumns
import org.oxycblt.auxio.music.MusicStore.Callback
import org.oxycblt.auxio.music.MusicStore.Library
import org.oxycblt.auxio.util.contentResolverSafe import org.oxycblt.auxio.util.contentResolverSafe
/** /**
@ -99,12 +101,16 @@ class MusicStore private constructor() {
/** Sanitize an old item to find the corresponding item in a new library. */ /** Sanitize an old item to find the corresponding item in a new library. */
fun sanitize(song: Song) = find<Song>(song.uid) fun sanitize(song: Song) = find<Song>(song.uid)
/** Sanitize an old item to find the corresponding item in a new library. */ /** Sanitize an old item to find the corresponding item in a new library. */
fun sanitize(songs: List<Song>) = songs.mapNotNull { sanitize(it) } fun sanitize(songs: List<Song>) = songs.mapNotNull { sanitize(it) }
/** Sanitize an old item to find the corresponding item in a new library. */ /** Sanitize an old item to find the corresponding item in a new library. */
fun sanitize(album: Album) = find<Album>(album.uid) fun sanitize(album: Album) = find<Album>(album.uid)
/** Sanitize an old item to find the corresponding item in a new library. */ /** Sanitize an old item to find the corresponding item in a new library. */
fun sanitize(artist: Artist) = find<Artist>(artist.uid) fun sanitize(artist: Artist) = find<Artist>(artist.uid)
/** Sanitize an old item to find the corresponding item in a new library. */ /** Sanitize an old item to find the corresponding item in a new library. */
fun sanitize(genre: Genre) = find<Genre>(genre.uid) fun sanitize(genre: Genre) = find<Genre>(genre.uid)

View file

@ -24,9 +24,9 @@ import android.database.Cursor
import android.net.Uri import android.net.Uri
import android.provider.MediaStore import android.provider.MediaStore
import android.text.format.DateUtils import android.text.format.DateUtils
import java.util.UUID
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import java.util.UUID
/** Shortcut for making a [ContentResolver] query with less superfluous arguments. */ /** Shortcut for making a [ContentResolver] query with less superfluous arguments. */
fun ContentResolver.queryCursor( fun ContentResolver.queryCursor(
@ -59,13 +59,17 @@ val Long.audioUri: Uri
val Long.albumCoverUri: Uri val Long.albumCoverUri: Uri
get() = ContentUris.withAppendedId(EXTERNAL_ALBUM_ART_URI, this) get() = ContentUris.withAppendedId(EXTERNAL_ALBUM_ART_URI, this)
/** Shortcut to resolve a year from a nullable date. Will return "No Date" if it is null. */ /** Shortcut to resolve a year from a nullable date. Will return "No Date" if it is null. */
fun Date?.resolveYear(context: Context) = fun Date?.resolveYear(context: Context) =
this?.resolveYear(context) ?: context.getString(R.string.def_date) this?.resolveYear(context) ?: context.getString(R.string.def_date)
/** Converts this string to a UUID, or returns null if it is not valid. */ /** Converts this string to a UUID, or returns null if it is not valid. */
fun String.toUuid() = try { UUID.fromString(this) } catch (e: IllegalArgumentException) { null } fun String.toUuid() =
try {
UUID.fromString(this)
} catch (e: IllegalArgumentException) {
null
}
/** Converts a long in milliseconds to a long in deci-seconds */ /** Converts a long in milliseconds to a long in deci-seconds */
fun Long.msToDs() = floorDiv(100) fun Long.msToDs() = floorDiv(100)
@ -115,4 +119,4 @@ fun Long.formatDurationSecs(isElapsed: Boolean): String {
} }
return durationString return durationString
} }

View file

@ -30,10 +30,12 @@ class MusicViewModel : ViewModel(), Indexer.Callback {
private val indexer = Indexer.getInstance() private val indexer = Indexer.getInstance()
private val _indexerState = MutableStateFlow<Indexer.State?>(null) private val _indexerState = MutableStateFlow<Indexer.State?>(null)
/** The current music indexing state. */ /** The current music indexing state. */
val indexerState: StateFlow<Indexer.State?> = _indexerState val indexerState: StateFlow<Indexer.State?> = _indexerState
private val _libraryExists = MutableStateFlow(false) private val _libraryExists = MutableStateFlow(false)
/** Whether a music library has successfully been loaded. */ /** Whether a music library has successfully been loaded. */
val libraryExists: StateFlow<Boolean> = _libraryExists val libraryExists: StateFlow<Boolean> = _libraryExists

View file

@ -31,7 +31,6 @@ import java.lang.reflect.Method
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.util.lazyReflectedMethod import org.oxycblt.auxio.util.lazyReflectedMethod
/** A path to a file. [name] is the stripped file name, [parent] is the parent path. */ /** A path to a file. [name] is the stripped file name, [parent] is the parent path. */
data class Path(val name: String, val parent: Directory) data class Path(val name: String, val parent: Directory)
@ -48,9 +47,9 @@ class Directory private constructor(val volume: StorageVolume, val relativePath:
// "primary" actually corresponds to the internal storage, not the primary volume. // "primary" actually corresponds to the internal storage, not the primary volume.
// Removable storage is represented with the UUID. // Removable storage is represented with the UUID.
if (volume.isInternalCompat) { if (volume.isInternalCompat) {
"${DOCUMENT_URI_PRIMARY_NAME}:${relativePath}" "$DOCUMENT_URI_PRIMARY_NAME:$relativePath"
} else { } else {
volume.uuidCompat?.let { uuid -> "${uuid}:${relativePath}" } volume.uuidCompat?.let { uuid -> "$uuid:$relativePath" }
} }
override fun hashCode(): Int { override fun hashCode(): Int {

View file

@ -1,10 +1,25 @@
/*
* Copyright (c) 2022 Auxio Project
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music.extractor package org.oxycblt.auxio.music.extractor
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
/** /** TODO: Stub class, not implemented yet */
* TODO: Stub class, not implemented yet
*/
class CacheLayer { class CacheLayer {
fun init() { fun init() {
// STUB: Add cache database // STUB: Add cache database
@ -15,4 +30,4 @@ class CacheLayer {
} }
fun maybePopulateCachedRaw(raw: Song.Raw) = false fun maybePopulateCachedRaw(raw: Song.Raw) = false
} }

View file

@ -1,3 +1,20 @@
/*
* Copyright (c) 2022 Auxio Project
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music.extractor package org.oxycblt.auxio.music.extractor
import android.content.Context import android.content.Context
@ -9,6 +26,7 @@ import android.provider.MediaStore
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.database.getIntOrNull import androidx.core.database.getIntOrNull
import androidx.core.database.getStringOrNull import androidx.core.database.getStringOrNull
import java.io.File
import org.oxycblt.auxio.music.Date import org.oxycblt.auxio.music.Date
import org.oxycblt.auxio.music.Directory import org.oxycblt.auxio.music.Directory
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
@ -20,7 +38,6 @@ import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.contentResolverSafe import org.oxycblt.auxio.util.contentResolverSafe
import org.oxycblt.auxio.util.getSystemServiceCompat import org.oxycblt.auxio.util.getSystemServiceCompat
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import java.io.File
/* /*
* This file acts as the base for most the black magic required to get a remotely sensible music * This file acts as the base for most the black magic required to get a remotely sensible music
@ -81,13 +98,13 @@ import java.io.File
*/ */
/** /**
* The layer that loads music from the MediaStore database. This is an intermediate step in * The layer that loads music from the MediaStore database. This is an intermediate step in the
* the music loading process. * music loading process.
* @author OxygenCobalt * @author OxygenCobalt
*/ */
abstract class MediaStoreLayer(private val context: Context, private val cacheLayer: CacheLayer) { abstract class MediaStoreLayer(private val context: Context, private val cacheLayer: CacheLayer) {
private var cursor: Cursor? = null private var cursor: Cursor? = null
private var idIndex = -1 private var idIndex = -1
private var titleIndex = -1 private var titleIndex = -1
private var displayNameIndex = -1 private var displayNameIndex = -1
@ -101,18 +118,17 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
private var albumIdIndex = -1 private var albumIdIndex = -1
private var artistIndex = -1 private var artistIndex = -1
private var albumArtistIndex = -1 private var albumArtistIndex = -1
private val settings = Settings(context) private val settings = Settings(context)
private val _volumes = mutableListOf<StorageVolume>() private val _volumes = mutableListOf<StorageVolume>()
protected val volumes: List<StorageVolume> get() = _volumes protected val volumes: List<StorageVolume>
get() = _volumes
/** /** Initialize this instance by making a query over the media database. */
* Initialize this instance by making a query over the media database.
*/
open fun init(): Cursor { open fun init(): Cursor {
cacheLayer.init() cacheLayer.init()
val storageManager = context.getSystemServiceCompat(StorageManager::class) val storageManager = context.getSystemServiceCompat(StorageManager::class)
_volumes.addAll(storageManager.storageVolumesCompat) _volumes.addAll(storageManager.storageVolumesCompat)
val dirs = settings.getMusicDirs(storageManager) val dirs = settings.getMusicDirs(storageManager)
@ -149,39 +165,38 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
logD("Starting query [proj: ${projection.toList()}, selector: $selector, args: $args]") logD("Starting query [proj: ${projection.toList()}, selector: $selector, args: $args]")
val cursor = requireNotNull( val cursor =
context.contentResolverSafe.queryCursor( requireNotNull(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, context.contentResolverSafe.queryCursor(
projection, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
selector, projection,
args.toTypedArray())) { "Content resolver failure: No Cursor returned" } selector,
.also { cursor = it } args.toTypedArray())) { "Content resolver failure: No Cursor returned" }
.also { cursor = it }
idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns._ID) idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns._ID)
titleIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.TITLE) titleIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.TITLE)
displayNameIndex = displayNameIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DISPLAY_NAME)
cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DISPLAY_NAME)
mimeTypeIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.MIME_TYPE) mimeTypeIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.MIME_TYPE)
sizeIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.SIZE) sizeIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.SIZE)
dateAddedIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DATE_ADDED) dateAddedIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DATE_ADDED)
dateModifiedIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DATE_MODIFIED) dateModifiedIndex =
cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DATE_MODIFIED)
durationIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DURATION) durationIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DURATION)
yearIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.YEAR) yearIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.YEAR)
albumIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ALBUM) albumIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ALBUM)
albumIdIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ALBUM_ID) albumIdIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ALBUM_ID)
artistIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ARTIST) artistIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ARTIST)
albumArtistIndex = cursor.getColumnIndexOrThrow(AUDIO_COLUMN_ALBUM_ARTIST) albumArtistIndex = cursor.getColumnIndexOrThrow(AUDIO_COLUMN_ALBUM_ARTIST)
return cursor return cursor
} }
/** /** Finalize this instance by closing the cursor and finalizing the cache. */
* Finalize this instance by closing the cursor and finalizing the cache.
*/
fun finalize(rawSongs: List<Song.Raw>) { fun finalize(rawSongs: List<Song.Raw>) {
cursor?.close() cursor?.close()
cursor = null cursor = null
cacheLayer.finalize(rawSongs) cacheLayer.finalize(rawSongs)
} }
@ -281,7 +296,8 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
} }
// The album artist field is nullable and never has placeholder values. // The album artist field is nullable and never has placeholder values.
raw.albumArtistNames = cursor.getStringOrNull(albumArtistIndex)?.maybeParseSeparators(settings) raw.albumArtistNames =
cursor.getStringOrNull(albumArtistIndex)?.maybeParseSeparators(settings)
} }
companion object { companion object {
@ -303,7 +319,6 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
} }
} }
// Note: The separation between version-specific backends may not be the cleanest. To preserve // Note: The separation between version-specific backends may not be the cleanest. To preserve
// speed, we only want to add redundancy on known issues, not with possible issues. // speed, we only want to add redundancy on known issues, not with possible issues.
@ -311,7 +326,7 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
* A [MediaStoreLayer] that completes the music loading process in a way compatible from * A [MediaStoreLayer] that completes the music loading process in a way compatible from
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class Api21MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : class Api21MediaStoreLayer(context: Context, cacheLayer: CacheLayer) :
MediaStoreLayer(context, cacheLayer) { MediaStoreLayer(context, cacheLayer) {
private var trackIndex = -1 private var trackIndex = -1
private var dataIndex = -1 private var dataIndex = -1
@ -339,7 +354,7 @@ class Api21MediaStoreLayer(context: Context, cacheLayer: CacheLayer) :
override fun buildRaw(cursor: Cursor, raw: Song.Raw) { override fun buildRaw(cursor: Cursor, raw: Song.Raw) {
super.buildRaw(cursor, raw) super.buildRaw(cursor, raw)
// DATA is equivalent to the absolute path of the file. // DATA is equivalent to the absolute path of the file.
val data = cursor.getString(dataIndex) val data = cursor.getString(dataIndex)
@ -377,17 +392,18 @@ class Api21MediaStoreLayer(context: Context, cacheLayer: CacheLayer) :
* @author OxygenCobalt * @author OxygenCobalt
*/ */
@RequiresApi(Build.VERSION_CODES.Q) @RequiresApi(Build.VERSION_CODES.Q)
open class BaseApi29MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : MediaStoreLayer(context, cacheLayer) { open class BaseApi29MediaStoreLayer(context: Context, cacheLayer: CacheLayer) :
MediaStoreLayer(context, cacheLayer) {
private var volumeIndex = -1 private var volumeIndex = -1
private var relativePathIndex = -1 private var relativePathIndex = -1
override fun init(): Cursor { override fun init(): Cursor {
val cursor = super.init() val cursor = super.init()
volumeIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.VOLUME_NAME) volumeIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.VOLUME_NAME)
relativePathIndex = relativePathIndex =
cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.RELATIVE_PATH) cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.RELATIVE_PATH)
return cursor return cursor
} }
@ -431,7 +447,8 @@ open class BaseApi29MediaStoreLayer(context: Context, cacheLayer: CacheLayer) :
* @author OxygenCobalt * @author OxygenCobalt
*/ */
@RequiresApi(Build.VERSION_CODES.Q) @RequiresApi(Build.VERSION_CODES.Q)
open class Api29MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : BaseApi29MediaStoreLayer(context, cacheLayer) { open class Api29MediaStoreLayer(context: Context, cacheLayer: CacheLayer) :
BaseApi29MediaStoreLayer(context, cacheLayer) {
private var trackIndex = -1 private var trackIndex = -1
override fun init(): Cursor { override fun init(): Cursor {
@ -445,7 +462,7 @@ open class Api29MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : Base
override fun buildRaw(cursor: Cursor, raw: Song.Raw) { override fun buildRaw(cursor: Cursor, raw: Song.Raw) {
super.buildRaw(cursor, raw) super.buildRaw(cursor, raw)
// This backend is volume-aware, but does not support the modern track fields. // This backend is volume-aware, but does not support the modern track fields.
// Use the old field instead. // Use the old field instead.
val rawTrack = cursor.getIntOrNull(trackIndex) val rawTrack = cursor.getIntOrNull(trackIndex)
@ -462,7 +479,8 @@ open class Api29MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : Base
* @author OxygenCobalt * @author OxygenCobalt
*/ */
@RequiresApi(Build.VERSION_CODES.R) @RequiresApi(Build.VERSION_CODES.R)
class Api30MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : BaseApi29MediaStoreLayer(context, cacheLayer) { class Api30MediaStoreLayer(context: Context, cacheLayer: CacheLayer) :
BaseApi29MediaStoreLayer(context, cacheLayer) {
private var trackIndex: Int = -1 private var trackIndex: Int = -1
private var discIndex: Int = -1 private var discIndex: Int = -1

View file

@ -1,20 +1,36 @@
/*
* Copyright (c) 2022 Auxio Project
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music.extractor package org.oxycblt.auxio.music.extractor
import android.content.Context import android.content.Context
import androidx.core.text.isDigitsOnly import androidx.core.text.isDigitsOnly
import com.google.android.exoplayer2.MediaItem import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.MetadataRetriever import com.google.android.exoplayer2.MetadataRetriever
import com.google.android.exoplayer2.metadata.Metadata
import com.google.android.exoplayer2.metadata.id3.TextInformationFrame import com.google.android.exoplayer2.metadata.id3.TextInformationFrame
import com.google.android.exoplayer2.metadata.vorbis.VorbisComment import com.google.android.exoplayer2.metadata.vorbis.VorbisComment
import org.oxycblt.auxio.music.Date
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.audioUri import org.oxycblt.auxio.music.audioUri
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import com.google.android.exoplayer2.metadata.Metadata
import org.oxycblt.auxio.music.Date
import org.oxycblt.auxio.util.logW import org.oxycblt.auxio.util.logW
/** /**
* The layer that leverages ExoPlayer's metadata retrieval system to index metadata. * The layer that leverages ExoPlayer's metadata retrieval system to index metadata.
* *
@ -32,14 +48,10 @@ class MetadataLayer(private val context: Context, private val mediaStoreLayer: M
private val settings = Settings(context) private val settings = Settings(context)
private val taskPool: Array<Task?> = arrayOfNulls(TASK_CAPACITY) private val taskPool: Array<Task?> = arrayOfNulls(TASK_CAPACITY)
/** /** Initialize the sub-layers that this layer relies on. */
* Initialize the sub-layers that this layer relies on.
*/
fun init() = mediaStoreLayer.init().count fun init() = mediaStoreLayer.init().count
/** /** Finalize the sub-layers that this layer relies on. */
* Finalize the sub-layers that this layer relies on.
*/
fun finalize(rawSongs: List<Song.Raw>) = mediaStoreLayer.finalize(rawSongs) fun finalize(rawSongs: List<Song.Raw>) = mediaStoreLayer.finalize(rawSongs)
fun parse(emit: (Song.Raw) -> Unit) { fun parse(emit: (Song.Raw) -> Unit) {
@ -90,7 +102,6 @@ class MetadataLayer(private val context: Context, private val mediaStoreLayer: M
} }
} }
companion object { companion object {
/** The amount of tasks this backend can run efficiently at once. */ /** The amount of tasks this backend can run efficiently at once. */
private const val TASK_CAPACITY = 8 private const val TASK_CAPACITY = 8
@ -191,7 +202,7 @@ class Task(context: Context, private val settings: Settings, private val raw: So
tags["TRCK"]?.run { get(0).parsePositionNum() }?.let { raw.track = it } tags["TRCK"]?.run { get(0).parsePositionNum() }?.let { raw.track = it }
// Disc, as NN/TT // Disc, as NN/TT
tags["TPOS"]?.run { get(0).parsePositionNum() } ?.let { raw.disc = it } tags["TPOS"]?.run { get(0).parsePositionNum() }?.let { raw.disc = it }
// Dates are somewhat complicated, as not only did their semantics change from a flat year // Dates are somewhat complicated, as not only did their semantics change from a flat year
// value in ID3v2.3 to a full ISO-8601 date in ID3v2.4, but there are also a variety of // value in ID3v2.3 to a full ISO-8601 date in ID3v2.4, but there are also a variety of
@ -203,9 +214,8 @@ class Task(context: Context, private val settings: Settings, private val raw: So
// 4. ID3v2.3 Original Date, as it is like #1 // 4. ID3v2.3 Original Date, as it is like #1
// 5. ID3v2.3 Release Year, as it is the most common date type // 5. ID3v2.3 Release Year, as it is the most common date type
(tags["TDOR"]?.run { get(0).parseTimestamp() } (tags["TDOR"]?.run { get(0).parseTimestamp() }
?: tags["TDRC"]?.run { get(0).parseTimestamp() } ?: tags["TDRC"]?.run { get(0).parseTimestamp() }
?: tags["TDRL"]?.run { get(0).parseTimestamp() } ?: tags["TDRL"]?.run { get(0).parseTimestamp() } ?: parseId3v23Date(tags))
?: parseId3v23Date(tags))
?.let { raw.date = it } ?.let { raw.date = it }
// (Sort) Album // (Sort) Album
@ -230,7 +240,9 @@ class Task(context: Context, private val settings: Settings, private val raw: So
} }
private fun parseId3v23Date(tags: Map<String, List<String>>): Date? { private fun parseId3v23Date(tags: Map<String, List<String>>): Date? {
val year = tags["TORY"]?.run { get(0).toIntOrNull() } ?: tags["TYER"]?.run { get(0).toIntOrNull() } ?: return null val year =
tags["TORY"]?.run { get(0).toIntOrNull() }
?: tags["TYER"]?.run { get(0).toIntOrNull() } ?: return null
val tdat = tags["TDAT"] val tdat = tags["TDAT"]
return if (tdat != null && tdat[0].length == 4 && tdat[0].isDigitsOnly()) { return if (tdat != null && tdat[0].length == 4 && tdat[0].isDigitsOnly()) {
@ -274,7 +286,7 @@ class Task(context: Context, private val settings: Settings, private val raw: So
// (Sort) Album // (Sort) Album
tags["ALBUM"]?.let { raw.albumName = it[0] } tags["ALBUM"]?.let { raw.albumName = it[0] }
tags["ALBUMSORT"]?.let { raw.albumSortName = it[0] } tags["ALBUMSORT"]?.let { raw.albumSortName = it[0] }
// (Sort) Artist // (Sort) Artist
tags["ARTIST"]?.let { raw.artistNames = it.parseMultiValue(settings) } tags["ARTIST"]?.let { raw.artistNames = it.parseMultiValue(settings) }

View file

@ -1,3 +1,20 @@
/*
* Copyright (c) 2022 Auxio Project
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music.extractor package org.oxycblt.auxio.music.extractor
import androidx.core.text.isDigitsOnly import androidx.core.text.isDigitsOnly
@ -50,21 +67,23 @@ fun List<String>.parseMultiValue(settings: Settings) =
} }
/** /**
* Maybe a single tag into multi values with the user-preferred separators. If not enabled, * Maybe a single tag into multi values with the user-preferred separators. If not enabled, the
* the plain string will be returned. * plain string will be returned.
*/ */
fun String.maybeParseSeparators(settings: Settings): List<String> { fun String.maybeParseSeparators(settings: Settings): List<String> {
// Get the separators the user desires. If null, we don't parse any. // Get the separators the user desires. If null, we don't parse any.
val separators = settings.separators ?: return listOf(this) val separators = settings.separators ?: return listOf(this)
// Try to cache compiled regexes for particular separator combinations. // Try to cache compiled regexes for particular separator combinations.
val regex = synchronized(SEPARATOR_REGEX_CACHE) { val regex =
SEPARATOR_REGEX_CACHE.getOrPut(separators) { Regex("[^\\\\][$separators]") } synchronized(SEPARATOR_REGEX_CACHE) {
} SEPARATOR_REGEX_CACHE.getOrPut(separators) { Regex("[^\\\\][$separators]") }
}
val escape = synchronized(ESCAPE_REGEX_CACHE) { val escape =
ESCAPE_REGEX_CACHE.getOrPut(separators) { Regex("\\\\[$separators]")} synchronized(ESCAPE_REGEX_CACHE) {
} ESCAPE_REGEX_CACHE.getOrPut(separators) { Regex("\\\\[$separators]") }
}
return regex.split(this).map { value -> return regex.split(this).map { value ->
// Convert escaped separators to their correct value // Convert escaped separators to their correct value
@ -72,15 +91,13 @@ fun String.maybeParseSeparators(settings: Settings): List<String> {
} }
} }
/** /** Parse a multi-value tag into a [ReleaseType], handling separators in the process. */
* Parse a multi-value tag into a [ReleaseType], handling separators in the process.
*/
fun List<String>.parseReleaseType(settings: Settings) = ReleaseType.parse(parseMultiValue(settings)) fun List<String>.parseReleaseType(settings: Settings) = ReleaseType.parse(parseMultiValue(settings))
/** /**
* Parse a multi-value genre name using ID3v2 rules. If there is one value, the ID3v2.3 * Parse a multi-value genre name using ID3v2 rules. If there is one value, the ID3v2.3 rules will
* rules will be used, followed by separator parsing. Otherwise, each value will be iterated * be used, followed by separator parsing. Otherwise, each value will be iterated through, and
* through, and numeric values transformed into string values. * numeric values transformed into string values.
*/ */
fun List<String>.parseId3GenreNames(settings: Settings) = fun List<String>.parseId3GenreNames(settings: Settings) =
if (size == 1) { if (size == 1) {
@ -89,13 +106,9 @@ fun List<String>.parseId3GenreNames(settings: Settings) =
map { it.parseId3v1Genre() ?: it } map { it.parseId3v1Genre() ?: it }
} }
/** /** Parse a single genre name using ID3v2.3 rules. */
* Parse a single genre name using ID3v2.3 rules.
*/
fun String.parseId3GenreNames(settings: Settings) = fun String.parseId3GenreNames(settings: Settings) =
parseId3v1Genre()?.let { listOf(it) } ?: parseId3v1Genre()?.let { listOf(it) } ?: parseId3v2Genre() ?: maybeParseSeparators(settings)
parseId3v2Genre() ?:
maybeParseSeparators(settings)
private fun String.parseId3v1Genre(): String? = private fun String.parseId3v1Genre(): String? =
when { when {

View file

@ -26,7 +26,11 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.music.* import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.extractor.Api21MediaStoreLayer import org.oxycblt.auxio.music.extractor.Api21MediaStoreLayer
import org.oxycblt.auxio.music.extractor.Api29MediaStoreLayer import org.oxycblt.auxio.music.extractor.Api29MediaStoreLayer
import org.oxycblt.auxio.music.extractor.Api30MediaStoreLayer import org.oxycblt.auxio.music.extractor.Api30MediaStoreLayer
@ -50,8 +54,8 @@ import org.oxycblt.auxio.util.logW
* 3. Using the songs to build the library, which primarily involves linking up all data objects * 3. Using the songs to build the library, which primarily involves linking up all data objects
* with their corresponding parents/children. * with their corresponding parents/children.
* *
* This class in particular handles 3 primarily. For the code that handles 1 and 2, see the * This class in particular handles 3 primarily. For the code that handles 1 and 2, see the layer
* layer implementations. * implementations.
* *
* This class also fulfills the role of maintaining the current music loading state, which seems * This class also fulfills the role of maintaining the current music loading state, which seems
* like a job for [MusicStore] but in practice is only really leveraged by the components that * like a job for [MusicStore] but in practice is only really leveraged by the components that
@ -205,8 +209,10 @@ class Indexer {
val mediaStoreLayer = val mediaStoreLayer =
when { when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> Api30MediaStoreLayer(context, cacheLayer) Build.VERSION.SDK_INT >= Build.VERSION_CODES.R ->
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> Api29MediaStoreLayer(context, cacheLayer) Api30MediaStoreLayer(context, cacheLayer)
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ->
Api29MediaStoreLayer(context, cacheLayer)
else -> Api21MediaStoreLayer(context, cacheLayer) else -> Api21MediaStoreLayer(context, cacheLayer)
} }
@ -234,8 +240,8 @@ class Indexer {
} }
/** /**
* Does the initial query over the song database using [metadataLayer]. The songs returned by this * Does the initial query over the song database using [metadataLayer]. The songs returned by
* function are **not** well-formed. The companion [buildAlbums], [buildArtists], and * this function are **not** well-formed. The companion [buildAlbums], [buildArtists], and
* [buildGenres] functions must be called with the returned list so that all songs are properly * [buildGenres] functions must be called with the returned list so that all songs are properly
* linked up. * linked up.
*/ */

View file

@ -20,7 +20,10 @@ package org.oxycblt.auxio.music.system
import android.app.Service import android.app.Service
import android.content.Intent import android.content.Intent
import android.database.ContentObserver import android.database.ContentObserver
import android.os.* import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.os.PowerManager
import android.provider.MediaStore import android.provider.MediaStore
import coil.imageLoader import coil.imageLoader
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -218,7 +221,7 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback {
override fun onSettingChanged(key: String) { override fun onSettingChanged(key: String) {
when (key) { when (key) {
getString(R.string.set_key_music_dirs), getString(R.string.set_key_music_dirs),
getString(R.string.set_key_music_dirs_include)-> onStartIndexing() getString(R.string.set_key_music_dirs_include) -> onStartIndexing()
getString(R.string.set_key_observing) -> { getString(R.string.set_key_observing) -> {
if (!indexer.isIndexing) { if (!indexer.isIndexing) {
updateIdleSession() updateIdleSession()

View file

@ -41,7 +41,7 @@ constructor(
context: Context, context: Context,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
defStyleAttr: Int = 0, defStyleAttr: Int = 0,
defStyleRes: Int = 0, defStyleRes: Int = 0
) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) { ) : FrameLayout(context, attrs, defStyleAttr, defStyleRes) {
override fun onFinishInflate() { override fun onFinishInflate() {
super.onFinishInflate() super.onFinishInflate()

View file

@ -24,6 +24,7 @@ import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentPlaybackBarBinding import org.oxycblt.auxio.databinding.FragmentPlaybackBarBinding
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.msToDs
import org.oxycblt.auxio.playback.state.RepeatMode import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.MainNavigationAction import org.oxycblt.auxio.ui.MainNavigationAction
@ -33,7 +34,6 @@ import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.getAttrColorCompat import org.oxycblt.auxio.util.getAttrColorCompat
import org.oxycblt.auxio.util.getColorCompat import org.oxycblt.auxio.util.getColorCompat
import org.oxycblt.auxio.music.msToDs
/** /**
* A fragment showing the current playback state in a compact manner. Used as the bar for the * A fragment showing the current playback state in a compact manner. Used as the bar for the

View file

@ -26,10 +26,13 @@ import org.oxycblt.auxio.IntegerTable
enum class PlaybackMode { enum class PlaybackMode {
/** Construct the queue from the genre's songs */ /** Construct the queue from the genre's songs */
ALL_SONGS, ALL_SONGS,
/** Construct the queue from the artist's songs */ /** Construct the queue from the artist's songs */
IN_ALBUM, IN_ALBUM,
/** Construct the queue from the album's songs */ /** Construct the queue from the album's songs */
IN_ARTIST, IN_ARTIST,
/** Construct the queue from all songs */ /** Construct the queue from all songs */
IN_GENRE; IN_GENRE;

View file

@ -31,11 +31,11 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentPlaybackPanelBinding import org.oxycblt.auxio.databinding.FragmentPlaybackPanelBinding
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.msToDs
import org.oxycblt.auxio.playback.state.RepeatMode import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.ui.MainNavigationAction import org.oxycblt.auxio.ui.MainNavigationAction
import org.oxycblt.auxio.ui.fragment.MenuFragment import org.oxycblt.auxio.ui.fragment.MenuFragment
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.music.msToDs
import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.showToast
import org.oxycblt.auxio.util.systemBarInsetsCompat import org.oxycblt.auxio.util.systemBarInsetsCompat

View file

@ -30,15 +30,15 @@ import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.dsToMs
import org.oxycblt.auxio.music.msToDs
import org.oxycblt.auxio.playback.state.InternalPlayer import org.oxycblt.auxio.playback.state.InternalPlayer
import org.oxycblt.auxio.playback.state.PlaybackStateDatabase import org.oxycblt.auxio.playback.state.PlaybackStateDatabase
import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.playback.state.RepeatMode import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.application import org.oxycblt.auxio.util.application
import org.oxycblt.auxio.music.dsToMs
import org.oxycblt.auxio.util.logE import org.oxycblt.auxio.util.logE
import org.oxycblt.auxio.music.msToDs
/** /**
* The ViewModel that provides a UI frontend for [PlaybackStateManager]. * The ViewModel that provides a UI frontend for [PlaybackStateManager].
@ -54,21 +54,25 @@ class PlaybackViewModel(application: Application) :
private val playbackManager = PlaybackStateManager.getInstance() private val playbackManager = PlaybackStateManager.getInstance()
private val _song = MutableStateFlow<Song?>(null) private val _song = MutableStateFlow<Song?>(null)
/** The current song. */ /** The current song. */
val song: StateFlow<Song?> val song: StateFlow<Song?>
get() = _song get() = _song
private val _parent = MutableStateFlow<MusicParent?>(null) private val _parent = MutableStateFlow<MusicParent?>(null)
/** The current model that is being played from, such as an [Album] or [Artist] */ /** The current model that is being played from, such as an [Album] or [Artist] */
val parent: StateFlow<MusicParent?> = _parent val parent: StateFlow<MusicParent?> = _parent
private val _isPlaying = MutableStateFlow(false) private val _isPlaying = MutableStateFlow(false)
val isPlaying: StateFlow<Boolean> val isPlaying: StateFlow<Boolean>
get() = _isPlaying get() = _isPlaying
private val _positionDs = MutableStateFlow(0L) private val _positionDs = MutableStateFlow(0L)
/** The current playback position, in *deci-seconds* */ /** The current playback position, in *deci-seconds* */
val positionDs: StateFlow<Long> val positionDs: StateFlow<Long>
get() = _positionDs get() = _positionDs
private val _repeatMode = MutableStateFlow(RepeatMode.NONE) private val _repeatMode = MutableStateFlow(RepeatMode.NONE)
/** The current repeat mode, see [RepeatMode] for more information */ /** The current repeat mode, see [RepeatMode] for more information */
val repeatMode: StateFlow<RepeatMode> val repeatMode: StateFlow<RepeatMode>
get() = _repeatMode get() = _repeatMode

View file

@ -43,11 +43,7 @@ import org.oxycblt.auxio.util.logD
*/ */
class StyledSeekBar class StyledSeekBar
@JvmOverloads @JvmOverloads
constructor( constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) :
ForcedLTRFrameLayout(context, attrs, defStyleAttr), ForcedLTRFrameLayout(context, attrs, defStyleAttr),
Slider.OnSliderTouchListener, Slider.OnSliderTouchListener,
Slider.OnChangeListener { Slider.OnChangeListener {

View file

@ -28,8 +28,13 @@ import com.google.android.material.shape.MaterialShapeDrawable
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.ItemQueueSongBinding import org.oxycblt.auxio.databinding.ItemQueueSongBinding
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.ui.recycler.* import org.oxycblt.auxio.ui.recycler.IndicatorAdapter
import org.oxycblt.auxio.util.* import org.oxycblt.auxio.ui.recycler.SongViewHolder
import org.oxycblt.auxio.ui.recycler.SyncListDiffer
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.getAttrColorCompat
import org.oxycblt.auxio.util.getDimen
import org.oxycblt.auxio.util.inflater
class QueueAdapter(private val listener: QueueItemListener) : class QueueAdapter(private val listener: QueueItemListener) :
RecyclerView.Adapter<QueueSongViewHolder>() { RecyclerView.Adapter<QueueSongViewHolder>() {
@ -104,10 +109,8 @@ interface QueueItemListener {
fun onPickUp(viewHolder: RecyclerView.ViewHolder) fun onPickUp(viewHolder: RecyclerView.ViewHolder)
} }
class QueueSongViewHolder class QueueSongViewHolder private constructor(private val binding: ItemQueueSongBinding) :
private constructor( IndicatorAdapter.ViewHolder(binding.root) {
private val binding: ItemQueueSongBinding,
) : IndicatorAdapter.ViewHolder(binding.root) {
val bodyView: View val bodyView: View
get() = binding.body get() = binding.body
val backgroundView: View val backgroundView: View

View file

@ -25,7 +25,11 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.MaterialShapeDrawable
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.ui.AuxioSheetBehavior import org.oxycblt.auxio.ui.AuxioSheetBehavior
import org.oxycblt.auxio.util.* import org.oxycblt.auxio.util.getAttrColorCompat
import org.oxycblt.auxio.util.getDimen
import org.oxycblt.auxio.util.getDimenSize
import org.oxycblt.auxio.util.replaceSystemBarInsetsCompat
import org.oxycblt.auxio.util.systemBarInsetsCompat
/** /**
* The bottom sheet behavior designed for the queue in particular. * The bottom sheet behavior designed for the queue in particular.

View file

@ -23,8 +23,10 @@ import org.oxycblt.auxio.IntegerTable
enum class ReplayGainMode { enum class ReplayGainMode {
/** Apply the track gain, falling back to the album gain if the track gain is not found. */ /** Apply the track gain, falling back to the album gain if the track gain is not found. */
TRACK, TRACK,
/** Apply the album gain, falling back to the track gain if the album gain is not found. */ /** Apply the album gain, falling back to the track gain if the album gain is not found. */
ALBUM, ALBUM,
/** Apply the album gain only when playing from an album, defaulting to track gain otherwise. */ /** Apply the album gain only when playing from an album, defaulting to track gain otherwise. */
DYNAMIC; DYNAMIC;
@ -46,5 +48,5 @@ data class ReplayGainPreAmp(
/** The value to use when ReplayGain tags are present. */ /** The value to use when ReplayGain tags are present. */
val with: Float, val with: Float,
/** The value to use when ReplayGain tags are not present. */ /** The value to use when ReplayGain tags are not present. */
val without: Float, val without: Float
) )

View file

@ -116,8 +116,7 @@ class PlaybackStateDatabase private constructor(context: Context) :
queue = queue, queue = queue,
positionMs = rawState.positionMs, positionMs = rawState.positionMs,
repeatMode = rawState.repeatMode, repeatMode = rawState.repeatMode,
isShuffled = rawState.isShuffled, isShuffled = rawState.isShuffled)
)
} }
private fun readRawState(): RawState? { private fun readRawState(): RawState? {
@ -258,7 +257,7 @@ class PlaybackStateDatabase private constructor(context: Context) :
val parent: MusicParent?, val parent: MusicParent?,
val positionMs: Long, val positionMs: Long,
val repeatMode: RepeatMode, val repeatMode: RepeatMode,
val isShuffled: Boolean, val isShuffled: Boolean
) )
private data class RawState( private data class RawState(

View file

@ -27,6 +27,7 @@ import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.PlaybackStateManager.Callback
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW import org.oxycblt.auxio.util.logW
@ -58,13 +59,16 @@ class PlaybackStateManager private constructor() {
/** The currently playing song. Null if there isn't one */ /** The currently playing song. Null if there isn't one */
val song val song
get() = queue.getOrNull(index) get() = queue.getOrNull(index)
/** The parent the queue is based on, null if all songs */ /** The parent the queue is based on, null if all songs */
var parent: MusicParent? = null var parent: MusicParent? = null
private set private set
private var _queue = mutableListOf<Song>() private var _queue = mutableListOf<Song>()
/** The current queue determined by [parent] */ /** The current queue determined by [parent] */
val queue val queue
get() = _queue get() = _queue
/** The current position in the queue */ /** The current position in the queue */
var index = -1 var index = -1
private set private set
@ -79,6 +83,7 @@ class PlaybackStateManager private constructor() {
field = value field = value
notifyRepeatModeChanged() notifyRepeatModeChanged()
} }
/** Whether the queue is shuffled */ /** Whether the queue is shuffled */
var isShuffled = false var isShuffled = false
private set private set

View file

@ -53,6 +53,7 @@ class SearchViewModel(application: Application) :
private val settings = Settings(application) private val settings = Settings(application)
private val _searchResults = MutableStateFlow(listOf<Item>()) private val _searchResults = MutableStateFlow(listOf<Item>())
/** Current search results from the last [search] call. */ /** Current search results from the last [search] call. */
val searchResults: StateFlow<List<Item>> val searchResults: StateFlow<List<Item>>
get() = _searchResults get() = _searchResults

View file

@ -36,10 +36,10 @@ import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.ui.fragment.ViewBindingFragment import org.oxycblt.auxio.ui.fragment.ViewBindingFragment
import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.collectImmediately import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.showToast
import org.oxycblt.auxio.util.systemBarInsetsCompat import org.oxycblt.auxio.util.systemBarInsetsCompat

View file

@ -19,7 +19,9 @@ package org.oxycblt.auxio.settings
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Build
import android.os.storage.StorageManager import android.os.storage.StorageManager
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.edit import androidx.core.content.edit
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
@ -34,7 +36,6 @@ import org.oxycblt.auxio.playback.replaygain.ReplayGainPreAmp
import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.accent.Accent import org.oxycblt.auxio.ui.accent.Accent
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.unlikelyToBeNull import org.oxycblt.auxio.util.unlikelyToBeNull
/** /**
@ -215,13 +216,12 @@ class Settings(private val context: Context, private val callback: Callback? = n
} }
} }
/** /** The list of separators the user wants to parse by. */
* The list of separators the user wants to parse by.
*/
var separators: String? var separators: String?
// Differ from convention and store a string of separator characters instead of an int // Differ from convention and store a string of separator characters instead of an int
// code. This makes it easier to use in Regexes and makes it more extendable. // code. This makes it easier to use in Regexes and makes it more extendable.
get() = inner.getString(context.getString(R.string.set_key_separators), null)?.ifEmpty { null } get() =
inner.getString(context.getString(R.string.set_key_separators), null)?.ifEmpty { null }
set(value) { set(value) {
inner.edit { inner.edit {
putString(context.getString(R.string.set_key_separators), value) putString(context.getString(R.string.set_key_separators), value)
@ -344,3 +344,34 @@ class Settings(private val context: Context, private val callback: Callback? = n
} }
} }
} }
// --- COMPAT ---
fun handleAccentCompat(context: Context, prefs: SharedPreferences): Accent {
val currentKey = context.getString(R.string.set_key_accent)
if (prefs.contains(OldKeys.KEY_ACCENT3)) {
Log.d("Auxio.SettingsCompat", "Migrating ${OldKeys.KEY_ACCENT3}")
var accent = prefs.getInt(OldKeys.KEY_ACCENT3, 5)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// Accents were previously frozen as soon as the OS was updated to android twelve,
// as dynamic colors were enabled by default. This is no longer the case, so we need
// to re-update the setting to dynamic colors here.
accent = 16
}
prefs.edit {
putInt(currentKey, accent)
remove(OldKeys.KEY_ACCENT3)
apply()
}
}
return Accent.from(prefs.getInt(currentKey, Accent.DEFAULT))
}
/** Cache of the old keys used in Auxio. */
private object OldKeys {
const val KEY_ACCENT3 = "auxio_accent"
}

View file

@ -1,58 +0,0 @@
/*
* Copyright (c) 2021 Auxio Project
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.settings
import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import android.util.Log
import androidx.core.content.edit
import org.oxycblt.auxio.R
import org.oxycblt.auxio.ui.accent.Accent
// A couple of utils for migrating from old settings values to the new formats.
// Usually, these will last for 6 months before being removed.
fun handleAccentCompat(context: Context, prefs: SharedPreferences): Accent {
val currentKey = context.getString(R.string.set_key_accent)
if (prefs.contains(OldKeys.KEY_ACCENT3)) {
Log.d("Auxio.SettingsCompat", "Migrating ${OldKeys.KEY_ACCENT3}")
var accent = prefs.getInt(OldKeys.KEY_ACCENT3, 5)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// Accents were previously frozen as soon as the OS was updated to android twelve,
// as dynamic colors were enabled by default. This is no longer the case, so we need
// to re-update the setting to dynamic colors here.
accent = 16
}
prefs.edit {
putInt(currentKey, accent)
remove(OldKeys.KEY_ACCENT3)
apply()
}
}
return Accent.from(prefs.getInt(currentKey, Accent.DEFAULT))
}
/** Cache of the old keys used in Auxio. */
private object OldKeys {
const val KEY_ACCENT3 = "auxio_accent"
}

View file

@ -26,7 +26,8 @@ import android.view.WindowInsets
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.bottomsheet.NeoBottomSheetBehavior import com.google.android.material.bottomsheet.NeoBottomSheetBehavior
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.util.* import org.oxycblt.auxio.util.getDimen
import org.oxycblt.auxio.util.systemGestureInsetsCompat
/** /**
* Implements a reasonable enough skeleton around BottomSheetBehavior (Excluding auxio extensions in * Implements a reasonable enough skeleton around BottomSheetBehavior (Excluding auxio extensions in

View file

@ -30,11 +30,13 @@ import org.oxycblt.auxio.util.logD
*/ */
class NavigationViewModel : ViewModel() { class NavigationViewModel : ViewModel() {
private val _mainNavigationAction = MutableStateFlow<MainNavigationAction?>(null) private val _mainNavigationAction = MutableStateFlow<MainNavigationAction?>(null)
/** Flag for main fragment navigation. Intended for MainFragment use only. */ /** Flag for main fragment navigation. Intended for MainFragment use only. */
val mainNavigationAction: StateFlow<MainNavigationAction?> val mainNavigationAction: StateFlow<MainNavigationAction?>
get() = _mainNavigationAction get() = _mainNavigationAction
private val _exploreNavigationItem = MutableStateFlow<Music?>(null) private val _exploreNavigationItem = MutableStateFlow<Music?>(null)
/** /**
* Flag for navigation within the explore fragments. Observe this to coordinate navigation to an * Flag for navigation within the explore fragments. Observe this to coordinate navigation to an
* item's UI. * item's UI.
@ -85,12 +87,16 @@ class NavigationViewModel : ViewModel() {
sealed class MainNavigationAction { sealed class MainNavigationAction {
/** Expand the playback panel. */ /** Expand the playback panel. */
object Expand : MainNavigationAction() object Expand : MainNavigationAction()
/** Collapse the playback panel. */ /** Collapse the playback panel. */
object Collapse : MainNavigationAction() object Collapse : MainNavigationAction()
/** Go to settings. */ /** Go to settings. */
object Settings : MainNavigationAction() object Settings : MainNavigationAction()
/** Go to the about page. */ /** Go to the about page. */
object About : MainNavigationAction() object About : MainNavigationAction()
/** Show song details. */ /** Show song details. */
data class SongDetails(val song: Song) : MainNavigationAction() data class SongDetails(val song: Song) : MainNavigationAction()
} }

View file

@ -18,7 +18,6 @@
package org.oxycblt.auxio.ui package org.oxycblt.auxio.ui
import androidx.annotation.IdRes import androidx.annotation.IdRes
import kotlin.UnsupportedOperationException
import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
@ -27,6 +26,7 @@ import org.oxycblt.auxio.music.Date
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.ui.Sort.Mode
/** /**
* Represents the sort modes used in Auxio. * Represents the sort modes used in Auxio.

View file

@ -349,7 +349,6 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
if (!dragging && if (!dragging &&
thumbView.isUnder(downX, thumbView.top.toFloat(), minTouchTargetSize) && thumbView.isUnder(downX, thumbView.top.toFloat(), minTouchTargetSize) &&
abs(eventY - downY) > touchSlop) { abs(eventY - downY) > touchSlop) {
if (thumbView.isUnder(downX, downY, minTouchTargetSize)) { if (thumbView.isUnder(downX, downY, minTouchTargetSize)) {
dragStartY = lastY dragStartY = lastY
dragStartThumbOffset = thumbOffset dragStartThumbOffset = thumbOffset

View file

@ -73,10 +73,8 @@ class SongViewHolder private constructor(private val binding: ItemSongBinding) :
* The Shared ViewHolder for a [Album]. * The Shared ViewHolder for a [Album].
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class AlbumViewHolder class AlbumViewHolder private constructor(private val binding: ItemParentBinding) :
private constructor( IndicatorAdapter.ViewHolder(binding.root) {
private val binding: ItemParentBinding,
) : IndicatorAdapter.ViewHolder(binding.root) {
fun bind(item: Album, listener: MenuItemListener) { fun bind(item: Album, listener: MenuItemListener) {
binding.parentImage.bind(item) binding.parentImage.bind(item)
@ -157,10 +155,8 @@ class ArtistViewHolder private constructor(private val binding: ItemParentBindin
* The Shared ViewHolder for a [Genre]. * The Shared ViewHolder for a [Genre].
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class GenreViewHolder class GenreViewHolder private constructor(private val binding: ItemParentBinding) :
private constructor( IndicatorAdapter.ViewHolder(binding.root) {
private val binding: ItemParentBinding,
) : IndicatorAdapter.ViewHolder(binding.root) {
fun bind(item: Genre, listener: MenuItemListener) { fun bind(item: Genre, listener: MenuItemListener) {
binding.parentImage.bind(item) binding.parentImage.bind(item)

View file

@ -45,10 +45,9 @@ fun <T> unlikelyToBeNull(value: T?) =
/** Returns null if this value is 0. */ /** Returns null if this value is 0. */
fun Int.nonZeroOrNull() = if (this > 0) this else null fun Int.nonZeroOrNull() = if (this > 0) this else null
/** Returns null if this value is not in [range]. */ /** Returns null if this value is not in [range]. */
fun Int.inRangeOrNull(range: IntRange) = if (range.contains(this)) this else null fun Int.inRangeOrNull(range: IntRange) = if (range.contains(this)) this else null
/** Lazily reflect to retrieve a [Field]. */ /** Lazily reflect to retrieve a [Field]. */
fun lazyReflectedField(clazz: KClass<*>, field: String) = lazy { fun lazyReflectedField(clazz: KClass<*>, field: String) = lazy {
clazz.java.getDeclaredField(field).also { it.isAccessible = true } clazz.java.getDeclaredField(field).also { it.isAccessible = true }

View file

@ -44,6 +44,7 @@ fun createThinWidget(context: Context, state: WidgetComponent.WidgetState) =
.applyRoundingToBackground(context) .applyRoundingToBackground(context)
.applyMeta(context, state) .applyMeta(context, state)
.applyBasicControls(context, state) .applyBasicControls(context, state)
/** /**
* The small widget is for 2x2 widgets and just shows the cover art and playback controls. This is * The small widget is for 2x2 widgets and just shows the cover art and playback controls. This is
* generally because a Medium widget is too large for this widget size and a text-only widget is too * generally because a Medium widget is too large for this widget size and a text-only widget is too

View file

@ -160,6 +160,6 @@ class WidgetComponent(private val context: Context) :
val cover: Bitmap?, val cover: Bitmap?,
val isPlaying: Boolean, val isPlaying: Boolean,
val repeatMode: RepeatMode, val repeatMode: RepeatMode,
val isShuffled: Boolean, val isShuffled: Boolean
) )
} }

View file

@ -12,7 +12,7 @@ buildscript {
classpath 'com.android.tools.build:gradle:7.4.0-alpha10' classpath 'com.android.tools.build:gradle:7.4.0-alpha10'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version"
classpath "com.diffplug.spotless:spotless-plugin-gradle:6.6.1" classpath "com.diffplug.spotless:spotless-plugin-gradle:6.10.0"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files

Binary file not shown.

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

6
gradlew vendored
View file

@ -205,6 +205,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \ org.gradle.wrapper.GradleWrapperMain \
"$@" "$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args. # Use "xargs" to parse quoted args.
# #
# With -n1 it outputs one arg per line, with the quotes and backslashes removed. # With -n1 it outputs one arg per line, with the quotes and backslashes removed.

14
gradlew.bat vendored
View file

@ -14,7 +14,7 @@
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@if "%DEBUG%" == "" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@ -25,7 +25,7 @@
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd if %ERRORLEVEL% equ 0 goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 set EXIT_CODE=%ERRORLEVEL%
exit /b 1 if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal