music: split off extractor parsing
Split off parsing-related components from extractor into a new parsing module. A lot of these methods are used in non-extractor code, so it makes more sense for them to not be part of the extractors. The code that is really extractor-specific can remain within the extractor files.
This commit is contained in:
parent
dc46c49f07
commit
7721e64096
20 changed files with 143 additions and 155 deletions
|
@ -116,7 +116,7 @@
|
|||
<!-- </intent-filter>-->
|
||||
<!-- </receiver>-->
|
||||
|
||||
<!-- "Now Playing" widget.. -->
|
||||
<!-- "Now Playing" widget. -->
|
||||
<receiver
|
||||
android:name=".widgets.WidgetProvider"
|
||||
android:exported="false"
|
||||
|
|
|
@ -216,7 +216,7 @@ class MainFragment :
|
|||
lastInsets?.let { translationY = it.systemBarInsetsCompat.top * halfOutRatio }
|
||||
}
|
||||
|
||||
// Prevent interactions when the playback panell fully fades out.
|
||||
// Prevent interactions when the playback panel fully fades out.
|
||||
binding.playbackPanelFragment.isInvisible = binding.playbackPanelFragment.alpha == 0f
|
||||
|
||||
binding.queueSheet.apply {
|
||||
|
|
|
@ -31,12 +31,12 @@ 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.ui.CoordinatorAppBarLayout
|
||||
import org.oxycblt.auxio.util.getInteger
|
||||
import org.oxycblt.auxio.util.lazyReflectedField
|
||||
|
||||
/**
|
||||
* An [AuxioAppBarLayout] that displays the title of a hidden [Toolbar] when the scrolling view goes
|
||||
* An [CoordinatorAppBarLayout] that displays the title of a hidden [Toolbar] when the scrolling view goes
|
||||
* beyond it's first item.
|
||||
*
|
||||
* This is intended for the detail views, in which the first item is the album/artist/genre header,
|
||||
|
@ -51,7 +51,7 @@ import org.oxycblt.auxio.util.lazyReflectedField
|
|||
class DetailAppBarLayout
|
||||
@JvmOverloads
|
||||
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
||||
AuxioAppBarLayout(context, attrs, defStyleAttr) {
|
||||
CoordinatorAppBarLayout(context, attrs, defStyleAttr) {
|
||||
private var titleView: TextView? = null
|
||||
private var recycler: RecyclerView? = null
|
||||
|
||||
|
|
|
@ -345,7 +345,6 @@ class HomeFragment :
|
|||
is Indexer.Response.Err -> {
|
||||
logD("Updating UI to Response.Err state")
|
||||
binding.homeIndexingStatus.text = context.getString(R.string.err_index_failed)
|
||||
|
||||
// Configure the action to act as a reload trigger.
|
||||
binding.homeIndexingAction.apply {
|
||||
visibility = View.VISIBLE
|
||||
|
@ -354,10 +353,9 @@ class HomeFragment :
|
|||
}
|
||||
}
|
||||
is Indexer.Response.NoMusic -> {
|
||||
// TODO: Move this state to the list fragments (makes life easier)
|
||||
// TODO: Move this state to the list fragments (quality of life)
|
||||
logD("Updating UI to Response.NoMusic state")
|
||||
binding.homeIndexingStatus.text = context.getString(R.string.err_no_music)
|
||||
|
||||
// Configure the action to act as a reload trigger.
|
||||
binding.homeIndexingAction.apply {
|
||||
visibility = View.VISIBLE
|
||||
|
@ -368,7 +366,6 @@ class HomeFragment :
|
|||
is Indexer.Response.NoPerms -> {
|
||||
logD("Updating UI to Response.NoPerms state")
|
||||
binding.homeIndexingStatus.text = context.getString(R.string.err_no_perms)
|
||||
|
||||
// Configure the action to act as a permission launcher.
|
||||
binding.homeIndexingAction.apply {
|
||||
visibility = View.VISIBLE
|
||||
|
|
|
@ -65,7 +65,7 @@ constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr
|
|||
private val cornerRadius: Float
|
||||
|
||||
init {
|
||||
// Obtain some StyledImageView attributes to use later when theming the cusotm view.
|
||||
// Obtain some StyledImageView attributes to use later when theming the custom view.
|
||||
@SuppressLint("CustomViewStyleable")
|
||||
val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.StyledImageView)
|
||||
// Keep track of our corner radius so that we can apply the same attributes to the custom
|
||||
|
|
|
@ -30,9 +30,8 @@ import kotlinx.parcelize.IgnoredOnParcel
|
|||
import kotlinx.parcelize.Parcelize
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.list.Item
|
||||
import org.oxycblt.auxio.music.extractor.parseId3GenreNames
|
||||
import org.oxycblt.auxio.music.extractor.parseMultiValue
|
||||
import org.oxycblt.auxio.music.extractor.toUuidOrNull
|
||||
import org.oxycblt.auxio.music.parsing.parseId3GenreNames
|
||||
import org.oxycblt.auxio.music.parsing.parseMultiValue
|
||||
import org.oxycblt.auxio.music.storage.*
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.util.nonZeroOrNull
|
||||
|
@ -1213,6 +1212,18 @@ class Genre constructor(private val raw: Raw, override val songs: List<Song>) :
|
|||
|
||||
// --- MUSIC UID CREATION UTILITIES ---
|
||||
|
||||
/**
|
||||
* Convert a [String] to a [UUID].
|
||||
* @return A [UUID] converted from the [String] value, or null if the value was not valid.
|
||||
* @see UUID.fromString
|
||||
*/
|
||||
fun String.toUuidOrNull(): UUID? =
|
||||
try {
|
||||
UUID.fromString(this)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
null
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a [MessageDigest] with a lowercase [String].
|
||||
* @param string The [String] to hash. If null, it will not be hashed.
|
||||
|
|
|
@ -23,7 +23,10 @@ import android.database.sqlite.SQLiteDatabase
|
|||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import androidx.core.database.getIntOrNull
|
||||
import androidx.core.database.getStringOrNull
|
||||
import org.oxycblt.auxio.music.Date
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.music.parsing.correctWhitespace
|
||||
import org.oxycblt.auxio.music.parsing.splitEscaped
|
||||
import org.oxycblt.auxio.util.*
|
||||
|
||||
/**
|
||||
|
@ -278,7 +281,7 @@ private class CacheDatabase(context: Context) :
|
|||
|
||||
raw.track = cursor.getIntOrNull(trackIndex)
|
||||
raw.disc = cursor.getIntOrNull(discIndex)
|
||||
raw.date = cursor.getStringOrNull(dateIndex)?.parseTimestamp()
|
||||
raw.date = cursor.getStringOrNull(dateIndex)?.let(Date::from)
|
||||
|
||||
raw.albumMusicBrainzId = cursor.getStringOrNull(albumMusicBrainzIdIndex)
|
||||
raw.albumName = cursor.getString(albumNameIndex)
|
||||
|
|
|
@ -26,8 +26,10 @@ import android.provider.MediaStore
|
|||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.database.getIntOrNull
|
||||
import androidx.core.database.getStringOrNull
|
||||
import org.oxycblt.auxio.music.Date
|
||||
import java.io.File
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.music.parsing.parseId3v2Position
|
||||
import org.oxycblt.auxio.music.storage.Directory
|
||||
import org.oxycblt.auxio.music.storage.contentResolverSafe
|
||||
import org.oxycblt.auxio.music.storage.directoryCompat
|
||||
|
@ -38,6 +40,7 @@ import org.oxycblt.auxio.music.storage.useQuery
|
|||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.util.getSystemServiceCompat
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.nonZeroOrNull
|
||||
|
||||
/**
|
||||
* The layer that loads music from the [MediaStore] database. This is an intermediate step in the
|
||||
|
@ -302,7 +305,7 @@ abstract class MediaStoreExtractor(
|
|||
// MediaStore only exposes the year value of a file. This is actually worse than it
|
||||
// seems, as it means that it will not read ID3v2 TDRC tags or Vorbis DATE comments.
|
||||
// This is one of the major weaknesses of using MediaStore, hence the redundancy layers.
|
||||
raw.date = cursor.getIntOrNull(yearIndex)?.toDate()
|
||||
raw.date = cursor.getIntOrNull(yearIndex)?.let(Date::from)
|
||||
// A non-existent album name should theoretically be the name of the folder it contained
|
||||
// in, but in practice it is more often "0" (as in /storage/emulated/0), even when it the
|
||||
// file is not actually in the root internal storage directory. We can't do anything to
|
||||
|
@ -561,7 +564,24 @@ class Api30MediaStoreExtractor(context: Context, cacheExtractor: CacheExtractor)
|
|||
// the tag itself, which is to say that it is formatted as NN/TT tracks, where
|
||||
// N is the number and T is the total. Parse the number while ignoring the
|
||||
// total, as we have no use for it.
|
||||
cursor.getStringOrNull(trackIndex)?.parsePositionNum()?.let { raw.track = it }
|
||||
cursor.getStringOrNull(discIndex)?.parsePositionNum()?.let { raw.disc = it }
|
||||
cursor.getStringOrNull(trackIndex)?.parseId3v2Position()?.let { raw.track = it }
|
||||
cursor.getStringOrNull(discIndex)?.parseId3v2Position()?.let { raw.disc = it }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpack the track number from a combined track + disc [Int] field. These fields appear within
|
||||
* MediaStore's TRACK column, and combine the track and disc value into a single field where the
|
||||
* disc number is the 4th+ digit.
|
||||
* @return The track number extracted from the combined integer value, or null if the value was
|
||||
* zero.
|
||||
*/
|
||||
private fun Int.unpackTrackNo() = mod(1000).nonZeroOrNull()
|
||||
|
||||
/**
|
||||
* Unpack the disc number from a combined track + disc [Int] field. These fields appear within
|
||||
* MediaStore's TRACK column, and combine the track and disc value into a single field where the
|
||||
* disc number is the 4th+ digit.
|
||||
* @return The disc number extracted from the combined integer field, or null if the value was zero.
|
||||
*/
|
||||
private fun Int.unpackDiscNo() = div(1000).nonZeroOrNull()
|
|
@ -26,6 +26,8 @@ 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.parsing.correctWhitespace
|
||||
import org.oxycblt.auxio.music.parsing.parseId3v2Position
|
||||
import org.oxycblt.auxio.music.storage.toAudioUri
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.logW
|
||||
|
@ -128,7 +130,6 @@ class MetadataExtractor(
|
|||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class Task(context: Context, private val raw: Song.Raw) {
|
||||
// TODO: Unify with MetadataExtractor
|
||||
// Note that we do not leverage future callbacks. This is because errors in the
|
||||
// (highly fallible) extraction process will not bubble up to Indexer when a
|
||||
// listener is used, instead crashing the app entirely.
|
||||
|
@ -144,6 +145,7 @@ class Task(context: Context, private val raw: Song.Raw) {
|
|||
*/
|
||||
fun get(): Song.Raw? {
|
||||
if (!future.isDone) {
|
||||
// Not done yet, nothing to do.
|
||||
return null
|
||||
}
|
||||
|
||||
|
@ -227,10 +229,10 @@ class Task(context: Context, private val raw: Song.Raw) {
|
|||
textFrames["TSOT"]?.let { raw.sortName = it[0] }
|
||||
|
||||
// Track. Only parse out the track number and ignore the total tracks value.
|
||||
textFrames["TRCK"]?.run { get(0).parsePositionNum() }?.let { raw.track = it }
|
||||
textFrames["TRCK"]?.run { get(0).parseId3v2Position() }?.let { raw.track = it }
|
||||
|
||||
// Disc. Only parse out the disc number and ignore the total discs value.
|
||||
textFrames["TPOS"]?.run { get(0).parsePositionNum() }?.let { raw.disc = it }
|
||||
textFrames["TPOS"]?.run { get(0).parseId3v2Position() }?.let { raw.disc = it }
|
||||
|
||||
// Dates are somewhat complicated, as not only did their semantics change from a flat year
|
||||
// value in ID3v2.3 to a full ISO-8601 date in ID3v2.4, but there are also a variety of
|
||||
|
@ -241,9 +243,9 @@ class Task(context: Context, private val raw: Song.Raw) {
|
|||
// 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
|
||||
(textFrames["TDOR"]?.run { get(0).parseTimestamp() }
|
||||
?: textFrames["TDRC"]?.run { get(0).parseTimestamp() }
|
||||
?: textFrames["TDRL"]?.run { get(0).parseTimestamp() }
|
||||
(textFrames["TDOR"]?.run { Date.from(get(0)) }
|
||||
?: textFrames["TDRC"]?.run { Date.from(get(0)) }
|
||||
?: textFrames["TDRL"]?.run { Date.from(get(0)) }
|
||||
?: parseId3v23Date(textFrames))
|
||||
?.let { raw.date = it }
|
||||
|
||||
|
@ -335,9 +337,9 @@ class Task(context: Context, private val raw: Song.Raw) {
|
|||
// 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
|
||||
// date tag that android supports, so it must be 15 years old or more!)
|
||||
(comments["ORIGINALDATE"]?.run { get(0).parseTimestamp() }
|
||||
?: comments["DATE"]?.run { get(0).parseTimestamp() }
|
||||
?: comments["YEAR"]?.run { get(0).parseYear() })
|
||||
(comments["ORIGINALDATE"]?.run { Date.from(get(0)) }
|
||||
?: comments["DATE"]?.run { Date.from(get(0)) }
|
||||
?: comments["YEAR"]?.run { get(0).toIntOrNull()?.let(Date::from) })
|
||||
?.let { raw.date = it }
|
||||
|
||||
// Album
|
||||
|
|
|
@ -15,59 +15,27 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.music.extractor
|
||||
package org.oxycblt.auxio.music.parsing
|
||||
|
||||
import java.util.UUID
|
||||
import org.oxycblt.auxio.music.Date
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.util.nonZeroOrNull
|
||||
|
||||
/**
|
||||
* Unpack the track number from a combined track + disc [Int] field. These fields appear within
|
||||
* MediaStore's TRACK column, and combine the track and disc value into a single field where the
|
||||
* disc number is the 4th+ digit.
|
||||
* @return The track number extracted from the combined integer value, or null if the value was
|
||||
* zero.
|
||||
*/
|
||||
fun Int.unpackTrackNo() = mod(1000).nonZeroOrNull()
|
||||
/// --- GENERIC PARSING ---
|
||||
|
||||
/**
|
||||
* Unpack the disc number from a combined track + disc [Int] field. These fields appear within
|
||||
* MediaStore's TRACK column, and combine the track and disc value into a single field where the
|
||||
* disc number is the 4th+ digit.
|
||||
* @return The disc number extracted from the combined integer field, or null if the value was zero.
|
||||
* Parse a multi-value tag based on the user configuration. If the value is already composed of more
|
||||
* than one value, nothing is done. Otherwise, this function will attempt to split it based on the
|
||||
* user's separator preferences.
|
||||
* @param settings [Settings] required to obtain user separator configuration.
|
||||
* @return A new list of one or more [String]s.
|
||||
*/
|
||||
fun Int.unpackDiscNo() = div(1000).nonZeroOrNull()
|
||||
|
||||
/**
|
||||
* Parse the number out of a combined number + total position [String] field. These fields often
|
||||
* appear in ID3v2 files, and consist of a number and an (optional) total value delimited by a /.
|
||||
* @return The number value extracted from the string field, or null if the value could not be
|
||||
* parsed or if the value was zero.
|
||||
*/
|
||||
fun String.parsePositionNum() = split('/', limit = 2)[0].toIntOrNull()?.nonZeroOrNull()
|
||||
|
||||
/**
|
||||
* Transform an [Int] year field into a [Date].
|
||||
* @return A [Date] consisting of the year value, or null if the value was zero.
|
||||
* @see Date.from
|
||||
*/
|
||||
fun Int.toDate() = Date.from(this)
|
||||
|
||||
/**
|
||||
* Parse an integer year field from a [String] and transform it into a [Date].
|
||||
* @return A [Date] consisting of the year value, or null if the value could not be parsed or if the
|
||||
* value was zero.
|
||||
* @see Date.from
|
||||
*/
|
||||
fun String.parseYear() = toIntOrNull()?.toDate()
|
||||
|
||||
/**
|
||||
* Parse an ISO-8601 timestamp [String] into a [Date].
|
||||
* @return A [Date] consisting of the year value plus one or more refinement values (ex. month,
|
||||
* day), or null if the timestamp was not valid.
|
||||
*/
|
||||
fun String.parseTimestamp() = Date.from(this)
|
||||
fun List<String>.parseMultiValue(settings: Settings) =
|
||||
if (size == 1) {
|
||||
get(0).maybeParseBySeparators(settings)
|
||||
} else {
|
||||
// Nothing to do.
|
||||
this
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a [String] by the given selector, automatically handling escaped characters that satisfy
|
||||
|
@ -126,43 +94,26 @@ fun String.correctWhitespace() = trim().ifBlank { null }
|
|||
*/
|
||||
fun List<String>.correctWhitespace() = mapNotNull { it.correctWhitespace() }
|
||||
|
||||
/**
|
||||
* Parse a multi-value tag based on the user configuration. If the value is already composed of more
|
||||
* than one value, nothing is done. Otherwise, this function will attempt to split it based on the
|
||||
* user's separator preferences.
|
||||
* @param settings [Settings] required to obtain user separator configuration.
|
||||
* @return A new list of one or more [String]s.
|
||||
*/
|
||||
fun List<String>.parseMultiValue(settings: Settings) =
|
||||
if (size == 1) {
|
||||
get(0).maybeParseSeparators(settings)
|
||||
} else {
|
||||
// Nothing to do.
|
||||
this
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to parse a string by the user's separator preferences.
|
||||
* @param settings [Settings] required to obtain user separator configuration.
|
||||
* @return A list of one or more [String]s that were split up by the user-defined separators.
|
||||
*/
|
||||
fun String.maybeParseSeparators(settings: Settings): List<String> {
|
||||
private fun String.maybeParseBySeparators(settings: Settings): List<String> {
|
||||
// Get the separators the user desires. If null, there's nothing to do.
|
||||
val separators = settings.musicSeparators ?: return listOf(this)
|
||||
return splitEscaped { separators.contains(it) }.correctWhitespace()
|
||||
}
|
||||
|
||||
/// --- ID3v2 PARSING ---
|
||||
|
||||
/**
|
||||
* Convert a [String] to a [UUID].
|
||||
* @return A [UUID] converted from the [String] value, or null if the value was not valid.
|
||||
* @see UUID.fromString
|
||||
* Parse the number out of a ID3v2-style number + total position [String] field. These fields
|
||||
* consist of a number and an (optional) total value delimited by a /.
|
||||
* @return The number value extracted from the string field, or null if the value could not be
|
||||
* parsed or if the value was zero.
|
||||
*/
|
||||
fun String.toUuidOrNull(): UUID? =
|
||||
try {
|
||||
UUID.fromString(this)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
null
|
||||
}
|
||||
fun String.parseId3v2Position() = split('/', limit = 2)[0].toIntOrNull()?.nonZeroOrNull()
|
||||
|
||||
/**
|
||||
* Parse a multi-value genre name using ID3 rules. This will convert any ID3v1 integer
|
||||
|
@ -173,7 +124,7 @@ fun String.toUuidOrNull(): UUID? =
|
|||
*/
|
||||
fun List<String>.parseId3GenreNames(settings: Settings) =
|
||||
if (size == 1) {
|
||||
get(0).parseId3GenreNames(settings)
|
||||
get(0).parseId3MultiValueGenre(settings)
|
||||
} else {
|
||||
// Nothing to split, just map any ID3v1 genres to their name counterparts.
|
||||
map { it.parseId3v1Genre() ?: it }
|
||||
|
@ -183,8 +134,8 @@ fun List<String>.parseId3GenreNames(settings: Settings) =
|
|||
* Parse a single ID3v1/ID3v2 integer genre field into their named representations.
|
||||
* @return A list of one or more genre names.
|
||||
*/
|
||||
fun String.parseId3GenreNames(settings: Settings) =
|
||||
parseId3v1Genre()?.let { listOf(it) } ?: parseId3v2Genre() ?: maybeParseSeparators(settings)
|
||||
private fun String.parseId3MultiValueGenre(settings: Settings) =
|
||||
parseId3v1Genre()?.let { listOf(it) } ?: parseId3v2Genre() ?: maybeParseBySeparators(settings)
|
||||
|
||||
/**
|
||||
* Parse an ID3v1 integer genre field.
|
||||
|
@ -193,10 +144,10 @@ fun String.parseId3GenreNames(settings: Settings) =
|
|||
*/
|
||||
private fun String.parseId3v1Genre(): String? {
|
||||
// ID3v1 genres are a plain integer value without formatting, so in that case
|
||||
// try to index the genre table with such. If this fails, then try to compare it
|
||||
// to some other hard-coded values.
|
||||
// try to index the genre table with such.
|
||||
val numeric =
|
||||
toIntOrNull()
|
||||
// Not a numeric value, try some other fixed values.
|
||||
?: return when (this) {
|
||||
// CR and RX are not technically ID3v1, but are formatted similarly to a plain
|
||||
// number.
|
||||
|
@ -204,7 +155,6 @@ private fun String.parseId3v1Genre(): String? {
|
|||
"RX" -> "Remix"
|
||||
else -> null
|
||||
}
|
||||
|
||||
return GENRE_TABLE.getOrNull(numeric)
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package org.oxycblt.auxio.music.parsing
|
||||
|
||||
/**
|
||||
* Defines the allowed separator characters that can be used to delimit multi-value tags.
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
object Separators {
|
||||
const val COMMA = ','
|
||||
const val SEMICOLON = ';'
|
||||
const val SLASH = '/'
|
||||
const val PLUS = '+'
|
||||
const val AND = '&'
|
||||
}
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.music.extractor
|
||||
package org.oxycblt.auxio.music.parsing
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
|
@ -62,11 +62,11 @@ class SeparatorsDialog : ViewBindingDialogFragment<DialogSeparatorsBinding>() {
|
|||
?: Settings(requireContext()).musicSeparators)
|
||||
?.forEach {
|
||||
when (it) {
|
||||
SEPARATOR_COMMA -> binding.separatorComma.isChecked = true
|
||||
SEPARATOR_SEMICOLON -> binding.separatorSemicolon.isChecked = true
|
||||
SEPARATOR_SLASH -> binding.separatorSlash.isChecked = true
|
||||
SEPARATOR_PLUS -> binding.separatorPlus.isChecked = true
|
||||
SEPARATOR_AND -> binding.separatorAnd.isChecked = true
|
||||
Separators.COMMA -> binding.separatorComma.isChecked = true
|
||||
Separators.SEMICOLON -> binding.separatorSemicolon.isChecked = true
|
||||
Separators.SLASH -> binding.separatorSlash.isChecked = true
|
||||
Separators.PLUS -> binding.separatorPlus.isChecked = true
|
||||
Separators.AND -> binding.separatorAnd.isChecked = true
|
||||
else -> error("Unexpected separator in settings data")
|
||||
}
|
||||
}
|
||||
|
@ -84,21 +84,15 @@ class SeparatorsDialog : ViewBindingDialogFragment<DialogSeparatorsBinding>() {
|
|||
// of use a mapping that could feasibly drift from the actual layout.
|
||||
var separators = ""
|
||||
val binding = requireBinding()
|
||||
if (binding.separatorComma.isChecked) separators += SEPARATOR_COMMA
|
||||
if (binding.separatorSemicolon.isChecked) separators += SEPARATOR_SEMICOLON
|
||||
if (binding.separatorSlash.isChecked) separators += SEPARATOR_SLASH
|
||||
if (binding.separatorPlus.isChecked) separators += SEPARATOR_PLUS
|
||||
if (binding.separatorAnd.isChecked) separators += SEPARATOR_AND
|
||||
if (binding.separatorComma.isChecked) separators += Separators.COMMA
|
||||
if (binding.separatorSemicolon.isChecked) separators += Separators.SEMICOLON
|
||||
if (binding.separatorSlash.isChecked) separators += Separators.SLASH
|
||||
if (binding.separatorPlus.isChecked) separators += Separators.PLUS
|
||||
if (binding.separatorAnd.isChecked) separators += Separators.AND
|
||||
return separators
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val KEY_PENDING_SEPARATORS = BuildConfig.APPLICATION_ID + ".key.PENDING_SEPARATORS"
|
||||
// TODO: Move these to a more "Correct" location?
|
||||
const val SEPARATOR_COMMA = ','
|
||||
const val SEPARATOR_SEMICOLON = ';'
|
||||
const val SEPARATOR_SLASH = '/'
|
||||
const val SEPARATOR_PLUS = '+'
|
||||
const val SEPARATOR_AND = '&'
|
||||
const val KEY_PENDING_SEPARATORS = BuildConfig.APPLICATION_ID + ".key.PENDING_SEPARATORS"
|
||||
}
|
||||
}
|
|
@ -133,7 +133,7 @@ class PlaybackPanelFragment :
|
|||
// Launch the system equalizer app, if possible.
|
||||
val equalizerIntent =
|
||||
Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL)
|
||||
// Provide audio session ID so equalizer can show options for this app
|
||||
// Provide audio session ID so the equalizer can show options for this app
|
||||
// in particular.
|
||||
.putExtra(
|
||||
AudioEffect.EXTRA_AUDIO_SESSION, playbackModel.currentAudioSessionId)
|
||||
|
|
|
@ -104,46 +104,44 @@ class PreferenceFragment : PreferenceFragmentCompat() {
|
|||
}
|
||||
|
||||
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||
val context = requireContext()
|
||||
|
||||
// Hook generic preferences to their specified preferences
|
||||
// TODO: These seem like good things to put into a side navigation view, if I choose to
|
||||
// do one.
|
||||
when (preference.key) {
|
||||
context.getString(R.string.set_key_save_state) -> {
|
||||
getString(R.string.set_key_save_state) -> {
|
||||
playbackModel.savePlaybackState { saved ->
|
||||
// Use the nullable context, as we could try to show a toast when this
|
||||
// fragment is no longer attached.
|
||||
if (saved) {
|
||||
this.context?.showToast(R.string.lbl_state_saved)
|
||||
context?.showToast(R.string.lbl_state_saved)
|
||||
} else {
|
||||
this.context?.showToast(R.string.err_did_not_save)
|
||||
context?.showToast(R.string.err_did_not_save)
|
||||
}
|
||||
}
|
||||
}
|
||||
context.getString(R.string.set_key_wipe_state) -> {
|
||||
getString(R.string.set_key_wipe_state) -> {
|
||||
playbackModel.wipePlaybackState { wiped ->
|
||||
if (wiped) {
|
||||
// Use the nullable context, as we could try to show a toast when this
|
||||
// fragment is no longer attached.
|
||||
this.context?.showToast(R.string.lbl_state_wiped)
|
||||
context?.showToast(R.string.lbl_state_wiped)
|
||||
} else {
|
||||
this.context?.showToast(R.string.err_did_not_wipe)
|
||||
context?.showToast(R.string.err_did_not_wipe)
|
||||
}
|
||||
}
|
||||
}
|
||||
context.getString(R.string.set_key_restore_state) ->
|
||||
getString(R.string.set_key_restore_state) ->
|
||||
playbackModel.tryRestorePlaybackState { restored ->
|
||||
if (restored) {
|
||||
// Use the nullable context, as we could try to show a toast when this
|
||||
// fragment is no longer attached.
|
||||
this.context?.showToast(R.string.lbl_state_restored)
|
||||
context?.showToast(R.string.lbl_state_restored)
|
||||
} else {
|
||||
this.context?.showToast(R.string.err_did_not_restore)
|
||||
context?.showToast(R.string.err_did_not_restore)
|
||||
}
|
||||
}
|
||||
context.getString(R.string.set_key_reindex) -> musicModel.refresh()
|
||||
context.getString(R.string.set_key_rescan) -> musicModel.rescan()
|
||||
getString(R.string.set_key_reindex) -> musicModel.refresh()
|
||||
getString(R.string.set_key_rescan) -> musicModel.rescan()
|
||||
else -> return super.onPreferenceTreeClick(preference)
|
||||
}
|
||||
|
||||
|
@ -151,8 +149,7 @@ class PreferenceFragment : PreferenceFragmentCompat() {
|
|||
}
|
||||
|
||||
private fun setupPreference(preference: Preference) {
|
||||
val context = requireActivity()
|
||||
val settings = Settings(context)
|
||||
val settings = Settings(requireContext())
|
||||
|
||||
if (!preference.isVisible) {
|
||||
// Nothing to do.
|
||||
|
@ -165,30 +162,31 @@ class PreferenceFragment : PreferenceFragmentCompat() {
|
|||
}
|
||||
|
||||
when (preference.key) {
|
||||
context.getString(R.string.set_key_theme) -> {
|
||||
getString(R.string.set_key_theme) -> {
|
||||
preference.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _, value ->
|
||||
AppCompatDelegate.setDefaultNightMode(value as Int)
|
||||
true
|
||||
}
|
||||
}
|
||||
context.getString(R.string.set_key_accent) -> {
|
||||
preference.summary = context.getString(settings.accent.name)
|
||||
getString(R.string.set_key_accent) -> {
|
||||
preference.summary = getString(settings.accent.name)
|
||||
}
|
||||
context.getString(R.string.set_key_black_theme) -> {
|
||||
getString(R.string.set_key_black_theme) -> {
|
||||
preference.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _, _ ->
|
||||
if (context.isNight) {
|
||||
context.recreate()
|
||||
val activity = requireActivity()
|
||||
if (activity.isNight) {
|
||||
activity.recreate()
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
context.getString(R.string.set_key_cover_mode) -> {
|
||||
getString(R.string.set_key_cover_mode) -> {
|
||||
preference.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _, _ ->
|
||||
Coil.imageLoader(context).memoryCache?.clear()
|
||||
Coil.imageLoader(requireContext()).memoryCache?.clear()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ import org.oxycblt.auxio.util.coordinatorLayoutBehavior
|
|||
*
|
||||
* @author Hai Zhang, Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
open class AuxioAppBarLayout
|
||||
open class CoordinatorAppBarLayout
|
||||
@JvmOverloads
|
||||
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
||||
AppBarLayout(context, attrs, defStyleAttr) {
|
|
@ -9,7 +9,7 @@
|
|||
android:transitionGroup="true"
|
||||
tools:context=".settings.AboutFragment">
|
||||
|
||||
<org.oxycblt.auxio.ui.AuxioAppBarLayout
|
||||
<org.oxycblt.auxio.ui.CoordinatorAppBarLayout
|
||||
android:id="@+id/about_appbar"
|
||||
style="@style/Widget.Auxio.AppBarLayout"
|
||||
app:liftOnScroll="true">
|
||||
|
@ -21,7 +21,7 @@
|
|||
app:navigationIcon="@drawable/ic_back_24"
|
||||
app:title="@string/lbl_about" />
|
||||
|
||||
</org.oxycblt.auxio.ui.AuxioAppBarLayout>
|
||||
</org.oxycblt.auxio.ui.CoordinatorAppBarLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/about_contents"
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
android:background="?attr/colorSurface"
|
||||
android:transitionGroup="true">
|
||||
|
||||
<org.oxycblt.auxio.ui.AuxioAppBarLayout
|
||||
<org.oxycblt.auxio.ui.CoordinatorAppBarLayout
|
||||
android:id="@+id/home_appbar"
|
||||
style="@style/Widget.Auxio.AppBarLayout"
|
||||
android:fitsSystemWindows="true">
|
||||
|
@ -37,7 +37,7 @@
|
|||
app:tabGravity="start"
|
||||
app:tabMode="scrollable" />
|
||||
|
||||
</org.oxycblt.auxio.ui.AuxioAppBarLayout>
|
||||
</org.oxycblt.auxio.ui.CoordinatorAppBarLayout>
|
||||
|
||||
|
||||
<FrameLayout
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
android:background="?attr/colorSurface"
|
||||
android:transitionGroup="true">
|
||||
|
||||
<org.oxycblt.auxio.ui.AuxioAppBarLayout
|
||||
<org.oxycblt.auxio.ui.CoordinatorAppBarLayout
|
||||
style="@style/Widget.Auxio.AppBarLayout"
|
||||
app:liftOnScroll="true"
|
||||
app:liftOnScrollTargetViewId="@id/search_recycler">
|
||||
|
@ -51,7 +51,7 @@
|
|||
|
||||
</org.oxycblt.auxio.list.selection.SelectionToolbarOverlay>
|
||||
|
||||
</org.oxycblt.auxio.ui.AuxioAppBarLayout>
|
||||
</org.oxycblt.auxio.ui.CoordinatorAppBarLayout>
|
||||
|
||||
<org.oxycblt.auxio.list.recycler.AuxioRecyclerView
|
||||
android:id="@+id/search_recycler"
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
android:orientation="vertical"
|
||||
android:transitionGroup="true">
|
||||
|
||||
<org.oxycblt.auxio.ui.AuxioAppBarLayout
|
||||
<org.oxycblt.auxio.ui.CoordinatorAppBarLayout
|
||||
android:id="@+id/settings_appbar"
|
||||
style="@style/Widget.Auxio.AppBarLayout"
|
||||
android:clickable="true"
|
||||
|
@ -22,7 +22,7 @@
|
|||
app:navigationIcon="@drawable/ic_back_24"
|
||||
app:title="@string/set_title" />
|
||||
|
||||
</org.oxycblt.auxio.ui.AuxioAppBarLayout>
|
||||
</org.oxycblt.auxio.ui.CoordinatorAppBarLayout>
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/settings_list_fragment"
|
||||
|
|
|
@ -111,7 +111,7 @@
|
|||
tools:layout="@layout/dialog_music_dirs" />
|
||||
<dialog
|
||||
android:id="@+id/separators_dialog"
|
||||
android:name="org.oxycblt.auxio.music.extractor.SeparatorsDialog"
|
||||
android:name="org.oxycblt.auxio.music.parsing.SeparatorsDialog"
|
||||
android:label="music_dirs_dialog"
|
||||
tools:layout="@layout/dialog_separators" />
|
||||
|
||||
|
|
Loading…
Reference in a new issue