all: lint with ktlint

Just lint with ktlint. It has better import checking functionality.
This commit is contained in:
Alexander Capehart 2022-09-08 21:54:38 -06:00
parent 09823d7829
commit 06d6495dcd
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
77 changed files with 537 additions and 274 deletions

View file

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

View file

@ -47,8 +47,11 @@ class AuxioApp : Application(), ImageLoaderFactory {
.setIcon(IconCompat.createWithResource(this, R.drawable.ic_shortcut_shuffle_24))
.setIntent(
Intent(this, MainActivity::class.java)
.setAction(INTENT_KEY_SHORTCUT_SHUFFLE))
.build()))
.setAction(INTENT_KEY_SHORTCUT_SHUFFLE)
)
.build()
)
)
}
override fun newImageLoader() =

View file

@ -98,9 +98,7 @@ class MainActivity : AppCompatActivity() {
val action =
when (intent.action) {
Intent.ACTION_VIEW -> InternalPlayer.Action.Open(intent.data ?: return false)
AuxioApp.INTENT_KEY_SHORTCUT_SHUFFLE -> {
InternalPlayer.Action.ShuffleAll
}
AuxioApp.INTENT_KEY_SHORTCUT_SHUFFLE -> InternalPlayer.Action.ShuffleAll
else -> return false
}

View file

@ -31,8 +31,6 @@ import androidx.navigation.fragment.findNavController
import com.google.android.material.bottomsheet.NeoBottomSheetBehavior
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.transition.MaterialFadeThrough
import kotlin.math.max
import kotlin.math.min
import org.oxycblt.auxio.databinding.FragmentMainBinding
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.Song
@ -51,6 +49,8 @@ import org.oxycblt.auxio.util.getAttrColorCompat
import org.oxycblt.auxio.util.getDimen
import org.oxycblt.auxio.util.systemBarInsetsCompat
import org.oxycblt.auxio.util.unlikelyToBeNull
import kotlin.math.max
import kotlin.math.min
/**
* A wrapper around the home fragment that shows the playback fragment and controls the more
@ -89,9 +89,13 @@ class MainFragment :
// Send meaningful accessibility events for bottom sheets
ViewCompat.setAccessibilityPaneTitle(
binding.playbackSheet, context.getString(R.string.lbl_playback))
binding.playbackSheet,
context.getString(R.string.lbl_playback)
)
ViewCompat.setAccessibilityPaneTitle(
binding.queueSheet, context.getString(R.string.lbl_queue))
binding.queueSheet,
context.getString(R.string.lbl_queue)
)
val queueSheetBehavior = binding.queueSheet.coordinatorLayoutBehavior as QueueSheetBehavior?
if (queueSheetBehavior != null) {
@ -100,7 +104,8 @@ class MainFragment :
unlikelyToBeNull(binding.handleWrapper).setOnClickListener {
if (playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_EXPANDED &&
queueSheetBehavior.state == NeoBottomSheetBehavior.STATE_COLLAPSED) {
queueSheetBehavior.state == NeoBottomSheetBehavior.STATE_COLLAPSED
) {
queueSheetBehavior.state = NeoBottomSheetBehavior.STATE_EXPANDED
}
}
@ -328,14 +333,16 @@ class MainFragment :
if (queueSheetBehavior != null &&
queueSheetBehavior.state != NeoBottomSheetBehavior.STATE_COLLAPSED &&
playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_EXPANDED) {
playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_EXPANDED
) {
// Collapse the queue first if it is expanded.
queueSheetBehavior.state = NeoBottomSheetBehavior.STATE_COLLAPSED
return
}
if (playbackSheetBehavior.state != NeoBottomSheetBehavior.STATE_COLLAPSED &&
playbackSheetBehavior.state != NeoBottomSheetBehavior.STATE_HIDDEN) {
playbackSheetBehavior.state != NeoBottomSheetBehavior.STATE_HIDDEN
) {
// Then collapse the playback sheet.
playbackSheetBehavior.state = NeoBottomSheetBehavior.STATE_COLLAPSED
return
@ -355,9 +362,9 @@ class MainFragment :
isEnabled =
playbackSheetBehavior.state == NeoBottomSheetBehavior.STATE_EXPANDED ||
queueSheetBehavior?.state == NeoBottomSheetBehavior.STATE_EXPANDED ||
exploreNavController.currentDestination?.id !=
exploreNavController.graph.startDestinationId
queueSheetBehavior?.state == NeoBottomSheetBehavior.STATE_EXPANDED ||
exploreNavController.currentDestination?.id !=
exploreNavController.graph.startDestinationId
}
}
}

View file

@ -89,7 +89,11 @@ class AlbumDetailFragment :
collectImmediately(detailModel.currentAlbum, ::handleItemChange)
collectImmediately(detailModel.albumData, detailAdapter::submitList)
collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
playbackModel.song,
playbackModel.parent,
playbackModel.isPlaying,
::updatePlayback
)
collect(navModel.exploreNavigationItem, ::handleNavigation)
}
@ -164,7 +168,9 @@ class AlbumDetailFragment :
findNavController()
.navigate(
AlbumDetailFragmentDirections.actionShowArtist(
unlikelyToBeNull(detailModel.currentAlbum.value).artist.uid))
unlikelyToBeNull(detailModel.currentAlbum.value).artist.uid
)
)
}
private fun handleItemChange(album: Album?) {
@ -228,7 +234,8 @@ class AlbumDetailFragment :
binding.detailRecycler.post {
// Make sure to increment the position to make up for the detail header
binding.detailRecycler.layoutManager?.startSmoothScroll(
CenterSmoothScroller(requireContext(), pos))
CenterSmoothScroller(requireContext(), pos)
)
// If the recyclerview can scroll, its certain that it will have to scroll to
// correctly center the playing item, so make sure that the Toolbar is lifted in

View file

@ -84,7 +84,11 @@ class ArtistDetailFragment :
collectImmediately(detailModel.currentArtist, ::handleItemChange)
collectImmediately(detailModel.artistData, detailAdapter::submitList)
collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
playbackModel.song,
playbackModel.parent,
playbackModel.isPlaying,
::updatePlayback
)
collect(navModel.exploreNavigationItem, ::handleNavigation)
}

View file

@ -29,10 +29,10 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.appbar.AppBarLayout
import java.lang.reflect.Field
import org.oxycblt.auxio.R
import org.oxycblt.auxio.ui.AuxioAppBarLayout
import org.oxycblt.auxio.util.lazyReflectedField
import java.lang.reflect.Field
/**
* An [AuxioAppBarLayout] variant that also shows the name of the toolbar whenever the detail
@ -145,6 +145,6 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
private const val TOOLBAR_FADE_DURATION = 150L
private val TOOLBAR_TITLE_TEXT_FIELD: Field by
lazyReflectedField(Toolbar::class, "mTitleTextView")
lazyReflectedField(Toolbar::class, "mTitleTextView")
}
}

View file

@ -85,7 +85,11 @@ class GenreDetailFragment :
collectImmediately(detailModel.currentGenre, ::handleItemChange)
collectImmediately(detailModel.genreData, detailAdapter::submitList)
collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::updatePlayback)
playbackModel.song,
playbackModel.parent,
playbackModel.isPlaying,
::updatePlayback
)
collect(navModel.exploreNavigationItem, ::handleNavigation)
}

View file

@ -74,14 +74,16 @@ class SongDetailDialog : ViewBindingDialogFragment<DialogSongDetailBinding>() {
if (song.info.bitrateKbps != null) {
binding.detailBitrate.setText(
getString(R.string.fmt_bitrate, song.info.bitrateKbps))
getString(R.string.fmt_bitrate, song.info.bitrateKbps)
)
} else {
binding.detailBitrate.setText(R.string.def_bitrate)
}
if (song.info.sampleRate != null) {
binding.detailSampleRate.setText(
getString(R.string.fmt_sample_rate, song.info.sampleRate))
getString(R.string.fmt_sample_rate, song.info.sampleRate)
)
} else {
binding.detailSampleRate.setText(R.string.def_sample_rate)
}

View file

@ -127,7 +127,8 @@ private class ArtistDetailViewHolder private constructor(private val binding: It
binding.context.getString(
R.string.fmt_two,
binding.context.getPlural(R.plurals.fmt_album_count, item.albums.size),
binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size))
binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size)
)
binding.detailPlayButton.setOnClickListener { listener.onPlayParent() }
binding.detailShuffleButton.setOnClickListener { listener.onShuffleParent() }

View file

@ -42,7 +42,8 @@ abstract class DetailAdapter<L : DetailAdapter.Listener>(
) : IndicatorAdapter<RecyclerView.ViewHolder>(), AuxioRecyclerView.SpanSizeLookup {
private var isPlaying = false
@Suppress("LeakingThis") override fun getItemCount() = differ.currentList.size
@Suppress("LeakingThis")
override fun getItemCount() = differ.currentList.size
override fun getItemViewType(position: Int) =
when (differ.currentList[position]) {
@ -83,7 +84,8 @@ abstract class DetailAdapter<L : DetailAdapter.Listener>(
return item is Header || item is SortHeader
}
@Suppress("LeakingThis") protected val differ = AsyncListDiffer(this, diffCallback)
@Suppress("LeakingThis")
protected val differ = AsyncListDiffer(this, diffCallback)
override val currentList: List<Item>
get() = differ.currentList

View file

@ -36,8 +36,6 @@ import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.tabs.TabLayoutMediator
import com.google.android.material.transition.MaterialSharedAxis
import java.lang.reflect.Field
import kotlin.math.abs
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeBinding
@ -64,6 +62,8 @@ import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.getColorCompat
import org.oxycblt.auxio.util.lazyReflectedField
import org.oxycblt.auxio.util.logD
import java.lang.reflect.Field
import kotlin.math.abs
/**
* The main "Launching Point" fragment of Auxio, allowing navigation to the detail views for each
@ -111,7 +111,8 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
binding.homeToolbar.alpha = 1f - (abs(offset.toFloat()) / (range.toFloat() / 2))
binding.homeContent.updatePadding(
bottom = binding.homeAppbar.totalScrollRange + offset)
bottom = binding.homeAppbar.totalScrollRange + offset
)
}
}
@ -138,7 +139,8 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) =
homeModel.updateCurrentTab(position)
})
}
)
TabLayoutMediator(binding.homeTabs, this, AdaptiveTabStrategy(context, homeModel))
.attach()
@ -197,7 +199,8 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
homeModel.updateCurrentSort(
homeModel
.getSortForDisplay(homeModel.currentTab.value)
.withAscending(item.isChecked))
.withAscending(item.isChecked)
)
}
else -> {
// Sorting option was selected, mark it as selected and update the mode
@ -205,7 +208,8 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
homeModel.updateCurrentSort(
homeModel
.getSortForDisplay(homeModel.currentTab.value)
.withMode(requireNotNull(Sort.Mode.fromItemId(item.itemId))))
.withMode(requireNotNull(Sort.Mode.fromItemId(item.itemId)))
)
}
}
@ -284,7 +288,7 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
binding.homeTabs.isVisible = true
toolbarParams.scrollFlags =
AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL or
AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS
AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS
}
}
@ -440,9 +444,9 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
companion object {
private val VIEW_PAGER_RECYCLER_FIELD: Field by
lazyReflectedField(ViewPager2::class, "mRecyclerView")
lazyReflectedField(ViewPager2::class, "mRecyclerView")
private val VIEW_PAGER_TOUCH_SLOP_FIELD: Field by
lazyReflectedField(RecyclerView::class, "mTouchSlop")
lazyReflectedField(RecyclerView::class, "mTouchSlop")
private const val KEY_LAST_TRANSITION_AXIS =
BuildConfig.APPLICATION_ID + ".key.LAST_TRANSITION_AXIS"
}

View file

@ -21,7 +21,6 @@ import android.os.Bundle
import android.text.format.DateUtils
import android.view.View
import android.view.ViewGroup
import java.util.Formatter
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.Album
@ -36,6 +35,7 @@ import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.ui.recycler.MenuItemListener
import org.oxycblt.auxio.ui.recycler.SyncListDiffer
import org.oxycblt.auxio.util.collectImmediately
import java.util.Formatter
/**
* A [HomeListFragment] for showing a list of [Album]s.
@ -83,11 +83,12 @@ class AlbumListFragment : HomeListFragment<Album>() {
val dateAddedMillis = album.dateAdded.secsToMs()
formatterSb.setLength(0)
DateUtils.formatDateRange(
context,
formatter,
dateAddedMillis,
dateAddedMillis,
DateUtils.FORMAT_ABBREV_ALL)
context,
formatter,
dateAddedMillis,
dateAddedMillis,
DateUtils.FORMAT_ABBREV_ALL
)
.toString()
}

View file

@ -21,7 +21,6 @@ import android.os.Bundle
import android.text.format.DateUtils
import android.view.View
import android.view.ViewGroup
import java.util.Formatter
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.MusicParent
@ -38,6 +37,7 @@ import org.oxycblt.auxio.ui.recycler.SongViewHolder
import org.oxycblt.auxio.ui.recycler.SyncListDiffer
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.util.context
import java.util.Formatter
/**
* A [HomeListFragment] for showing a list of [Song]s.
@ -59,7 +59,11 @@ class SongListFragment : HomeListFragment<Song>() {
collectImmediately(homeModel.songs, homeAdapter::replaceList)
collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::handlePlayback)
playbackModel.song,
playbackModel.parent,
playbackModel.isPlaying,
::handlePlayback
)
}
override fun getPopup(pos: Int): String? {
@ -89,11 +93,12 @@ class SongListFragment : HomeListFragment<Song>() {
val dateAddedMillis = song.dateAdded.secsToMs()
formatterSb.setLength(0)
DateUtils.formatDateRange(
context,
formatter,
dateAddedMillis,
dateAddedMillis,
DateUtils.FORMAT_ABBREV_ALL)
context,
formatter,
dateAddedMillis,
dateAddedMillis,
DateUtils.FORMAT_ABBREV_ALL
)
.toString()
}

View file

@ -66,7 +66,8 @@ sealed class Tab(open val mode: DisplayMode) {
DisplayMode.SHOW_SONGS,
DisplayMode.SHOW_ALBUMS,
DisplayMode.SHOW_ARTISTS,
DisplayMode.SHOW_GENRES)
DisplayMode.SHOW_GENRES
)
/** Convert an array [tabs] into a sequence of tabs. */
fun toSequence(tabs: Array<Tab>): Int {

View file

@ -91,7 +91,8 @@ class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAd
when (tab) {
is Tab.Visible -> Tab.Invisible(tab.mode)
is Tab.Invisible -> Tab.Visible(tab.mode)
})
}
)
}
(requireDialog() as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE).isEnabled =

View file

@ -22,7 +22,6 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.media.MediaMetadataRetriever
import android.util.Size as AndroidSize
import androidx.core.graphics.drawable.toDrawable
import coil.decode.DataSource
import coil.decode.ImageSource
@ -38,8 +37,6 @@ import com.google.android.exoplayer2.MediaMetadata
import com.google.android.exoplayer2.MetadataRetriever
import com.google.android.exoplayer2.metadata.flac.PictureFrame
import com.google.android.exoplayer2.metadata.id3.ApicFrame
import java.io.ByteArrayInputStream
import java.io.InputStream
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okio.buffer
@ -48,6 +45,9 @@ import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW
import java.io.ByteArrayInputStream
import java.io.InputStream
import android.util.Size as AndroidSize
/**
* The base implementation for all image fetchers in Auxio.
@ -194,7 +194,8 @@ abstract class BaseFetcher : Fetcher {
return SourceResult(
source = ImageSource(stream.source().buffer(), context),
mimeType = null,
dataSource = DataSource.DISK)
dataSource = DataSource.DISK
)
}
}
@ -223,7 +224,9 @@ abstract class BaseFetcher : Fetcher {
// resolution.
val bitmap =
SquareFrameTransform.INSTANCE.transform(
BitmapFactory.decodeStream(stream), mosaicFrameSize)
BitmapFactory.decodeStream(stream),
mosaicFrameSize
)
canvas.drawBitmap(bitmap, x.toFloat(), y.toFloat(), null)
@ -241,7 +244,8 @@ abstract class BaseFetcher : Fetcher {
return DrawableResult(
drawable = mosaicBitmap.toDrawable(context.resources),
isSampled = true,
dataSource = DataSource.DISK)
dataSource = DataSource.DISK
)
}
private fun Dimension.mosaicSize(): Int {

View file

@ -70,8 +70,10 @@ class BitmapProvider(private val context: Context) {
if (guard.check(handle)) {
target.onCompleted(null)
}
})
.transformations(SquareFrameTransform.INSTANCE))
}
)
.transformations(SquareFrameTransform.INSTANCE)
)
currentRequest = Request(context.imageLoader.enqueue(request.build()), target)
}

View file

@ -27,7 +27,6 @@ import coil.fetch.SourceResult
import coil.key.Keyer
import coil.request.Options
import coil.size.Size
import kotlin.math.min
import okio.buffer
import okio.source
import org.oxycblt.auxio.music.Album
@ -36,6 +35,7 @@ import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.ui.Sort
import kotlin.math.min
/** A basic keyer for music data. */
class MusicKeyer : Keyer<Music> {
@ -60,7 +60,8 @@ private constructor(private val context: Context, private val album: Album) : Ba
SourceResult(
source = ImageSource(stream.source().buffer(), context),
mimeType = null,
dataSource = DataSource.DISK)
dataSource = DataSource.DISK
)
}
class SongFactory : Fetcher.Factory<Song> {

View file

@ -26,11 +26,11 @@ import androidx.annotation.AttrRes
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.widget.ImageViewCompat
import com.google.android.material.shape.MaterialShapeDrawable
import kotlin.math.max
import org.oxycblt.auxio.R
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.getColorCompat
import org.oxycblt.auxio.util.getDrawableCompat
import kotlin.math.max
/**
* View that displays the playback indicator. Nominally emulates [StyledImageView], but is much
@ -101,15 +101,21 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
0f,
0f,
drawable.intrinsicWidth.toFloat(),
drawable.intrinsicHeight.toFloat())
drawable.intrinsicHeight.toFloat()
)
indicatorMatrixDst.set(0f, 0f, iconSize.toFloat(), iconSize.toFloat())
indicatorMatrix.setRectToRect(
indicatorMatrixSrc, indicatorMatrixDst, Matrix.ScaleToFit.CENTER)
indicatorMatrixSrc,
indicatorMatrixDst,
Matrix.ScaleToFit.CENTER
)
// Then actually center it into the icon, which the previous call does not
// actually do.
indicatorMatrix.postTranslate(
(measuredWidth - iconSize) / 2f, (measuredHeight - iconSize) / 2f)
(measuredWidth - iconSize) / 2f,
(measuredHeight - iconSize) / 2f
)
}
}
}

View file

@ -94,7 +94,9 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.StyledImageView)
val staticIcon =
styledAttrs.getResourceId(
R.styleable.StyledImageView_staticIcon, ResourcesCompat.ID_NULL)
R.styleable.StyledImageView_staticIcon,
ResourcesCompat.ID_NULL
)
if (staticIcon != ResourcesCompat.ID_NULL) {
this.staticIcon = context.getDrawableCompat(staticIcon)
}
@ -143,7 +145,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
adjustWidth,
adjustHeight,
bounds.width() - adjustWidth,
bounds.height() - adjustHeight)
bounds.height() - adjustHeight
)
src.draw(canvas)
}

View file

@ -21,11 +21,6 @@ package org.oxycblt.auxio.music
import android.content.Context
import android.os.Parcelable
import java.security.MessageDigest
import java.util.UUID
import kotlin.math.max
import kotlin.math.min
import kotlin.reflect.KClass
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
import org.oxycblt.auxio.BuildConfig
@ -36,6 +31,11 @@ import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.util.inRangeOrNull
import org.oxycblt.auxio.util.nonZeroOrNull
import org.oxycblt.auxio.util.unlikelyToBeNull
import java.security.MessageDigest
import java.util.UUID
import kotlin.math.max
import kotlin.math.min
import kotlin.reflect.KClass
// --- MUSIC MODELS ---
@ -164,13 +164,15 @@ class Song constructor(raw: Raw) : Music() {
val path =
Path(
name = requireNotNull(raw.displayName) { "Invalid raw: No display name" },
parent = requireNotNull(raw.directory) { "Invalid raw: No parent directory" })
parent = requireNotNull(raw.directory) { "Invalid raw: No parent directory" }
)
/** The mime type of the audio file. Only intended for display. */
val mimeType =
MimeType(
fromExtension = requireNotNull(raw.extensionMimeType) { "Invalid raw: No mime type" },
fromFormat = raw.formatMimeType)
fromFormat = raw.formatMimeType
)
/** The size of this audio file. */
val size = requireNotNull(raw.size) { "Invalid raw: No size" }
@ -234,11 +236,12 @@ class Song constructor(raw: Raw) : Music() {
date = raw.date,
releaseType = raw.albumReleaseType,
rawArtist =
if (albumArtistName != null) {
Artist.Raw(albumArtistName, albumArtistSortName)
} else {
Artist.Raw(artistName, artistSortName)
})
if (albumArtistName != null) {
Artist.Raw(albumArtistName, albumArtistSortName)
} else {
Artist.Raw(artistName, artistSortName)
}
)
val _rawGenres = raw.genreNames?.map { Genre.Raw(it) } ?: listOf(Genre.Raw(null))
@ -505,7 +508,9 @@ fun MessageDigest.update(n: Long?) {
n.shr(32).toByte(),
n.shr(40).toByte(),
n.shl(48).toByte(),
n.shr(56).toByte()))
n.shr(56).toByte()
)
)
}
/**
@ -535,7 +540,8 @@ fun ByteArray.toUuid(): UUID {
.or(get(12).toLong().and(0xFF).shl(24))
.or(get(13).toLong().and(0xFF).shl(16))
.or(get(14).toLong().and(0xFF).shl(8))
.or(get(15).toLong().and(0xFF)))
.or(get(15).toLong().and(0xFF))
)
}
/**
@ -628,7 +634,8 @@ class Date private constructor(private val tokens: List<Int>) : Comparable<Date>
companion object {
private val ISO8601_REGEX =
Regex(
"""^(\d{4,})([-.](\d{2})([-.](\d{2})([T ](\d{2})([:.](\d{2})([:.](\d{2}))?)?)?)?)?$""")
"""^(\d{4,})([-.](\d{2})([-.](\d{2})([T ](\d{2})([:.](\d{2})([:.](\d{2}))?)?)?)?)?$"""
)
fun from(year: Int) = fromTokens(listOf(year))

View file

@ -97,7 +97,8 @@ class MusicStore private constructor() {
}
}
@Suppress("UNCHECKED_CAST") fun <T : Music> find(uid: Music.UID): T? = uidMap[uid] as? T
@Suppress("UNCHECKED_CAST")
fun <T : Music> find(uid: Music.UID): T? = uidMap[uid] as? T
/** Sanitize an old item to find the corresponding item in a new library. */
fun sanitize(song: Song) = find<Song>(song.uid)
@ -117,7 +118,7 @@ class MusicStore private constructor() {
/** Find a song for a [uri]. */
fun findSongForUri(context: Context, uri: Uri) =
context.contentResolverSafe.useQuery(uri, arrayOf(OpenableColumns.DISPLAY_NAME)) {
cursor ->
cursor ->
cursor.moveToFirst()
val displayName =

View file

@ -24,9 +24,9 @@ import android.database.Cursor
import android.net.Uri
import android.provider.MediaStore
import android.text.format.DateUtils
import java.util.UUID
import org.oxycblt.auxio.R
import org.oxycblt.auxio.util.logD
import java.util.UUID
/** Shortcut for making a [ContentResolver] query with less superfluous arguments. */
fun ContentResolver.queryCursor(

View file

@ -26,10 +26,10 @@ import android.os.storage.StorageVolume
import android.provider.MediaStore
import android.webkit.MimeTypeMap
import com.google.android.exoplayer2.util.MimeTypes
import java.io.File
import java.lang.reflect.Method
import org.oxycblt.auxio.R
import org.oxycblt.auxio.util.lazyReflectedMethod
import java.io.File
import java.lang.reflect.Method
/** 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)
@ -66,7 +66,9 @@ class Directory private constructor(val volume: StorageVolume, val relativePath:
fun from(volume: StorageVolume, relativePath: String) =
Directory(
volume, relativePath.removePrefix(File.separator).removeSuffix(File.separator))
volume,
relativePath.removePrefix(File.separator).removeSuffix(File.separator)
)
/**
* Converts an opaque document uri in the form of VOLUME:PATH into a [Directory]. This is a
@ -91,14 +93,15 @@ class Directory private constructor(val volume: StorageVolume, val relativePath:
@Suppress("NewApi")
private val SM_API21_GET_VOLUME_LIST_METHOD: Method by
lazyReflectedMethod(StorageManager::class, "getVolumeList")
lazyReflectedMethod(StorageManager::class, "getVolumeList")
@Suppress("NewApi")
private val SV_API21_GET_PATH_METHOD: Method by lazyReflectedMethod(StorageVolume::class, "getPath")
/** The "primary" storage volume containing the OS. May be an SD Card. */
val StorageManager.primaryStorageVolumeCompat: StorageVolume
@Suppress("NewApi") get() = primaryStorageVolume
@Suppress("NewApi")
get() = primaryStorageVolume
/**
* A list of recognized volumes, retrieved in a compatible manner. Note that these volumes may be
@ -135,11 +138,13 @@ fun StorageVolume.getDescriptionCompat(context: Context): String = getDescriptio
/** If this volume is the primary volume. May still be removable storage. */
val StorageVolume.isPrimaryCompat: Boolean
@SuppressLint("NewApi") get() = isPrimary
@SuppressLint("NewApi")
get() = isPrimary
/** If this volume is emulated. */
val StorageVolume.isEmulatedCompat: Boolean
@SuppressLint("NewApi") get() = isEmulated
@SuppressLint("NewApi")
get() = isEmulated
/**
* If this volume corresponds to "Internal shared storage", represented in document URIs as
@ -150,11 +155,13 @@ val StorageVolume.isInternalCompat: Boolean
/** Returns the UUID of the volume in a compatible manner. */
val StorageVolume.uuidCompat: String?
@SuppressLint("NewApi") get() = uuid
@SuppressLint("NewApi")
get() = uuid
/** Returns the state of the volume in a compatible manner. */
val StorageVolume.stateCompat: String
@SuppressLint("NewApi") get() = state
@SuppressLint("NewApi")
get() = state
/**
* Returns the name of this volume as it is used in [MediaStore]. This will be

View file

@ -99,7 +99,8 @@ class MusicDirsDialog :
dirs =
MusicDirs(
pendingDirs.mapNotNull { Directory.fromDocumentUri(storageManager, it) },
savedInstanceState.getBoolean(KEY_PENDING_MODE))
savedInstanceState.getBoolean(KEY_PENDING_MODE)
)
}
}
@ -112,7 +113,8 @@ class MusicDirsDialog :
R.id.dirs_mode_include
} else {
R.id.dirs_mode_exclude
})
}
)
updateMode()
addOnButtonCheckedListener { _, _, _ -> updateMode() }
@ -122,7 +124,9 @@ class MusicDirsDialog :
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putStringArrayList(
KEY_PENDING_DIRS, ArrayList(dirAdapter.dirs.map { it.toString() }))
KEY_PENDING_DIRS,
ArrayList(dirAdapter.dirs.map { it.toString() })
)
outState.putBoolean(KEY_PENDING_MODE, isInclude(requireBinding()))
}
@ -156,7 +160,9 @@ class MusicDirsDialog :
// Turn the raw URI into a document tree URI
val docUri =
DocumentsContract.buildDocumentUriUsingTree(
uri, DocumentsContract.getTreeDocumentId(uri))
uri,
DocumentsContract.getTreeDocumentId(uri)
)
// Turn it into a semi-usable path
val treeUri = DocumentsContract.getTreeDocumentId(docUri)

View file

@ -31,3 +31,5 @@ class CacheLayer {
fun maybePopulateCachedRaw(raw: Song.Raw) = false
}
// TODO: Make raw naming consistent (always rawSong(s), not raw)

View file

@ -26,7 +26,6 @@ import android.provider.MediaStore
import androidx.annotation.RequiresApi
import androidx.core.database.getIntOrNull
import androidx.core.database.getStringOrNull
import java.io.File
import org.oxycblt.auxio.music.Date
import org.oxycblt.auxio.music.Directory
import org.oxycblt.auxio.music.Song
@ -38,6 +37,7 @@ import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.contentResolverSafe
import org.oxycblt.auxio.util.getSystemServiceCompat
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
@ -167,11 +167,13 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
val cursor =
requireNotNull(
context.contentResolverSafe.queryCursor(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
projection,
selector,
args.toTypedArray())) { "Content resolver failure: No Cursor returned" }
context.contentResolverSafe.queryCursor(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
projection,
selector,
args.toTypedArray()
)
) { "Content resolver failure: No Cursor returned" }
.also { cursor = it }
idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns._ID)
@ -250,7 +252,8 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
MediaStore.Audio.AudioColumns.ALBUM,
MediaStore.Audio.AudioColumns.ALBUM_ID,
MediaStore.Audio.AudioColumns.ARTIST,
AUDIO_COLUMN_ALBUM_ARTIST)
AUDIO_COLUMN_ALBUM_ARTIST
)
abstract val dirSelector: String
abstract fun addDirToSelectorArgs(dir: Directory, args: MutableList<String>): Boolean
@ -287,8 +290,8 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
raw.artistNames =
cursor.getString(artistIndex).run {
if (this != MediaStore.UNKNOWN_STRING) {
// While we can't natively parse multi-value tags,
// from MediaStore itself, we can still parse by user-defined separators.
// While we can't natively parse multi-value tags from MediaStore itself, we
// can still parse by user-defined separators.
maybeParseSeparators(settings)
} else {
null
@ -412,7 +415,8 @@ open class BaseApi29MediaStoreLayer(context: Context, cacheLayer: CacheLayer) :
super.projection +
arrayOf(
MediaStore.Audio.AudioColumns.VOLUME_NAME,
MediaStore.Audio.AudioColumns.RELATIVE_PATH)
MediaStore.Audio.AudioColumns.RELATIVE_PATH
)
override val dirSelector: String
get() =
@ -496,7 +500,8 @@ class Api30MediaStoreLayer(context: Context, cacheLayer: CacheLayer) :
super.projection +
arrayOf(
MediaStore.Audio.AudioColumns.CD_TRACK_NUMBER,
MediaStore.Audio.AudioColumns.DISC_NUMBER)
MediaStore.Audio.AudioColumns.DISC_NUMBER
)
override fun buildRaw(cursor: Cursor, raw: Song.Raw) {
super.buildRaw(cursor, raw)

View file

@ -116,7 +116,8 @@ class Task(context: Context, private val settings: Settings, private val raw: So
private val future =
MetadataRetriever.retrieveMetadata(
context,
MediaItem.fromUri(requireNotNull(raw.mediaStoreId) { "Invalid raw: No id" }.audioUri))
MediaItem.fromUri(requireNotNull(raw.mediaStoreId) { "Invalid raw: No id" }.audioUri)
)
/**
* Get the song that this task is trying to complete. If the task is still busy, this will
@ -213,9 +214,11 @@ class Task(context: Context, private val settings: Settings, private val raw: So
// 3. ID3v2.4 Release Date, as it is the second most common date type
// 4. ID3v2.3 Original Date, as it is like #1
// 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["TDRL"]?.run { get(0).parseTimestamp() } ?: parseId3v23Date(tags))
?: tags["TDRL"]?.run { get(0).parseTimestamp() } ?: parseId3v23Date(tags)
)
?.let { raw.date = it }
// (Sort) Album
@ -279,9 +282,11 @@ class Task(context: Context, private val settings: Settings, private val raw: So
// 2. Date, as it is the most common date type
// 3. Year, as old vorbis tags tended to use this (I know this because it's the only
// tag that android supports, so it must be 15 years old or more!)
(tags["ORIGINALDATE"]?.run { get(0).parseTimestamp() }
(
tags["ORIGINALDATE"]?.run { get(0).parseTimestamp() }
?: tags["DATE"]?.run { get(0).parseTimestamp() }
?: tags["YEAR"]?.run { get(0).parseYear() })
?: tags["YEAR"]?.run { get(0).parseYear() }
)
?.let { raw.date = it }
// (Sort) Album

View file

@ -370,4 +370,5 @@ private val GENRE_TABLE =
"Psybient",
// Auxio's extensions (Future garage is also based and deserves a slot)
"Future Garage")
"Future Garage"
)

View file

@ -152,7 +152,8 @@ class Indexer {
if (library != null) {
logD(
"Music indexing completed successfully in " +
"${System.currentTimeMillis() - start}ms")
"${System.currentTimeMillis() - start}ms"
)
Response.Ok(library)
} else {
logE("No music found")

View file

@ -58,7 +58,8 @@ class IndexingNotification(private val context: Context) :
if (indexing.current % 50 == 0) {
logD("Updating state to $indexing")
setContentText(
context.getString(R.string.fmt_indexing, indexing.current, indexing.total))
context.getString(R.string.fmt_indexing, indexing.current, indexing.total)
)
setProgress(indexing.total, indexing.current, false)
return true
}
@ -88,4 +89,6 @@ class ObservingNotification(context: Context) : ServiceNotification(context, IND
private val INDEXER_CHANNEL =
ServiceNotification.ChannelInfo(
id = BuildConfig.APPLICATION_ID + ".channel.INDEXER", nameRes = R.string.lbl_indexer)
id = BuildConfig.APPLICATION_ID + ".channel.INDEXER",
nameRes = R.string.lbl_indexer
)

View file

@ -78,7 +78,9 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback {
wakeLock =
getSystemServiceCompat(PowerManager::class)
.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":IndexerService")
PowerManager.PARTIAL_WAKE_LOCK,
BuildConfig.APPLICATION_ID + ":IndexerService"
)
settings = Settings(this, this)
indexerContentObserver = SystemContentObserver()
@ -126,7 +128,8 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback {
when (state) {
is Indexer.State.Complete -> {
if (state.response is Indexer.Response.Ok &&
state.response.library != musicStore.library) {
state.response.library != musicStore.library
) {
logD("Applying new library")
val newLibrary = state.response.library
@ -236,7 +239,10 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback {
) : ContentObserver(handler), Runnable {
init {
contentResolverSafe.registerContentObserver(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, true, this)
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
true,
this
)
}
fun release() {

View file

@ -122,7 +122,9 @@ class PlaybackPanelFragment :
val equalizerIntent =
Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL)
.putExtra(
AudioEffect.EXTRA_AUDIO_SESSION, playbackModel.currentAudioSessionId)
AudioEffect.EXTRA_AUDIO_SESSION,
playbackModel.currentAudioSessionId
)
.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC)
try {

View file

@ -60,5 +60,7 @@ class PlaybackSheetBehavior<V : View>(context: Context, attributeSet: AttributeS
MaterialShapeDrawable(sheetBackgroundDrawable.shapeAppearanceModel).apply {
fillColor = sheetBackgroundDrawable.fillColor
},
sheetBackgroundDrawable))
sheetBackgroundDrawable
)
)
}

View file

@ -20,11 +20,11 @@ package org.oxycblt.auxio.playback
import android.content.Context
import android.util.AttributeSet
import com.google.android.material.slider.Slider
import kotlin.math.max
import org.oxycblt.auxio.databinding.ViewSeekBarBinding
import org.oxycblt.auxio.music.formatDurationDs
import org.oxycblt.auxio.util.inflater
import org.oxycblt.auxio.util.logD
import kotlin.math.max
/**
* A wrapper around [Slider] that shows not only position and duration values, but also basically

View file

@ -131,7 +131,9 @@ class QueueSongViewHolder private constructor(private val binding: ItemQueueSong
fillColor = binding.context.getAttrColorCompat(R.attr.colorSurface)
elevation = binding.context.getDimen(R.dimen.elevation_normal)
},
backgroundDrawable))
backgroundDrawable
)
)
}
@SuppressLint("ClickableViewAccessibility")

View file

@ -42,7 +42,9 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe
val queueHolder = viewHolder as QueueSongViewHolder
return if (queueHolder.isEnabled) {
makeFlag(
ItemTouchHelper.ACTION_STATE_DRAG, ItemTouchHelper.UP or ItemTouchHelper.DOWN) or
ItemTouchHelper.ACTION_STATE_DRAG,
ItemTouchHelper.UP or ItemTouchHelper.DOWN
) or
makeFlag(ItemTouchHelper.ACTION_STATE_SWIPE, ItemTouchHelper.START)
} else {
0
@ -132,7 +134,9 @@ class QueueDragCallback(private val playbackModel: QueueViewModel) : ItemTouchHe
target: RecyclerView.ViewHolder
) =
playbackModel.moveQueueDataItems(
viewHolder.bindingAdapterPosition, target.bindingAdapterPosition)
viewHolder.bindingAdapterPosition,
target.bindingAdapterPosition
)
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
playbackModel.removeQueueDataItem(viewHolder.bindingAdapterPosition)

View file

@ -61,13 +61,18 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemList
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
invalidateDivider()
}
})
}
)
}
// --- VIEWMODEL SETUP ----
collectImmediately(
queueModel.queue, queueModel.index, playbackModel.isPlaying, ::updateQueue)
queueModel.queue,
queueModel.index,
playbackModel.isPlaying,
::updateQueue
)
}
override fun onDestroyBinding(binding: FragmentQueueBinding) {
@ -97,7 +102,7 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemList
binding.queueDivider.isInvisible =
(binding.queueRecycler.layoutManager as LinearLayoutManager)
.findFirstCompletelyVisibleItemPosition() < 1
.findFirstCompletelyVisibleItemPosition() < 1
queueModel.finishReplace()
@ -122,6 +127,6 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemList
val binding = requireBinding()
binding.queueDivider.isInvisible =
(binding.queueRecycler.layoutManager as LinearLayoutManager)
.findFirstCompletelyVisibleItemPosition() < 1
.findFirstCompletelyVisibleItemPosition() < 1
}
}

View file

@ -70,6 +70,10 @@ class QueueSheetBehavior<V : View>(context: Context, attributeSet: AttributeSet?
val bars = insets.systemBarInsetsCompat
expandedOffset = bars.top + barHeight + barSpacing
return insets.replaceSystemBarInsetsCompat(
bars.left, bars.top, bars.right, expandedOffset + bars.bottom)
bars.left,
bars.top,
bars.right,
expandedOffset + bars.bottom
)
}
}

View file

@ -59,7 +59,8 @@ class QueueViewModel : ViewModel(), PlaybackStateManager.Callback {
/** Remove a queue item using it's recyclerview adapter index. */
fun removeQueueDataItem(adapterIndex: Int) {
if (adapterIndex <= playbackManager.index ||
adapterIndex !in playbackManager.queue.indices) {
adapterIndex !in playbackManager.queue.indices
) {
return
}

View file

@ -21,13 +21,13 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import kotlin.math.abs
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogPreAmpBinding
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment
import org.oxycblt.auxio.util.context
import kotlin.math.abs
/**
* The dialog for customizing the ReplayGain pre-amp values.

View file

@ -24,13 +24,13 @@ import com.google.android.exoplayer2.audio.BaseAudioProcessor
import com.google.android.exoplayer2.metadata.Metadata
import com.google.android.exoplayer2.metadata.id3.TextInformationFrame
import com.google.android.exoplayer2.metadata.vorbis.VorbisComment
import java.nio.ByteBuffer
import kotlin.math.pow
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.unlikelyToBeNull
import java.nio.ByteBuffer
import kotlin.math.pow
/**
* An [AudioProcessor] that handles ReplayGain values and their amplification of the audio stream.

View file

@ -92,7 +92,8 @@ interface InternalPlayer {
// Not advancing, so don't move the position.
0f
},
creationTime)
creationTime
)
// Equality ignores the creation time to prevent functionally
// identical states from being equal.
@ -119,7 +120,8 @@ interface InternalPlayer {
// main playing value is paused.
isPlaying && isAdvancing,
positionMs,
SystemClock.elapsedRealtime())
SystemClock.elapsedRealtime()
)
}
}

View file

@ -116,7 +116,8 @@ class PlaybackStateDatabase private constructor(context: Context) :
queue = queue,
positionMs = rawState.positionMs,
repeatMode = rawState.repeatMode,
isShuffled = rawState.isShuffled)
isShuffled = rawState.isShuffled
)
}
private fun readRawState(): RawState? {
@ -139,11 +140,12 @@ class PlaybackStateDatabase private constructor(context: Context) :
index = cursor.getInt(indexIndex),
positionMs = cursor.getLong(posIndex),
repeatMode = RepeatMode.fromIntCode(cursor.getInt(repeatModeIndex))
?: RepeatMode.NONE,
?: RepeatMode.NONE,
isShuffled = cursor.getInt(shuffleIndex) == 1,
songUid = Music.UID.fromString(cursor.getString(songUidIndex))
?: return@queryAll null,
parentUid = cursor.getString(parentUidIndex)?.let(Music.UID::fromString))
?: return@queryAll null,
parentUid = cursor.getString(parentUidIndex)?.let(Music.UID::fromString)
)
}
}
@ -179,7 +181,8 @@ class PlaybackStateDatabase private constructor(context: Context) :
repeatMode = state.repeatMode,
isShuffled = state.isShuffled,
songUid = state.queue[state.index].uid,
parentUid = state.parent?.uid)
parentUid = state.parent?.uid
)
writeRawState(rawState)
writeQueue(state.queue)

View file

@ -17,7 +17,6 @@
package org.oxycblt.auxio.playback.state
import kotlin.math.max
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.oxycblt.auxio.BuildConfig
@ -31,6 +30,7 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager.Callback
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW
import kotlin.math.max
/**
* Master class (and possible god object) for the playback state.
@ -491,7 +491,8 @@ class PlaybackStateManager private constructor() {
queue = _queue,
positionMs = playerState.calculateElapsedPosition(),
isShuffled = isShuffled,
repeatMode = repeatMode)
repeatMode = repeatMode
)
// --- CALLBACKS ---

View file

@ -140,16 +140,19 @@ class MediaSessionComponent(private val context: Context, private val callback:
.putText(MediaMetadataCompat.METADATA_KEY_ARTIST, artist)
.putText(
MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST,
song.album.artist.resolveName(context))
song.album.artist.resolveName(context)
)
.putText(MediaMetadataCompat.METADATA_KEY_AUTHOR, artist)
.putText(MediaMetadataCompat.METADATA_KEY_COMPOSER, artist)
.putText(MediaMetadataCompat.METADATA_KEY_WRITER, artist)
.putText(
MediaMetadataCompat.METADATA_KEY_GENRE,
song.genres.joinToString { it.resolveName(context) })
song.genres.joinToString { it.resolveName(context) }
)
.putText(
METADATA_KEY_PARENT,
parent?.resolveName(context) ?: context.getString(R.string.lbl_all_songs))
parent?.resolveName(context) ?: context.getString(R.string.lbl_all_songs)
)
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.durationMs)
song.track?.let {
@ -186,7 +189,8 @@ class MediaSessionComponent(private val context: Context, private val callback:
notification.updateMetadata(metadata)
callback.onPostNotification(notification, PostingReason.METADATA)
}
})
}
)
}
private fun updateQueue(queue: List<Song>) {
@ -223,7 +227,8 @@ class MediaSessionComponent(private val context: Context, private val callback:
RepeatMode.NONE -> PlaybackStateCompat.REPEAT_MODE_NONE
RepeatMode.TRACK -> PlaybackStateCompat.REPEAT_MODE_ONE
RepeatMode.ALL -> PlaybackStateCompat.REPEAT_MODE_ALL
})
}
)
invalidateSecondaryAction()
}
@ -234,7 +239,8 @@ class MediaSessionComponent(private val context: Context, private val callback:
PlaybackStateCompat.SHUFFLE_MODE_ALL
} else {
PlaybackStateCompat.SHUFFLE_MODE_NONE
})
}
)
invalidateSecondaryAction()
}
@ -320,7 +326,8 @@ class MediaSessionComponent(private val context: Context, private val callback:
playbackManager.reshuffle(
shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_ALL ||
shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_GROUP,
settings)
settings
)
}
override fun onSkipToQueueItem(id: Long) {
@ -368,19 +375,22 @@ class MediaSessionComponent(private val context: Context, private val callback:
R.drawable.ic_shuffle_on_24
} else {
R.drawable.ic_shuffle_off_24
})
}
)
} else {
PlaybackStateCompat.CustomAction.Builder(
PlaybackService.ACTION_INC_REPEAT_MODE,
context.getString(R.string.desc_change_repeat),
playbackManager.repeatMode.icon)
playbackManager.repeatMode.icon
)
}
val exitAction =
PlaybackStateCompat.CustomAction.Builder(
PlaybackService.ACTION_EXIT,
context.getString(R.string.desc_exit),
R.drawable.ic_close_24)
PlaybackService.ACTION_EXIT,
context.getString(R.string.desc_exit),
R.drawable.ic_close_24
)
.build()
state.addCustomAction(extraAction.build())

View file

@ -52,10 +52,12 @@ class NotificationComponent(private val context: Context, sessionToken: MediaSes
addAction(buildRepeatAction(context, RepeatMode.NONE))
addAction(
buildAction(context, PlaybackService.ACTION_SKIP_PREV, R.drawable.ic_skip_prev_24))
buildAction(context, PlaybackService.ACTION_SKIP_PREV, R.drawable.ic_skip_prev_24)
)
addAction(buildPlayPauseAction(context, true))
addAction(
buildAction(context, PlaybackService.ACTION_SKIP_NEXT, R.drawable.ic_skip_next_24))
buildAction(context, PlaybackService.ACTION_SKIP_NEXT, R.drawable.ic_skip_next_24)
)
addAction(buildAction(context, PlaybackService.ACTION_EXIT, R.drawable.ic_close_24))
setStyle(MediaStyle().setMediaSession(sessionToken).setShowActionsInCompactView(1, 2, 3))
@ -131,7 +133,10 @@ class NotificationComponent(private val context: Context, sessionToken: MediaSes
): NotificationCompat.Action {
val action =
NotificationCompat.Action.Builder(
iconRes, actionName, context.newBroadcastPendingIntent(actionName))
iconRes,
actionName,
context.newBroadcastPendingIntent(actionName)
)
return action.build()
}
@ -140,6 +145,7 @@ class NotificationComponent(private val context: Context, sessionToken: MediaSes
val CHANNEL_INFO =
ChannelInfo(
id = BuildConfig.APPLICATION_ID + ".channel.PLAYBACK",
nameRes = R.string.lbl_playback)
nameRes = R.string.lbl_playback
)
}
}

View file

@ -39,7 +39,6 @@ import com.google.android.exoplayer2.ext.flac.LibflacAudioRenderer
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory
import kotlin.math.max
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -58,6 +57,7 @@ import org.oxycblt.auxio.ui.system.ForegroundManager
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.widgets.WidgetComponent
import org.oxycblt.auxio.widgets.WidgetProvider
import kotlin.math.max
/**
* A service that manages the system-side aspects of playback, such as:
@ -122,8 +122,10 @@ class PlaybackService :
handler,
audioListener,
AudioCapabilities.DEFAULT_AUDIO_CAPABILITIES,
replayGainProcessor),
LibflacAudioRenderer(handler, audioListener, replayGainProcessor))
replayGainProcessor
),
LibflacAudioRenderer(handler, audioListener, replayGainProcessor)
)
}
// Enable constant bitrate seeking so that certain MP3s/AACs are seekable
@ -138,7 +140,8 @@ class PlaybackService :
.setUsage(C.USAGE_MEDIA)
.setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
.build(),
true)
true
)
.build()
player.addListener(this)
@ -278,7 +281,10 @@ class PlaybackService :
override val currentState: InternalPlayer.State
get() =
InternalPlayer.State.new(
player.playWhenReady, player.isPlaying, max(player.currentPosition, 0))
player.playWhenReady,
player.isPlaying,
max(player.currentPosition, 0)
)
override fun loadSong(song: Song?, play: Boolean) {
if (song == null) {
@ -315,7 +321,8 @@ class PlaybackService :
Intent(event)
.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, packageName)
.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId)
.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC))
.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC)
)
}
/** Stop the foreground state and hide the notification */
@ -348,7 +355,9 @@ class PlaybackService :
is InternalPlayer.Action.RestoreState -> {
restoreScope.launch {
playbackManager.restoreState(
PlaybackStateDatabase.getInstance(this@PlaybackService), false)
PlaybackStateDatabase.getInstance(this@PlaybackService),
false
)
}
}
is InternalPlayer.Action.ShuffleAll -> {
@ -401,7 +410,8 @@ class PlaybackService :
override fun onSettingChanged(key: String) {
if (key == getString(R.string.set_key_replay_gain) ||
key == getString(R.string.set_key_pre_amp_with) ||
key == getString(R.string.set_key_pre_amp_without)) {
key == getString(R.string.set_key_pre_amp_without)
) {
onTracksChanged(player.currentTracks)
}
}
@ -463,7 +473,8 @@ class PlaybackService :
private fun maybeResumeFromPlug() {
if (playbackManager.song != null &&
settings.headsetAutoplay &&
initialHeadsetPlugEventHandled) {
initialHeadsetPlugEventHandled
) {
logD("Device connected, resuming")
playbackManager.changePlaying(true)
}

View file

@ -109,7 +109,11 @@ class SearchFragment :
collectImmediately(searchModel.searchResults, ::handleResults)
collectImmediately(
playbackModel.song, playbackModel.parent, playbackModel.isPlaying, ::handlePlayback)
playbackModel.song,
playbackModel.parent,
playbackModel.isPlaying,
::handlePlayback
)
collect(navModel.exploreNavigationItem, ::handleNavigation)
}
@ -175,7 +179,8 @@ class SearchFragment :
is Artist -> SearchFragmentDirections.actionShowArtist(item.uid)
is Genre -> SearchFragmentDirections.actionShowGenre(item.uid)
else -> return
})
}
)
imm.hide()
}

View file

@ -23,7 +23,6 @@ import androidx.annotation.IdRes
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import java.text.Normalizer
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
@ -42,6 +41,7 @@ import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.util.TaskGuard
import org.oxycblt.auxio.util.application
import org.oxycblt.auxio.util.logD
import java.text.Normalizer
/**
* The [ViewModel] for search functionality.
@ -157,12 +157,12 @@ class SearchViewModel(application: Application) :
private inline fun <T : Music> List<T>.baseFilterBy(value: String, additional: (T) -> Boolean) =
filter {
// The basic comparison is first by the *normalized* name, as that allows a
// non-unicode search to match with some unicode characters. If that fails,
// filter impls have fallback values, primarily around sort tags or file names.
it.resolveNameNormalized(application).contains(value, ignoreCase = true) ||
additional(it)
}
// The basic comparison is first by the *normalized* name, as that allows a
// non-unicode search to match with some unicode characters. If that fails,
// filter impls have fallback values, primarily around sort tags or file names.
it.resolveNameNormalized(application).contains(value, ignoreCase = true) ||
additional(it)
}
.ifEmpty { null }
private fun Music.resolveNameNormalized(context: Context): String {

View file

@ -84,7 +84,8 @@ class AboutFragment : ViewBindingFragment<FragmentAboutBinding>() {
binding.aboutTotalDuration.text =
getString(
R.string.fmt_lib_total_duration,
songs.sumOf { it.durationMs }.formatDurationMs(false))
songs.sumOf { it.durationMs }.formatDurationMs(false)
)
}
private fun updateAlbumCount(albums: List<Album>) {

View file

@ -77,7 +77,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
get() =
inner.getInt(
context.getString(R.string.set_key_theme),
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
)
/** Whether the dark theme should be black or not */
val useBlackTheme: Boolean
@ -97,7 +98,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
var libTabs: Array<Tab>
get() =
Tab.fromSequence(
inner.getInt(context.getString(R.string.set_key_lib_tabs), Tab.SEQUENCE_DEFAULT))
inner.getInt(context.getString(R.string.set_key_lib_tabs), Tab.SEQUENCE_DEFAULT)
)
?: unlikelyToBeNull(Tab.fromSequence(Tab.SEQUENCE_DEFAULT))
set(value) {
inner.edit {
@ -122,7 +124,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
val barAction: BarAction
get() =
BarAction.fromIntCode(
inner.getInt(context.getString(R.string.set_key_bar_action), Int.MIN_VALUE))
inner.getInt(context.getString(R.string.set_key_bar_action), Int.MIN_VALUE)
)
?: BarAction.NEXT
/**
@ -140,7 +143,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
val replayGainMode: ReplayGainMode
get() =
ReplayGainMode.fromIntCode(
inner.getInt(context.getString(R.string.set_key_replay_gain), Int.MIN_VALUE))
inner.getInt(context.getString(R.string.set_key_replay_gain), Int.MIN_VALUE)
)
?: ReplayGainMode.DYNAMIC
/** The current ReplayGain pre-amp configuration */
@ -148,7 +152,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
get() =
ReplayGainPreAmp(
inner.getFloat(context.getString(R.string.set_key_pre_amp_with), 0f),
inner.getFloat(context.getString(R.string.set_key_pre_amp_without), 0f))
inner.getFloat(context.getString(R.string.set_key_pre_amp_without), 0f)
)
set(value) {
inner.edit {
putFloat(context.getString(R.string.set_key_pre_amp_with), value.with)
@ -162,7 +167,10 @@ class Settings(private val context: Context, private val callback: Callback? = n
get() =
PlaybackMode.fromInt(
inner.getInt(
context.getString(R.string.set_key_library_song_playback_mode), Int.MIN_VALUE))
context.getString(R.string.set_key_library_song_playback_mode),
Int.MIN_VALUE
)
)
?: PlaybackMode.ALL_SONGS
/**
@ -173,7 +181,10 @@ class Settings(private val context: Context, private val callback: Callback? = n
get() =
PlaybackMode.fromInt(
inner.getInt(
context.getString(R.string.set_key_detail_song_playback_mode), Int.MIN_VALUE))
context.getString(R.string.set_key_detail_song_playback_mode),
Int.MIN_VALUE
)
)
/** Whether shuffle should stay on when a new song is selected. */
val keepShuffle: Boolean
@ -201,7 +212,9 @@ class Settings(private val context: Context, private val callback: Callback? = n
.mapNotNull { Directory.fromDocumentUri(storageManager, it) }
return MusicDirs(
dirs, inner.getBoolean(context.getString(R.string.set_key_music_dirs_include), false))
dirs,
inner.getBoolean(context.getString(R.string.set_key_music_dirs_include), false)
)
}
/** Set the list of directories that music should be hidden/loaded from. */
@ -209,9 +222,12 @@ class Settings(private val context: Context, private val callback: Callback? = n
inner.edit {
putStringSet(
context.getString(R.string.set_key_music_dirs),
musicDirs.dirs.map(Directory::toDocumentUri).toSet())
musicDirs.dirs.map(Directory::toDocumentUri).toSet()
)
putBoolean(
context.getString(R.string.set_key_music_dirs_include), musicDirs.shouldInclude)
context.getString(R.string.set_key_music_dirs_include),
musicDirs.shouldInclude
)
apply()
}
}
@ -233,12 +249,14 @@ class Settings(private val context: Context, private val callback: Callback? = n
var searchFilterMode: DisplayMode?
get() =
DisplayMode.fromInt(
inner.getInt(context.getString(R.string.set_key_search_filter), Int.MIN_VALUE))
inner.getInt(context.getString(R.string.set_key_search_filter), Int.MIN_VALUE)
)
set(value) {
inner.edit {
putInt(
context.getString(R.string.set_key_search_filter),
value?.intCode ?: Int.MIN_VALUE)
value?.intCode ?: Int.MIN_VALUE
)
apply()
}
}
@ -247,7 +265,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
var libSongSort: Sort
get() =
Sort.fromIntCode(
inner.getInt(context.getString(R.string.set_key_lib_songs_sort), Int.MIN_VALUE))
inner.getInt(context.getString(R.string.set_key_lib_songs_sort), Int.MIN_VALUE)
)
?: Sort(Sort.Mode.ByName, true)
set(value) {
inner.edit {
@ -260,7 +279,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
var libAlbumSort: Sort
get() =
Sort.fromIntCode(
inner.getInt(context.getString(R.string.set_key_lib_albums_sort), Int.MIN_VALUE))
inner.getInt(context.getString(R.string.set_key_lib_albums_sort), Int.MIN_VALUE)
)
?: Sort(Sort.Mode.ByName, true)
set(value) {
inner.edit {
@ -273,7 +293,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
var libArtistSort: Sort
get() =
Sort.fromIntCode(
inner.getInt(context.getString(R.string.set_key_lib_artists_sort), Int.MIN_VALUE))
inner.getInt(context.getString(R.string.set_key_lib_artists_sort), Int.MIN_VALUE)
)
?: Sort(Sort.Mode.ByName, true)
set(value) {
inner.edit {
@ -286,7 +307,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
var libGenreSort: Sort
get() =
Sort.fromIntCode(
inner.getInt(context.getString(R.string.set_key_lib_genres_sort), Int.MIN_VALUE))
inner.getInt(context.getString(R.string.set_key_lib_genres_sort), Int.MIN_VALUE)
)
?: Sort(Sort.Mode.ByName, true)
set(value) {
inner.edit {
@ -301,7 +323,10 @@ class Settings(private val context: Context, private val callback: Callback? = n
var sort =
Sort.fromIntCode(
inner.getInt(
context.getString(R.string.set_key_detail_album_sort), Int.MIN_VALUE))
context.getString(R.string.set_key_detail_album_sort),
Int.MIN_VALUE
)
)
?: Sort(Sort.Mode.ByDisc, true)
// Correct legacy album sort modes to Disc
@ -322,7 +347,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
var detailArtistSort: Sort
get() =
Sort.fromIntCode(
inner.getInt(context.getString(R.string.set_key_detail_artist_sort), Int.MIN_VALUE))
inner.getInt(context.getString(R.string.set_key_detail_artist_sort), Int.MIN_VALUE)
)
?: Sort(Sort.Mode.ByYear, false)
set(value) {
inner.edit {
@ -335,7 +361,8 @@ class Settings(private val context: Context, private val callback: Callback? = n
var detailGenreSort: Sort
get() =
Sort.fromIntCode(
inner.getInt(context.getString(R.string.set_key_detail_genre_sort), Int.MIN_VALUE))
inner.getInt(context.getString(R.string.set_key_detail_genre_sort), Int.MIN_VALUE)
)
?: Sort(Sort.Mode.ByName, true)
set(value) {
inner.edit {

View file

@ -26,10 +26,10 @@ import androidx.core.content.res.getTextArrayOrThrow
import androidx.preference.DialogPreference
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import java.lang.reflect.Field
import org.oxycblt.auxio.R
import org.oxycblt.auxio.util.lazyReflectedField
import org.oxycblt.auxio.util.logD
import java.lang.reflect.Field
class IntListPreference
@JvmOverloads
@ -57,13 +57,18 @@ constructor(
init {
val prefAttrs =
context.obtainStyledAttributes(
attrs, R.styleable.IntListPreference, defStyleAttr, defStyleRes)
attrs,
R.styleable.IntListPreference,
defStyleAttr,
defStyleRes
)
entries = prefAttrs.getTextArrayOrThrow(R.styleable.IntListPreference_entries)
values =
context.resources.getIntArray(
prefAttrs.getResourceIdOrThrow(R.styleable.IntListPreference_entryValues))
prefAttrs.getResourceIdOrThrow(R.styleable.IntListPreference_entryValues)
)
val offValueId = prefAttrs.getResourceId(R.styleable.IntListPreference_offValue, -1)
if (offValueId > -1) {
@ -147,6 +152,6 @@ constructor(
companion object {
private val PREFERENCE_DEFAULT_VALUE_FIELD: Field by
lazyReflectedField(Preference::class, "mDefaultValue")
lazyReflectedField(Preference::class, "mDefaultValue")
}
}

View file

@ -49,7 +49,14 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
if (child != null) {
val coordinator = parent as CoordinatorLayout
coordinatorLayoutBehavior?.onNestedPreScroll(
coordinator, this, coordinator, 0, 0, tConsumed, 0)
coordinator,
this,
coordinator,
0,
0,
tConsumed,
0
)
}
true

View file

@ -23,10 +23,10 @@ import android.view.View
import android.view.WindowInsets
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.bottomsheet.NeoBottomSheetBehavior
import kotlin.math.abs
import org.oxycblt.auxio.util.coordinatorLayoutBehavior
import org.oxycblt.auxio.util.replaceSystemBarInsetsCompat
import org.oxycblt.auxio.util.systemBarInsetsCompat
import kotlin.math.abs
/**
* A behavior that automatically re-layouts and re-insets content to align with the parent layout's
@ -127,7 +127,11 @@ class BottomSheetContentBehavior<V : View>(context: Context, attributeSet: Attri
val bars = insets.systemBarInsetsCompat
insets.replaceSystemBarInsetsCompat(
bars.left, bars.top, bars.right, (bars.bottom - consumed).coerceAtLeast(0))
bars.left,
bars.top,
bars.right,
(bars.bottom - consumed).coerceAtLeast(0)
)
}
setup = true
@ -141,7 +145,9 @@ class BottomSheetContentBehavior<V : View>(context: Context, attributeSet: Attri
View.MeasureSpec.makeMeasureSpec(parent.measuredWidth, View.MeasureSpec.EXACTLY)
val contentHeightSpec =
View.MeasureSpec.makeMeasureSpec(
parent.measuredHeight - consumed, View.MeasureSpec.EXACTLY)
parent.measuredHeight - consumed,
View.MeasureSpec.EXACTLY
)
child.measure(contentWidthSpec, contentHeightSpec)
}

View file

@ -146,7 +146,8 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
compareByDynamic(ascending, BasicComparator.ALBUM) { it.album },
compareBy(NullableComparator.INT) { it.disc },
compareBy(NullableComparator.INT) { it.track },
compareBy(BasicComparator.SONG))
compareBy(BasicComparator.SONG)
)
}
/** Sort by the artist of an item, only supported by [Album] and [Song] */
@ -164,13 +165,15 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
compareByDescending(BasicComparator.ALBUM) { it.album },
compareBy(NullableComparator.INT) { it.disc },
compareBy(NullableComparator.INT) { it.track },
compareBy(BasicComparator.SONG))
compareBy(BasicComparator.SONG)
)
override fun getAlbumComparator(ascending: Boolean): Comparator<Album> =
MultiComparator(
compareByDynamic(ascending, BasicComparator.ARTIST) { it.artist },
compareByDescending(NullableComparator.DATE) { it.date },
compareBy(BasicComparator.ALBUM))
compareBy(BasicComparator.ALBUM)
)
}
/** Sort by the year of an item, only supported by [Album] and [Song] */
@ -187,12 +190,14 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
compareByDescending(BasicComparator.ALBUM) { it.album },
compareBy(NullableComparator.INT) { it.disc },
compareBy(NullableComparator.INT) { it.track },
compareBy(BasicComparator.SONG))
compareBy(BasicComparator.SONG)
)
override fun getAlbumComparator(ascending: Boolean): Comparator<Album> =
MultiComparator(
compareByDynamic(ascending, NullableComparator.DATE) { it.date },
compareBy(BasicComparator.ALBUM))
compareBy(BasicComparator.ALBUM)
)
}
/** Sort by the duration of the item. Supports all items. */
@ -205,20 +210,27 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
override fun getSongComparator(ascending: Boolean): Comparator<Song> =
MultiComparator(
compareByDynamic(ascending) { it.durationMs }, compareBy(BasicComparator.SONG))
compareByDynamic(ascending) { it.durationMs },
compareBy(BasicComparator.SONG)
)
override fun getAlbumComparator(ascending: Boolean): Comparator<Album> =
MultiComparator(
compareByDynamic(ascending) { it.durationMs }, compareBy(BasicComparator.ALBUM))
compareByDynamic(ascending) { it.durationMs },
compareBy(BasicComparator.ALBUM)
)
override fun getArtistComparator(ascending: Boolean): Comparator<Artist> =
MultiComparator(
compareByDynamic(ascending) { it.durationMs },
compareBy(BasicComparator.ARTIST))
compareBy(BasicComparator.ARTIST)
)
override fun getGenreComparator(ascending: Boolean): Comparator<Genre> =
MultiComparator(
compareByDynamic(ascending) { it.durationMs }, compareBy(BasicComparator.GENRE))
compareByDynamic(ascending) { it.durationMs },
compareBy(BasicComparator.GENRE)
)
}
/** Sort by the amount of songs. Only applicable to music parents. */
@ -231,16 +243,21 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
override fun getAlbumComparator(ascending: Boolean): Comparator<Album> =
MultiComparator(
compareByDynamic(ascending) { it.songs.size }, compareBy(BasicComparator.ALBUM))
compareByDynamic(ascending) { it.songs.size },
compareBy(BasicComparator.ALBUM)
)
override fun getArtistComparator(ascending: Boolean): Comparator<Artist> =
MultiComparator(
compareByDynamic(ascending) { it.songs.size },
compareBy(BasicComparator.ARTIST))
compareBy(BasicComparator.ARTIST)
)
override fun getGenreComparator(ascending: Boolean): Comparator<Genre> =
MultiComparator(
compareByDynamic(ascending) { it.songs.size }, compareBy(BasicComparator.GENRE))
compareByDynamic(ascending) { it.songs.size },
compareBy(BasicComparator.GENRE)
)
}
/** Sort by the disc, and then track number of an item. Only supported by [Song]. */
@ -255,7 +272,8 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
MultiComparator(
compareByDynamic(ascending, NullableComparator.INT) { it.disc },
compareBy(NullableComparator.INT) { it.track },
compareBy(BasicComparator.SONG))
compareBy(BasicComparator.SONG)
)
}
/**
@ -273,7 +291,8 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
MultiComparator(
compareBy(NullableComparator.INT) { it.disc },
compareByDynamic(ascending, NullableComparator.INT) { it.track },
compareBy(BasicComparator.SONG))
compareBy(BasicComparator.SONG)
)
}
/** Sort by the time the item was added. Only supported by [Song] */
@ -286,12 +305,15 @@ data class Sort(val mode: Mode, val isAscending: Boolean) {
override fun getSongComparator(ascending: Boolean): Comparator<Song> =
MultiComparator(
compareByDynamic(ascending) { it.dateAdded }, compareBy(BasicComparator.SONG))
compareByDynamic(ascending) { it.dateAdded },
compareBy(BasicComparator.SONG)
)
override fun getAlbumComparator(ascending: Boolean): Comparator<Album> =
MultiComparator(
compareByDynamic(ascending) { album -> album.songs.minOf { it.dateAdded } },
compareBy(BasicComparator.ALBUM))
compareBy(BasicComparator.ALBUM)
)
}
protected inline fun <T : Music, K> compareByDynamic(

View file

@ -39,7 +39,8 @@ private val ACCENT_NAMES =
R.string.clr_orange,
R.string.clr_brown,
R.string.clr_grey,
R.string.clr_dynamic)
R.string.clr_dynamic
)
private val ACCENT_THEMES =
intArrayOf(
@ -60,7 +61,7 @@ private val ACCENT_THEMES =
R.style.Theme_Auxio_Brown,
R.style.Theme_Auxio_Grey,
R.style.Theme_Auxio_App // Dynamic colors are on the base theme
)
)
private val ACCENT_BLACK_THEMES =
intArrayOf(
@ -81,7 +82,7 @@ private val ACCENT_BLACK_THEMES =
R.style.Theme_Auxio_Black_Brown,
R.style.Theme_Auxio_Black_Grey,
R.style.Theme_Auxio_Black // Dynamic colors are on the base theme
)
)
private val ACCENT_PRIMARY_COLORS =
intArrayOf(
@ -101,7 +102,8 @@ private val ACCENT_PRIMARY_COLORS =
R.color.orange_primary,
R.color.brown_primary,
R.color.grey_primary,
R.color.dynamic_primary)
R.color.dynamic_primary
)
/**
* The data object for an accent. In the UI this is known as a "Color Scheme." This can be nominally

View file

@ -62,7 +62,8 @@ class AccentCustomizeDialog :
Accent.from(savedInstanceState.getInt(KEY_PENDING_ACCENT))
} else {
settings.accent
})
}
)
}
override fun onSaveInstanceState(outState: Bundle) {

View file

@ -21,9 +21,9 @@ import android.content.Context
import android.util.AttributeSet
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlin.math.max
import org.oxycblt.auxio.R
import org.oxycblt.auxio.util.getDimenSize
import kotlin.math.max
/**
* A sub-class of [GridLayoutManager] that automatically sets the spans so that they fit the width

View file

@ -165,7 +165,8 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleRes: Int = 0)
centerY + radius,
startAngle,
sweepAngle,
false)
false
)
}
}

View file

@ -33,7 +33,6 @@ import androidx.core.view.isInvisible
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlin.math.abs
import org.oxycblt.auxio.R
import org.oxycblt.auxio.ui.recycler.AuxioRecyclerView
import org.oxycblt.auxio.util.getDimenSize
@ -41,6 +40,7 @@ import org.oxycblt.auxio.util.getDrawableCompat
import org.oxycblt.auxio.util.isRtl
import org.oxycblt.auxio.util.isUnder
import org.oxycblt.auxio.util.systemBarInsetsCompat
import kotlin.math.abs
/**
* A [RecyclerView] that enables better fast-scrolling. This is fundamentally a implementation of
@ -97,7 +97,9 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
FastScrollPopupView(context).apply {
layoutParams =
FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
.apply {
gravity = Gravity.CENTER_HORIZONTAL or Gravity.TOP
marginEnd = context.getDimenSize(R.dimen.spacing_small)
@ -172,7 +174,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
override fun onDraw(canvas: Canvas, parent: RecyclerView, state: State) {
onPreDraw()
}
})
}
)
// We use a listener instead of overriding onTouchEvent so that we don't conflict with
// RecyclerView touch events.
@ -188,7 +191,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
): Boolean {
return onItemTouch(event)
}
})
}
)
}
// --- RECYCLERVIEW EVENT MANAGEMENT ---
@ -243,7 +247,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
thumbWidth +
popupLayoutParams.leftMargin +
popupLayoutParams.rightMargin,
popupLayoutParams.width)
popupLayoutParams.width
)
val heightMeasureSpec =
ViewGroup.getChildMeasureSpec(
@ -252,7 +257,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
thumbPadding.bottom +
popupLayoutParams.topMargin +
popupLayoutParams.bottomMargin,
popupLayoutParams.height)
popupLayoutParams.height
)
popupView.measure(widthMeasureSpec, heightMeasureSpec)
}
@ -273,7 +279,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
(thumbTop + thumbAnchorY - popupAnchorY)
.coerceAtLeast(thumbPadding.top + popupLayoutParams.topMargin)
.coerceAtMost(
height - thumbPadding.bottom - popupLayoutParams.bottomMargin - popupHeight)
height - thumbPadding.bottom - popupLayoutParams.bottomMargin - popupHeight
)
popupView.layout(popupLeft, popupTop, popupLeft + popupWidth, popupTop + popupHeight)
}
@ -348,7 +355,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
MotionEvent.ACTION_MOVE -> {
if (!dragging &&
thumbView.isUnder(downX, thumbView.top.toFloat(), minTouchTargetSize) &&
abs(eventY - downY) > touchSlop) {
abs(eventY - downY) > touchSlop
) {
if (thumbView.isUnder(downX, downY, minTouchTargetSize)) {
dragStartY = lastY
dragStartThumbOffset = thumbOffset

View file

@ -26,10 +26,10 @@ import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBinding
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.unlikelyToBeNull
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
/**
* A dialog fragment enabling ViewBinding inflation and usage across the dialog fragment lifecycle.

View file

@ -23,10 +23,10 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBinding
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.unlikelyToBeNull
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
/**
* A fragment enabling ViewBinding inflation and usage across the fragment lifecycle.

View file

@ -106,6 +106,8 @@ abstract class DialogViewHolder(root: View) : RecyclerView.ViewHolder(root) {
// Actually make the item full-width, which it won't be in dialogs
root.layoutParams =
RecyclerView.LayoutParams(
RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)
RecyclerView.LayoutParams.MATCH_PARENT,
RecyclerView.LayoutParams.WRAP_CONTENT
)
}
}

View file

@ -49,7 +49,8 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
initialPadding.left,
initialPadding.top,
initialPadding.right,
initialPadding.bottom + insets.systemBarInsetsCompat.bottom)
initialPadding.bottom + insets.systemBarInsetsCompat.bottom
)
return insets
}

View file

@ -126,7 +126,8 @@ class SyncListDiffer<T>(
throw AssertionError()
}
}
})
}
)
field = newList
result.dispatchUpdatesTo(updateCallback)

View file

@ -68,13 +68,15 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
ViewGroup.getChildMeasureSpec(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
0,
divider.layoutParams.width)
divider.layoutParams.width
)
val heightMeasureSpec =
ViewGroup.getChildMeasureSpec(
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY),
0,
divider.layoutParams.height)
divider.layoutParams.height
)
divider.measure(widthMeasureSpec, heightMeasureSpec)
}

View file

@ -122,7 +122,8 @@ class ArtistViewHolder private constructor(private val binding: ItemParentBindin
binding.context.getString(
R.string.fmt_two,
binding.context.getPlural(R.plurals.fmt_album_count, item.albums.size),
binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size))
binding.context.getPlural(R.plurals.fmt_song_count, item.songs.size)
)
// binding.parentMenu.setOnClickListener { listener.onOpenMenu(item, it) }
binding.root.setOnLongClickListener {
listener.onOpenMenu(item, it)

View file

@ -37,9 +37,9 @@ import androidx.annotation.PluralsRes
import androidx.annotation.Px
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import kotlin.reflect.KClass
import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.MainActivity
import kotlin.reflect.KClass
/** Shortcut to get a [LayoutInflater] from a [Context] */
val Context.inflater: LayoutInflater
@ -152,7 +152,8 @@ fun Context.newMainPendingIntent(): PendingIntent =
this,
IntegerTable.REQUEST_CODE,
Intent(this, MainActivity::class.java),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0
)
/** Create a broadcast [PendingIntent] */
fun Context.newBroadcastPendingIntent(what: String): PendingIntent =
@ -160,4 +161,5 @@ fun Context.newBroadcastPendingIntent(what: String): PendingIntent =
this,
IntegerTable.REQUEST_CODE,
Intent(what).setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0
)

View file

@ -231,7 +231,8 @@ val WindowInsets.systemGestureInsetsCompat: Insets
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
Insets.max(
getCompatInsets(WindowInsets.Type.systemGestures()),
getCompatInsets(WindowInsets.Type.systemBars()))
getCompatInsets(WindowInsets.Type.systemBars())
)
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> {
@Suppress("DEPRECATION")
@ -246,7 +247,8 @@ fun WindowInsets.getSystemWindowCompatInsets() =
systemWindowInsetLeft,
systemWindowInsetTop,
systemWindowInsetRight,
systemWindowInsetBottom)
systemWindowInsetBottom
)
@Suppress("DEPRECATION")
@RequiresApi(Build.VERSION_CODES.Q)
@ -270,7 +272,8 @@ fun WindowInsets.replaceSystemBarInsetsCompat(
WindowInsets.Builder(this)
.setInsets(
WindowInsets.Type.systemBars(),
Insets.of(left, top, right, bottom).toPlatformInsets())
Insets.of(left, top, right, bottom).toPlatformInsets()
)
.build()
}
else -> {

View file

@ -82,11 +82,13 @@ private val Any.autoTag: String
@Suppress("KotlinConstantConditions")
private fun basedCopyleftNotice(): Boolean {
if (BuildConfig.APPLICATION_ID != "org.oxycblt.auxio" &&
BuildConfig.APPLICATION_ID != "org.oxycblt.auxio.debug") {
BuildConfig.APPLICATION_ID != "org.oxycblt.auxio.debug"
) {
Log.d(
"Auxio Project",
"Friendly reminder: Auxio is licensed under the " +
"GPLv3 and all derivative apps must be made open source!")
"GPLv3 and all derivative apps must be made open source!"
)
return true
}

View file

@ -18,11 +18,11 @@
package org.oxycblt.auxio.util
import android.os.Looper
import org.oxycblt.auxio.BuildConfig
import java.lang.reflect.Field
import java.lang.reflect.Method
import java.util.concurrent.CancellationException
import kotlin.reflect.KClass
import org.oxycblt.auxio.BuildConfig
/** Assert that we are on a background thread. */
fun requireBackgroundThread() {

View file

@ -126,7 +126,8 @@ private fun RemoteViews.applyCover(
setImageViewBitmap(R.id.widget_cover, state.cover)
setContentDescription(
R.id.widget_cover,
context.getString(R.string.desc_album_cover, state.song.album.resolveName(context)))
context.getString(R.string.desc_album_cover, state.song.album.resolveName(context))
)
} else {
setImageViewResource(R.id.widget_cover, R.drawable.ic_remote_default_cover_24)
setContentDescription(R.id.widget_cover, context.getString(R.string.desc_no_cover))
@ -144,7 +145,8 @@ private fun RemoteViews.applyPlayPauseControls(
setOnClickPendingIntent(
R.id.widget_play_pause,
context.newBroadcastPendingIntent(PlaybackService.ACTION_PLAY_PAUSE))
context.newBroadcastPendingIntent(PlaybackService.ACTION_PLAY_PAUSE)
)
// Like the Android 13 media controls, use a circular fab when paused, and a squircle fab
// when playing.
@ -173,10 +175,14 @@ private fun RemoteViews.applyBasicControls(
applyPlayPauseControls(context, state)
setOnClickPendingIntent(
R.id.widget_skip_prev, context.newBroadcastPendingIntent(PlaybackService.ACTION_SKIP_PREV))
R.id.widget_skip_prev,
context.newBroadcastPendingIntent(PlaybackService.ACTION_SKIP_PREV)
)
setOnClickPendingIntent(
R.id.widget_skip_next, context.newBroadcastPendingIntent(PlaybackService.ACTION_SKIP_NEXT))
R.id.widget_skip_next,
context.newBroadcastPendingIntent(PlaybackService.ACTION_SKIP_NEXT)
)
return this
}
@ -189,11 +195,13 @@ private fun RemoteViews.applyFullControls(
setOnClickPendingIntent(
R.id.widget_repeat,
context.newBroadcastPendingIntent(PlaybackService.ACTION_INC_REPEAT_MODE))
context.newBroadcastPendingIntent(PlaybackService.ACTION_INC_REPEAT_MODE)
)
setOnClickPendingIntent(
R.id.widget_shuffle,
context.newBroadcastPendingIntent(PlaybackService.ACTION_INVERT_SHUFFLE))
context.newBroadcastPendingIntent(PlaybackService.ACTION_INVERT_SHUFFLE)
)
val shuffleRes =
when {

View file

@ -22,7 +22,6 @@ import android.graphics.Bitmap
import android.os.Build
import coil.request.ImageRequest
import coil.transform.RoundedCornersTransformation
import kotlin.math.sqrt
import org.oxycblt.auxio.R
import org.oxycblt.auxio.image.BitmapProvider
import org.oxycblt.auxio.image.SquareFrameTransform
@ -34,6 +33,7 @@ import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.getDimenSize
import org.oxycblt.auxio.util.logD
import kotlin.math.sqrt
/**
* A wrapper around each [WidgetProvider] that plugs into the main Auxio process and updates the
@ -109,7 +109,8 @@ class WidgetComponent(private val context: Context) :
.size(computeSize(sw, sh, 10f))
.transformations(
SquareFrameTransform.INSTANCE,
RoundedCornersTransformation(cornerRadius.toFloat()))
RoundedCornersTransformation(cornerRadius.toFloat())
)
} else {
// Divide by two to really make sure we aren't hitting the memory limit.
builder.size(computeSize(sw, sh, 2f))
@ -120,7 +121,8 @@ class WidgetComponent(private val context: Context) :
val state = WidgetState(song, bitmap, isPlaying, repeatMode, isShuffled)
widget.update(context, state)
}
})
}
)
}
private fun computeSize(sw: Int, sh: Int, modifier: Float) =
@ -146,7 +148,8 @@ class WidgetComponent(private val context: Context) :
override fun onSettingChanged(key: String) {
if (key == context.getString(R.string.set_key_show_covers) ||
key == context.getString(R.string.set_key_quality_covers) ||
key == context.getString(R.string.set_key_round_mode)) {
key == context.getString(R.string.set_key_round_mode)
) {
update()
}
}

View file

@ -61,7 +61,8 @@ class WidgetProvider : AppWidgetProvider() {
SizeF(180f, 152f) to createSmallWidget(context, state),
SizeF(272f, 152f) to createWideWidget(context, state),
SizeF(180f, 272f) to createMediumWidget(context, state),
SizeF(272f, 272f) to createLargeWidget(context, state))
SizeF(272f, 272f) to createLargeWidget(context, state)
)
val awm = AppWidgetManager.getInstance(context)
@ -70,7 +71,9 @@ class WidgetProvider : AppWidgetProvider() {
} catch (e: Exception) {
logW("Unable to update widget: $e")
awm.updateAppWidget(
ComponentName(context, this::class.java), createDefaultWidget(context))
ComponentName(context, this::class.java),
createDefaultWidget(context)
)
}
}

View file

@ -204,14 +204,14 @@
<string name="set_rewind_prev_desc">Rewind before skipping to the previous song</string>
<string name="set_repeat_pause">Pause on repeat</string>
<string name="set_repeat_pause_desc">Pause when a song repeats</string>
<string name="set_content">Content</string>
<string name="set_save_state">Save playback state</string>
<string name="set_save_desc">Save the current playback state now</string>
<string name="set_wipe_state">Clear playback state</string>
<string name="set_wipe_desc">Clear the previously saved playback state (if any)</string>
<string name="set_restore_state">Restore playback state</string>
<string name="set_restore_desc">Restore the previously saved playback state (if any)</string>
<string name="set_content">Content</string>
<string name="set_reindex">Reload music</string>
<string name="set_reindex_desc">May wipe playback state</string>
<string name="set_dirs">Music folders</string>