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:
Alexander Capehart 2022-12-31 19:50:54 -07:00
parent dc46c49f07
commit 7721e64096
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
20 changed files with 143 additions and 155 deletions

View file

@ -116,7 +116,7 @@
<!-- </intent-filter>-->
<!-- </receiver>-->
<!-- "Now Playing" widget.. -->
<!-- "Now Playing" widget. -->
<receiver
android:name=".widgets.WidgetProvider"
android:exported="false"

View file

@ -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 {

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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)

View file

@ -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()

View file

@ -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

View file

@ -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)
}

View file

@ -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 = '&'
}

View file

@ -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"
}
}

View file

@ -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)

View file

@ -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
}
}

View file

@ -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) {

View file

@ -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"

View file

@ -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

View file

@ -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"

View file

@ -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"

View file

@ -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" />