music: enable quality tags [#128]
Enable the ExoPlayer parser in the UI. Now that runtime rescanning is implemented, this feature can also be finally enabled. Resolves #128.
This commit is contained in:
parent
d6f166a3ee
commit
3bdf7b136e
11 changed files with 79 additions and 58 deletions
|
@ -3,6 +3,8 @@
|
||||||
## dev
|
## dev
|
||||||
|
|
||||||
#### What's New
|
#### What's New
|
||||||
|
- Added direct metadata parsing, allowing more correct metadata at the cost of longer loading times
|
||||||
|
- Auxio can now reload music without requiring a restart
|
||||||
- Added a shuffle shortcut
|
- Added a shuffle shortcut
|
||||||
- Widgets now have a more sleek and consistent button layout
|
- Widgets now have a more sleek and consistent button layout
|
||||||
- "Rounded album covers" is now "Round mode"
|
- "Rounded album covers" is now "Round mode"
|
||||||
|
|
|
@ -27,6 +27,8 @@ import org.oxycblt.auxio.BuildConfig
|
||||||
import org.oxycblt.auxio.music.backend.Api21MediaStoreBackend
|
import org.oxycblt.auxio.music.backend.Api21MediaStoreBackend
|
||||||
import org.oxycblt.auxio.music.backend.Api29MediaStoreBackend
|
import org.oxycblt.auxio.music.backend.Api29MediaStoreBackend
|
||||||
import org.oxycblt.auxio.music.backend.Api30MediaStoreBackend
|
import org.oxycblt.auxio.music.backend.Api30MediaStoreBackend
|
||||||
|
import org.oxycblt.auxio.music.backend.ExoPlayerBackend
|
||||||
|
import org.oxycblt.auxio.settings.Settings
|
||||||
import org.oxycblt.auxio.ui.Sort
|
import org.oxycblt.auxio.ui.Sort
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.logE
|
import org.oxycblt.auxio.util.logE
|
||||||
|
@ -80,7 +82,7 @@ class Indexer {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized(this) { this.controller = controller }
|
this.controller = controller
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Unregister a [Controller] with this instance. */
|
/** Unregister a [Controller] with this instance. */
|
||||||
|
@ -119,9 +121,8 @@ class Indexer {
|
||||||
this.callback = null
|
this.callback = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun index(context: Context) {
|
fun index(context: Context) {
|
||||||
val generation = ++currentGeneration
|
val generation = synchronized(this) { ++currentGeneration }
|
||||||
|
|
||||||
val notGranted =
|
val notGranted =
|
||||||
ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) ==
|
ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) ==
|
||||||
|
@ -227,7 +228,15 @@ class Indexer {
|
||||||
else -> Api21MediaStoreBackend()
|
else -> Api21MediaStoreBackend()
|
||||||
}
|
}
|
||||||
|
|
||||||
val songs = buildSongs(context, mediaStoreBackend, generation)
|
val settings = Settings(context)
|
||||||
|
val backend =
|
||||||
|
if (settings.useQualityTags) {
|
||||||
|
ExoPlayerBackend(mediaStoreBackend)
|
||||||
|
} else {
|
||||||
|
mediaStoreBackend
|
||||||
|
}
|
||||||
|
|
||||||
|
val songs = buildSongs(context, backend, generation)
|
||||||
if (songs.isEmpty()) {
|
if (songs.isEmpty()) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,10 +27,11 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.oxycblt.auxio.IntegerTable
|
import org.oxycblt.auxio.IntegerTable
|
||||||
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackStateDatabase
|
import org.oxycblt.auxio.playback.state.PlaybackStateDatabase
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||||
|
import org.oxycblt.auxio.settings.Settings
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,8 +44,14 @@ import org.oxycblt.auxio.util.logD
|
||||||
* boilerplate you skip is not worth the insanity of androidx.
|
* boilerplate you skip is not worth the insanity of androidx.
|
||||||
*
|
*
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
|
*
|
||||||
|
* TODO: Add file observing
|
||||||
|
*
|
||||||
|
* TODO: Audit usages of synchronized
|
||||||
|
*
|
||||||
|
* TODO: Rework UI flow once again
|
||||||
*/
|
*/
|
||||||
class IndexerService : Service(), Indexer.Controller {
|
class IndexerService : Service(), Indexer.Controller, Settings.Callback {
|
||||||
private val indexer = Indexer.getInstance()
|
private val indexer = Indexer.getInstance()
|
||||||
private val musicStore = MusicStore.getInstance()
|
private val musicStore = MusicStore.getInstance()
|
||||||
|
|
||||||
|
@ -53,6 +60,7 @@ class IndexerService : Service(), Indexer.Controller {
|
||||||
private val updateScope = CoroutineScope(serviceJob + Dispatchers.Main)
|
private val updateScope = CoroutineScope(serviceJob + Dispatchers.Main)
|
||||||
|
|
||||||
private val playbackManager = PlaybackStateManager.getInstance()
|
private val playbackManager = PlaybackStateManager.getInstance()
|
||||||
|
private lateinit var settings: Settings
|
||||||
|
|
||||||
private var isForeground = false
|
private var isForeground = false
|
||||||
private lateinit var notification: IndexerNotification
|
private lateinit var notification: IndexerNotification
|
||||||
|
@ -61,6 +69,7 @@ class IndexerService : Service(), Indexer.Controller {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
notification = IndexerNotification(this)
|
notification = IndexerNotification(this)
|
||||||
|
settings = Settings(this, this)
|
||||||
|
|
||||||
indexer.registerController(this)
|
indexer.registerController(this)
|
||||||
if (musicStore.library == null && indexer.isIndeterminate) {
|
if (musicStore.library == null && indexer.isIndeterminate) {
|
||||||
|
@ -83,12 +92,14 @@ class IndexerService : Service(), Indexer.Controller {
|
||||||
indexer.cancelLast()
|
indexer.cancelLast()
|
||||||
indexer.unregisterController(this)
|
indexer.unregisterController(this)
|
||||||
serviceJob.cancel()
|
serviceJob.cancel()
|
||||||
|
settings.release()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- CONTROLLER CALLBACKS ---
|
||||||
|
|
||||||
override fun onStartIndexing() {
|
override fun onStartIndexing() {
|
||||||
if (indexer.isIndexing) {
|
if (indexer.isIndexing) {
|
||||||
indexer.cancelLast()
|
indexer.cancelLast()
|
||||||
indexScope.cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
indexScope.launch { indexer.index(this@IndexerService) }
|
indexScope.launch { indexer.index(this@IndexerService) }
|
||||||
|
@ -103,9 +114,8 @@ class IndexerService : Service(), Indexer.Controller {
|
||||||
|
|
||||||
val newLibrary = state.response.library
|
val newLibrary = state.response.library
|
||||||
|
|
||||||
// Load was completed successfully, so apply the new library if we
|
// Load was completed successfully. However, we still need to do some
|
||||||
// have not already. Only when we are done updating the library will
|
// extra work to update the app's state.
|
||||||
// the service stop it's foreground state.
|
|
||||||
updateScope.launch {
|
updateScope.launch {
|
||||||
imageLoader.memoryCache?.clear()
|
imageLoader.memoryCache?.clear()
|
||||||
|
|
||||||
|
@ -116,7 +126,7 @@ class IndexerService : Service(), Indexer.Controller {
|
||||||
PlaybackStateDatabase.getInstance(this@IndexerService), newLibrary)
|
PlaybackStateDatabase.getInstance(this@IndexerService), newLibrary)
|
||||||
}
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.Main) { musicStore.updateLibrary(newLibrary) }
|
musicStore.updateLibrary(newLibrary)
|
||||||
|
|
||||||
stopForegroundSession()
|
stopForegroundSession()
|
||||||
}
|
}
|
||||||
|
@ -152,6 +162,16 @@ class IndexerService : Service(), Indexer.Controller {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- SETTING CALLBACKS ---
|
||||||
|
|
||||||
|
override fun onSettingChanged(key: String) {
|
||||||
|
if (key == getString(R.string.set_key_music_dirs) ||
|
||||||
|
key == getString(R.string.set_key_music_dirs_include) ||
|
||||||
|
key == getString(R.string.set_key_quality_tags)) {
|
||||||
|
onStartIndexing()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun stopForegroundSession() {
|
private fun stopForegroundSession() {
|
||||||
if (isForeground) {
|
if (isForeground) {
|
||||||
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
|
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
|
||||||
|
|
|
@ -26,7 +26,6 @@ import org.oxycblt.auxio.util.contentResolverSafe
|
||||||
* The main storage for music items. The items themselves are located in a [Library], however this
|
* The main storage for music items. The items themselves are located in a [Library], however this
|
||||||
* might not be available at all times.
|
* might not be available at all times.
|
||||||
*
|
*
|
||||||
* TODO: Add automatic rescanning [major change]
|
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
class MusicStore private constructor() {
|
class MusicStore private constructor() {
|
||||||
|
|
|
@ -184,7 +184,7 @@ class Task(context: Context, private val audio: MediaStoreBackend.Audio) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is VorbisComment -> {
|
is VorbisComment -> {
|
||||||
val id = tag.value.sanitize()
|
val id = tag.key.sanitize()
|
||||||
val value = tag.value.sanitize()
|
val value = tag.value.sanitize()
|
||||||
if (value.isNotEmpty()) {
|
if (value.isNotEmpty()) {
|
||||||
vorbisTags[id] = value
|
vorbisTags[id] = value
|
||||||
|
@ -244,6 +244,7 @@ class Task(context: Context, private val audio: MediaStoreBackend.Audio) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun populateVorbis(tags: Map<String, String>) {
|
private fun populateVorbis(tags: Map<String, String>) {
|
||||||
|
logD(tags)
|
||||||
// Title
|
// Title
|
||||||
tags["TITLE"]?.let { audio.title = it }
|
tags["TITLE"]?.let { audio.title = it }
|
||||||
|
|
||||||
|
@ -266,7 +267,7 @@ class Task(context: Context, private val audio: MediaStoreBackend.Audio) {
|
||||||
tags["ALBUM"]?.let { audio.album = it }
|
tags["ALBUM"]?.let { audio.album = it }
|
||||||
|
|
||||||
// Artist
|
// Artist
|
||||||
tags["ARTIST"]?.let { audio.title }
|
tags["ARTIST"]?.let { audio.artist = it }
|
||||||
|
|
||||||
// Album artist. This actually comes into two flavors:
|
// Album artist. This actually comes into two flavors:
|
||||||
// 1. ALBUMARTIST, which is the most common
|
// 1. ALBUMARTIST, which is the most common
|
||||||
|
|
|
@ -25,16 +25,12 @@ import android.view.LayoutInflater
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.activityViewModels
|
|
||||||
import org.oxycblt.auxio.BuildConfig
|
import org.oxycblt.auxio.BuildConfig
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.DialogMusicDirsBinding
|
import org.oxycblt.auxio.databinding.DialogMusicDirsBinding
|
||||||
import org.oxycblt.auxio.music.Directory
|
import org.oxycblt.auxio.music.Directory
|
||||||
import org.oxycblt.auxio.music.IndexerViewModel
|
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
|
||||||
import org.oxycblt.auxio.settings.Settings
|
import org.oxycblt.auxio.settings.Settings
|
||||||
import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment
|
import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment
|
||||||
import org.oxycblt.auxio.util.androidActivityViewModels
|
|
||||||
import org.oxycblt.auxio.util.context
|
import org.oxycblt.auxio.util.context
|
||||||
import org.oxycblt.auxio.util.getSystemServiceSafe
|
import org.oxycblt.auxio.util.getSystemServiceSafe
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
@ -46,12 +42,11 @@ import org.oxycblt.auxio.util.showToast
|
||||||
*/
|
*/
|
||||||
class MusicDirsDialog :
|
class MusicDirsDialog :
|
||||||
ViewBindingDialogFragment<DialogMusicDirsBinding>(), MusicDirAdapter.Listener {
|
ViewBindingDialogFragment<DialogMusicDirsBinding>(), MusicDirAdapter.Listener {
|
||||||
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
|
|
||||||
private val indexerModel: IndexerViewModel by activityViewModels()
|
|
||||||
|
|
||||||
private val dirAdapter = MusicDirAdapter(this)
|
private val dirAdapter = MusicDirAdapter(this)
|
||||||
private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) }
|
private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) }
|
||||||
private var storageManager: StorageManager? = null
|
private val storageManager: StorageManager by lifecycleObject { binding ->
|
||||||
|
binding.context.getSystemServiceSafe(StorageManager::class)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateBinding(inflater: LayoutInflater) =
|
override fun onCreateBinding(inflater: LayoutInflater) =
|
||||||
DialogMusicDirsBinding.inflate(inflater)
|
DialogMusicDirsBinding.inflate(inflater)
|
||||||
|
@ -61,7 +56,17 @@ class MusicDirsDialog :
|
||||||
builder
|
builder
|
||||||
.setTitle(R.string.set_dirs)
|
.setTitle(R.string.set_dirs)
|
||||||
.setNeutralButton(R.string.lbl_add, null)
|
.setNeutralButton(R.string.lbl_add, null)
|
||||||
.setPositiveButton(R.string.lbl_save, null)
|
.setPositiveButton(R.string.lbl_save) { _, _ ->
|
||||||
|
val dirs = settings.getMusicDirs(storageManager)
|
||||||
|
val newDirs =
|
||||||
|
MusicDirs(
|
||||||
|
dirs = dirAdapter.data.currentList,
|
||||||
|
shouldInclude = isInclude(requireBinding()))
|
||||||
|
if (dirs != newDirs) {
|
||||||
|
logD("Committing changes")
|
||||||
|
settings.setMusicDirs(newDirs)
|
||||||
|
}
|
||||||
|
}
|
||||||
.setNegativeButton(R.string.lbl_cancel, null)
|
.setNegativeButton(R.string.lbl_cancel, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,19 +85,6 @@ class MusicDirsDialog :
|
||||||
logD("Opening launcher")
|
logD("Opening launcher")
|
||||||
launcher.launch(null)
|
launcher.launch(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.setOnClickListener {
|
|
||||||
val dirs = settings.getMusicDirs(requireStorageManager())
|
|
||||||
|
|
||||||
if (dirs.dirs != dirAdapter.data.currentList ||
|
|
||||||
dirs.shouldInclude != isInclude(requireBinding())) {
|
|
||||||
logD("Committing changes")
|
|
||||||
saveAndDismiss()
|
|
||||||
} else {
|
|
||||||
logD("Dropping changes")
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.dirsRecycler.apply {
|
binding.dirsRecycler.apply {
|
||||||
|
@ -100,7 +92,6 @@ class MusicDirsDialog :
|
||||||
itemAnimator = null
|
itemAnimator = null
|
||||||
}
|
}
|
||||||
|
|
||||||
val storageManager = requireStorageManager()
|
|
||||||
var dirs = settings.getMusicDirs(storageManager)
|
var dirs = settings.getMusicDirs(storageManager)
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
|
@ -173,7 +164,7 @@ class MusicDirsDialog :
|
||||||
val treeUri = DocumentsContract.getTreeDocumentId(docUri)
|
val treeUri = DocumentsContract.getTreeDocumentId(docUri)
|
||||||
|
|
||||||
// Parsing handles the rest
|
// Parsing handles the rest
|
||||||
return Directory.fromDocumentUri(requireStorageManager(), treeUri)
|
return Directory.fromDocumentUri(storageManager, treeUri)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateMode() {
|
private fun updateMode() {
|
||||||
|
@ -188,22 +179,6 @@ class MusicDirsDialog :
|
||||||
private fun isInclude(binding: DialogMusicDirsBinding) =
|
private fun isInclude(binding: DialogMusicDirsBinding) =
|
||||||
binding.folderModeGroup.checkedButtonId == R.id.dirs_mode_include
|
binding.folderModeGroup.checkedButtonId == R.id.dirs_mode_include
|
||||||
|
|
||||||
private fun saveAndDismiss() {
|
|
||||||
settings.setMusicDirs(MusicDirs(dirAdapter.data.currentList, isInclude(requireBinding())))
|
|
||||||
indexerModel.reindex()
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun requireStorageManager(): StorageManager {
|
|
||||||
val mgr = storageManager
|
|
||||||
if (mgr != null) {
|
|
||||||
return mgr
|
|
||||||
}
|
|
||||||
val newMgr = requireContext().getSystemServiceSafe(StorageManager::class)
|
|
||||||
storageManager = newMgr
|
|
||||||
return newMgr
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = BuildConfig.APPLICATION_ID + ".tag.EXCLUDED"
|
const val TAG = BuildConfig.APPLICATION_ID + ".tag.EXCLUDED"
|
||||||
const val KEY_PENDING_DIRS = BuildConfig.APPLICATION_ID + ".key.PENDING_DIRS"
|
const val KEY_PENDING_DIRS = BuildConfig.APPLICATION_ID + ".key.PENDING_DIRS"
|
||||||
|
|
|
@ -185,6 +185,10 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
||||||
val pauseOnRepeat: Boolean
|
val pauseOnRepeat: Boolean
|
||||||
get() = inner.getBoolean(context.getString(R.string.set_key_repeat_pause), false)
|
get() = inner.getBoolean(context.getString(R.string.set_key_repeat_pause), false)
|
||||||
|
|
||||||
|
/** Whether to parse metadata directly with ExoPlayer. */
|
||||||
|
val useQualityTags: Boolean
|
||||||
|
get() = inner.getBoolean(context.getString(R.string.set_key_quality_tags), false)
|
||||||
|
|
||||||
/** Get the list of directories that music should be hidden/loaded from. */
|
/** Get the list of directories that music should be hidden/loaded from. */
|
||||||
fun getMusicDirs(storageManager: StorageManager): MusicDirs {
|
fun getMusicDirs(storageManager: StorageManager): MusicDirs {
|
||||||
val key = context.getString(R.string.set_key_music_dirs)
|
val key = context.getString(R.string.set_key_music_dirs)
|
||||||
|
@ -214,9 +218,7 @@ class Settings(private val context: Context, private val callback: Callback? = n
|
||||||
musicDirs.dirs.map(Directory::toDocumentUri).toSet())
|
musicDirs.dirs.map(Directory::toDocumentUri).toSet())
|
||||||
putBoolean(
|
putBoolean(
|
||||||
context.getString(R.string.set_key_music_dirs_include), musicDirs.shouldInclude)
|
context.getString(R.string.set_key_music_dirs_include), musicDirs.shouldInclude)
|
||||||
|
apply()
|
||||||
// TODO: This is a stopgap measure before automatic rescanning, remove
|
|
||||||
commit()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,8 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||||
* extendable. You have been warned.
|
* extendable. You have been warned.
|
||||||
*
|
*
|
||||||
* @author OxygenCobalt (With help from Umano and Hai Zhang)
|
* @author OxygenCobalt (With help from Umano and Hai Zhang)
|
||||||
|
*
|
||||||
|
* FIXME: Inconsistent padding across recreates
|
||||||
*/
|
*/
|
||||||
class BottomSheetLayout
|
class BottomSheetLayout
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
<string name="set_key_reindex" translatable="false">auxio_reindex</string>
|
<string name="set_key_reindex" translatable="false">auxio_reindex</string>
|
||||||
<string name="set_key_music_dirs" translatable="false">auxio_music_dirs</string>
|
<string name="set_key_music_dirs" translatable="false">auxio_music_dirs</string>
|
||||||
<string name="set_key_music_dirs_include" translatable="false">auxio_include_dirs</string>
|
<string name="set_key_music_dirs_include" translatable="false">auxio_include_dirs</string>
|
||||||
|
<string name="set_key_quality_tags">auxio_quality_tags</string>
|
||||||
|
|
||||||
<string name="set_key_search_filter" translatable="false">KEY_SEARCH_FILTER</string>
|
<string name="set_key_search_filter" translatable="false">KEY_SEARCH_FILTER</string>
|
||||||
|
|
||||||
|
|
|
@ -132,7 +132,7 @@
|
||||||
<string name="set_save">Save playback state</string>
|
<string name="set_save">Save playback state</string>
|
||||||
<string name="set_save_desc">Save the current playback state now</string>
|
<string name="set_save_desc">Save the current playback state now</string>
|
||||||
<string name="set_reindex">Reload music</string>
|
<string name="set_reindex">Reload music</string>
|
||||||
<string name="set_reindex_desc">Will restart app</string>
|
<string name="set_reindex_desc">May wipe playback state</string>
|
||||||
<string name="set_dirs">Music folders</string>
|
<string name="set_dirs">Music folders</string>
|
||||||
<string name="set_dirs_desc">Manage where music should be loaded from</string>
|
<string name="set_dirs_desc">Manage where music should be loaded from</string>
|
||||||
<string name="set_dirs_mode">Mode</string>
|
<string name="set_dirs_mode">Mode</string>
|
||||||
|
@ -140,6 +140,8 @@
|
||||||
<string name="set_dirs_mode_exclude_desc">Music will <b>not</b> be loaded from the folders you add.</string>
|
<string name="set_dirs_mode_exclude_desc">Music will <b>not</b> be loaded from the folders you add.</string>
|
||||||
<string name="set_dirs_mode_include">Include</string>
|
<string name="set_dirs_mode_include">Include</string>
|
||||||
<string name="set_dirs_mode_include_desc">Music will <b>only</b> be loaded from the folders you add.</string>
|
<string name="set_dirs_mode_include_desc">Music will <b>only</b> be loaded from the folders you add.</string>
|
||||||
|
<string name="set_quality_tags">Ignore MediaStore tags</string>
|
||||||
|
<string name="set_quality_tags_desc">Increases tag quality, but requires longer loading times</string>
|
||||||
|
|
||||||
<!-- Error Namespace | Error Labels -->
|
<!-- Error Namespace | Error Labels -->
|
||||||
<string name="err_no_music">No music found</string>
|
<string name="err_no_music">No music found</string>
|
||||||
|
|
|
@ -173,5 +173,13 @@
|
||||||
app:summary="@string/set_dirs_desc"
|
app:summary="@string/set_dirs_desc"
|
||||||
app:title="@string/set_dirs" />
|
app:title="@string/set_dirs" />
|
||||||
|
|
||||||
|
<org.oxycblt.auxio.settings.ui.M3SwitchPreference
|
||||||
|
app:allowDividerBelow="false"
|
||||||
|
app:defaultValue="false"
|
||||||
|
app:iconSpaceReserved="false"
|
||||||
|
app:key="@string/set_key_quality_tags"
|
||||||
|
app:summary="@string/set_quality_tags_desc"
|
||||||
|
app:title="@string/set_quality_tags" />
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
Loading…
Reference in a new issue