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 {
kotlin {
target "src/**/*.kt"
ktfmt("0.37").dropboxStyle()
// ktlint does checking, while ktfmt actually does formatting
ktlint()
ktfmt().dropboxStyle()
licenseHeaderFile("NOTICE")
}
}
afterEvaluate {
preDebugBuild.dependsOn spotlessApply
}

View file

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

View file

@ -28,7 +28,14 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
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.ui.Sort
import org.oxycblt.auxio.ui.recycler.Header

View file

@ -26,10 +26,10 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogSongDetailBinding
import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment
import org.oxycblt.auxio.util.androidActivityViewModels
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

View file

@ -29,12 +29,12 @@ import org.oxycblt.auxio.databinding.ItemDiscHeaderBinding
import org.oxycblt.auxio.detail.DiscHeader
import org.oxycblt.auxio.music.Album
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.Item
import org.oxycblt.auxio.ui.recycler.MenuItemListener
import org.oxycblt.auxio.ui.recycler.SimpleItemCallback
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.util.getPlural
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 constructor(
private val binding: ItemParentBinding,
) : IndicatorAdapter.ViewHolder(binding.root) {
private class ArtistAlbumViewHolder private constructor(private val binding: ItemParentBinding) :
IndicatorAdapter.ViewHolder(binding.root) {
fun bind(item: Album, listener: MenuItemListener) {
binding.parentImage.bind(item)
binding.parentName.text = item.resolveName(binding.context)
@ -178,10 +176,8 @@ private constructor(
}
}
private class ArtistSongViewHolder
private constructor(
private val binding: ItemSongBinding,
) : IndicatorAdapter.ViewHolder(binding.root) {
private class ArtistSongViewHolder private constructor(private val binding: ItemSongBinding) :
IndicatorAdapter.ViewHolder(binding.root) {
fun bind(item: Song, listener: MenuItemListener) {
binding.songAlbumCover.bind(item)
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
}
@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

@ -25,11 +25,11 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.ItemDetailBinding
import org.oxycblt.auxio.music.Genre
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.SimpleItemCallback
import org.oxycblt.auxio.ui.recycler.SongViewHolder
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.util.getPlural
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.GenreListFragment
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.playback.PlaybackViewModel
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.Sort
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

View file

@ -21,11 +21,13 @@ import android.os.Bundle
import android.text.format.DateUtils
import android.view.View
import android.view.ViewGroup
import java.util.*
import java.util.Formatter
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.Album
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.Sort
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.SyncListDiffer
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.

View file

@ -24,6 +24,7 @@ import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort
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.SyncListDiffer
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.music.formatDurationMs
/**
* 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.music.Genre
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort
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.SyncListDiffer
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.music.formatDurationMs
/**
* 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.music.MusicParent
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.ui.DisplayMode
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.util.collectImmediately
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.

View file

@ -17,6 +17,10 @@
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.util.logE
@ -50,6 +54,7 @@ sealed class Tab(open val mode: DisplayMode) {
companion object {
/** The length a well-formed tab sequence should be */
private const val SEQUENCE_LEN = 4
/** The default tab sequence, represented in integer form */
const val SEQUENCE_DEFAULT = 0b1000_1001_1010_1011_0100

View file

@ -82,7 +82,7 @@ class ArtistImageFetcher
private constructor(
private val context: Context,
private val size: Size,
private val artist: Artist,
private val artist: Artist
) : BaseFetcher() {
override suspend fun fetch(): FetchResult? {
val albums = Sort(Sort.Mode.ByName, true).albums(artist.albums)
@ -104,7 +104,7 @@ class GenreImageFetcher
private constructor(
private val context: Context,
private val size: Size,
private val genre: Genre,
private val genre: Genre
) : BaseFetcher() {
override suspend fun fetch(): FetchResult? {
// 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 org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.Date.Companion.from
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.recycler.Item
import org.oxycblt.auxio.util.inRangeOrNull
@ -53,7 +54,9 @@ sealed class Music : Item {
* fast-scrolling.
*/
val sortName: String?
get() = rawSortName ?: rawName?.run {
get() =
rawSortName
?: rawName?.run {
when {
length > 5 && startsWith("the ", ignoreCase = true) -> substring(4)
length > 4 && startsWith("an ", ignoreCase = true) -> substring(3)
@ -185,6 +188,7 @@ class Song constructor(raw: Raw) : Music() {
val disc = raw.disc
private var _album: Album? = null
/** The album of this song. */
val album: Album
get() = unlikelyToBeNull(_album)
@ -212,6 +216,7 @@ class Song constructor(raw: Raw) : Music() {
artistName ?: album.artist.resolveName(context)
private val _genres: MutableList<Genre> = mutableListOf()
/**
* 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.
@ -327,6 +332,7 @@ class Album constructor(raw: Raw, override val songs: List<Song>) : MusicParent(
val durationMs = songs.sumOf { it.durationMs }
private var _artist: Artist? = null
/** The parent artist of this album. */
val artist: Artist
get() = unlikelyToBeNull(_artist)
@ -634,9 +640,8 @@ class Date private constructor(private val tokens: List<Int>) : Comparable<Date>
fun from(timestamp: String): Date? {
val groups =
(ISO8601_REGEX.matchEntire(timestamp) ?: return null)
.groupValues.mapIndexedNotNull { index, s ->
if (index % 2 != 0) s.toIntOrNull() else null
}
.groupValues
.mapIndexedNotNull { index, s -> if (index % 2 != 0) s.toIntOrNull() else null }
return fromTokens(groups)
}

View file

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

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(
@ -59,13 +59,17 @@ val Long.audioUri: Uri
val Long.albumCoverUri: Uri
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. */
fun Date?.resolveYear(context: Context) =
this?.resolveYear(context) ?: context.getString(R.string.def_date)
/** 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 */
fun Long.msToDs() = floorDiv(100)

View file

@ -30,10 +30,12 @@ class MusicViewModel : ViewModel(), Indexer.Callback {
private val indexer = Indexer.getInstance()
private val _indexerState = MutableStateFlow<Indexer.State?>(null)
/** The current music indexing state. */
val indexerState: StateFlow<Indexer.State?> = _indexerState
private val _libraryExists = MutableStateFlow(false)
/** Whether a music library has successfully been loaded. */
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.util.lazyReflectedMethod
/** 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)
@ -48,9 +47,9 @@ class Directory private constructor(val volume: StorageVolume, val relativePath:
// "primary" actually corresponds to the internal storage, not the primary volume.
// Removable storage is represented with the UUID.
if (volume.isInternalCompat) {
"${DOCUMENT_URI_PRIMARY_NAME}:${relativePath}"
"$DOCUMENT_URI_PRIMARY_NAME:$relativePath"
} else {
volume.uuidCompat?.let { uuid -> "${uuid}:${relativePath}" }
volume.uuidCompat?.let { uuid -> "$uuid:$relativePath" }
}
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
import org.oxycblt.auxio.music.Song
/**
* TODO: Stub class, not implemented yet
*/
/** TODO: Stub class, not implemented yet */
class CacheLayer {
fun init() {
// STUB: Add cache database

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
import android.content.Context
@ -9,6 +26,7 @@ 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
@ -20,7 +38,6 @@ 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
@ -81,8 +98,8 @@ import java.io.File
*/
/**
* The layer that loads music from the MediaStore database. This is an intermediate step in
* the music loading process.
* The layer that loads music from the MediaStore database. This is an intermediate step in the
* music loading process.
* @author OxygenCobalt
*/
abstract class MediaStoreLayer(private val context: Context, private val cacheLayer: CacheLayer) {
@ -105,11 +122,10 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
private val settings = Settings(context)
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 {
cacheLayer.init()
@ -149,7 +165,8 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
logD("Starting query [proj: ${projection.toList()}, selector: $selector, args: $args]")
val cursor = requireNotNull(
val cursor =
requireNotNull(
context.contentResolverSafe.queryCursor(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
projection,
@ -159,12 +176,12 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns._ID)
titleIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.TITLE)
displayNameIndex =
cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DISPLAY_NAME)
displayNameIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DISPLAY_NAME)
mimeTypeIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.MIME_TYPE)
sizeIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.SIZE)
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)
yearIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.YEAR)
albumIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ALBUM)
@ -175,9 +192,7 @@ abstract class MediaStoreLayer(private val context: Context, private val cacheLa
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>) {
cursor?.close()
cursor = null
@ -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.
raw.albumArtistNames = cursor.getStringOrNull(albumArtistIndex)?.maybeParseSeparators(settings)
raw.albumArtistNames =
cursor.getStringOrNull(albumArtistIndex)?.maybeParseSeparators(settings)
}
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
// speed, we only want to add redundancy on known issues, not with possible issues.
@ -377,7 +392,8 @@ class Api21MediaStoreLayer(context: Context, cacheLayer: CacheLayer) :
* @author OxygenCobalt
*/
@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 relativePathIndex = -1
@ -431,7 +447,8 @@ open class BaseApi29MediaStoreLayer(context: Context, cacheLayer: CacheLayer) :
* @author OxygenCobalt
*/
@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
override fun init(): Cursor {
@ -462,7 +479,8 @@ open class Api29MediaStoreLayer(context: Context, cacheLayer: CacheLayer) : Base
* @author OxygenCobalt
*/
@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 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
import android.content.Context
import androidx.core.text.isDigitsOnly
import com.google.android.exoplayer2.MediaItem
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.vorbis.VorbisComment
import org.oxycblt.auxio.music.Date
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.audioUri
import org.oxycblt.auxio.settings.Settings
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
/**
* 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 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
/**
* 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 parse(emit: (Song.Raw) -> Unit) {
@ -90,7 +102,6 @@ class MetadataLayer(private val context: Context, private val mediaStoreLayer: M
}
}
companion object {
/** The amount of tasks this backend can run efficiently at once. */
private const val TASK_CAPACITY = 8
@ -204,8 +215,7 @@ class Task(context: Context, private val settings: Settings, private val raw: So
// 5. ID3v2.3 Release Year, as it is the most common date type
(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
@ -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? {
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"]
return if (tdat != null && tdat[0].length == 4 && tdat[0].isDigitsOnly()) {

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
import androidx.core.text.isDigitsOnly
@ -50,19 +67,21 @@ fun List<String>.parseMultiValue(settings: Settings) =
}
/**
* Maybe a single tag into multi values with the user-preferred separators. If not enabled,
* the plain string will be returned.
* Maybe a single tag into multi values with the user-preferred separators. If not enabled, the
* plain string will be returned.
*/
fun String.maybeParseSeparators(settings: Settings): List<String> {
// Get the separators the user desires. If null, we don't parse any.
val separators = settings.separators ?: return listOf(this)
// Try to cache compiled regexes for particular separator combinations.
val regex = synchronized(SEPARATOR_REGEX_CACHE) {
val regex =
synchronized(SEPARATOR_REGEX_CACHE) {
SEPARATOR_REGEX_CACHE.getOrPut(separators) { Regex("[^\\\\][$separators]") }
}
val escape = synchronized(ESCAPE_REGEX_CACHE) {
val escape =
synchronized(ESCAPE_REGEX_CACHE) {
ESCAPE_REGEX_CACHE.getOrPut(separators) { Regex("\\\\[$separators]") }
}
@ -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))
/**
* Parse a multi-value genre name using ID3v2 rules. If there is one value, the ID3v2.3
* rules will be used, followed by separator parsing. Otherwise, each value will be iterated
* through, and numeric values transformed into string values.
* Parse a multi-value genre name using ID3v2 rules. If there is one value, the ID3v2.3 rules will
* be used, followed by separator parsing. Otherwise, each value will be iterated through, and
* numeric values transformed into string values.
*/
fun List<String>.parseId3GenreNames(settings: Settings) =
if (size == 1) {
@ -89,13 +106,9 @@ fun List<String>.parseId3GenreNames(settings: Settings) =
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) =
parseId3v1Genre()?.let { listOf(it) } ?:
parseId3v2Genre() ?:
maybeParseSeparators(settings)
parseId3v1Genre()?.let { listOf(it) } ?: parseId3v2Genre() ?: maybeParseSeparators(settings)
private fun String.parseId3v1Genre(): String? =
when {

View file

@ -26,7 +26,11 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
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.Api29MediaStoreLayer
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
* with their corresponding parents/children.
*
* This class in particular handles 3 primarily. For the code that handles 1 and 2, see the
* layer implementations.
* This class in particular handles 3 primarily. For the code that handles 1 and 2, see the layer
* implementations.
*
* 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
@ -205,8 +209,10 @@ class Indexer {
val mediaStoreLayer =
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> Api30MediaStoreLayer(context, cacheLayer)
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> Api29MediaStoreLayer(context, cacheLayer)
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R ->
Api30MediaStoreLayer(context, cacheLayer)
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q ->
Api29MediaStoreLayer(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
* function are **not** well-formed. The companion [buildAlbums], [buildArtists], and
* Does the initial query over the song database using [metadataLayer]. The songs returned by
* 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
* linked up.
*/

View file

@ -20,7 +20,10 @@ package org.oxycblt.auxio.music.system
import android.app.Service
import android.content.Intent
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 coil.imageLoader
import kotlinx.coroutines.CoroutineScope

View file

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

View file

@ -24,6 +24,7 @@ import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentPlaybackBarBinding
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.msToDs
import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.settings.Settings
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.getAttrColorCompat
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

View file

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

View file

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

View file

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

View file

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

View file

@ -25,7 +25,11 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.shape.MaterialShapeDrawable
import org.oxycblt.auxio.R
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.

View file

@ -23,8 +23,10 @@ import org.oxycblt.auxio.IntegerTable
enum class ReplayGainMode {
/** Apply the track gain, falling back to the album gain if the track gain is not found. */
TRACK,
/** Apply the album gain, falling back to the track gain if the album gain is not found. */
ALBUM,
/** Apply the album gain only when playing from an album, defaulting to track gain otherwise. */
DYNAMIC;
@ -46,5 +48,5 @@ data class ReplayGainPreAmp(
/** The value to use when ReplayGain tags are present. */
val with: Float,
/** 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,
positionMs = rawState.positionMs,
repeatMode = rawState.repeatMode,
isShuffled = rawState.isShuffled,
)
isShuffled = rawState.isShuffled)
}
private fun readRawState(): RawState? {
@ -258,7 +257,7 @@ class PlaybackStateDatabase private constructor(context: Context) :
val parent: MusicParent?,
val positionMs: Long,
val repeatMode: RepeatMode,
val isShuffled: Boolean,
val isShuffled: Boolean
)
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.MusicStore
import org.oxycblt.auxio.music.Song
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
@ -58,13 +59,16 @@ class PlaybackStateManager private constructor() {
/** The currently playing song. Null if there isn't one */
val song
get() = queue.getOrNull(index)
/** The parent the queue is based on, null if all songs */
var parent: MusicParent? = null
private set
private var _queue = mutableListOf<Song>()
/** The current queue determined by [parent] */
val queue
get() = _queue
/** The current position in the queue */
var index = -1
private set
@ -79,6 +83,7 @@ class PlaybackStateManager private constructor() {
field = value
notifyRepeatModeChanged()
}
/** Whether the queue is shuffled */
var isShuffled = false
private set

View file

@ -53,6 +53,7 @@ class SearchViewModel(application: Application) :
private val settings = Settings(application)
private val _searchResults = MutableStateFlow(listOf<Item>())
/** Current search results from the last [search] call. */
val searchResults: StateFlow<List<Item>>
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.Genre
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.ui.fragment.ViewBindingFragment
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.collectImmediately
import org.oxycblt.auxio.music.formatDurationMs
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.showToast
import org.oxycblt.auxio.util.systemBarInsetsCompat

View file

@ -19,7 +19,9 @@ package org.oxycblt.auxio.settings
import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import android.os.storage.StorageManager
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.edit
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.Sort
import org.oxycblt.auxio.ui.accent.Accent
import org.oxycblt.auxio.util.logD
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?
// 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.
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) {
inner.edit {
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 com.google.android.material.bottomsheet.NeoBottomSheetBehavior
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

View file

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

View file

@ -18,7 +18,6 @@
package org.oxycblt.auxio.ui
import androidx.annotation.IdRes
import kotlin.UnsupportedOperationException
import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R
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.Music
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.ui.Sort.Mode
/**
* Represents the sort modes used in Auxio.

View file

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

View file

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

View file

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

View file

@ -44,6 +44,7 @@ fun createThinWidget(context: Context, state: WidgetComponent.WidgetState) =
.applyRoundingToBackground(context)
.applyMeta(context, state)
.applyBasicControls(context, state)
/**
* 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

View file

@ -160,6 +160,6 @@ class WidgetComponent(private val context: Context) :
val cover: Bitmap?,
val isPlaying: Boolean,
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 "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_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
// in the individual module build.gradle files

Binary file not shown.

View file

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
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
zipStorePath=wrapper/dists

6
gradlew vendored
View file

@ -205,6 +205,12 @@ set -- \
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.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.

10
gradlew.bat vendored
View file

@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 0 goto execute
echo.
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
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal