music: revamp excluded into music dirs
Completely rework the excluded directory system into a new "Music Folders" system. This is implemented alongside a new "Include" mode. This mode allows the user to restrict music indexing to a parsicular folder. I've been reluctant to add this feature, as having two separate options seemed bad. This resolves it by effectively packing whether to include/exclude directories into a single option. Resolves #154.
This commit is contained in:
parent
53f3d0faef
commit
9b13b4c94e
28 changed files with 351 additions and 230 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -3,10 +3,12 @@
|
|||
## dev [v2.3.2, v2.4.0, or v3.0.0]
|
||||
|
||||
#### What's New
|
||||
- Excluded directories has been revampled into "Music folders"
|
||||
- Folders on external drives can now be excluded on Android Q+ [#134]
|
||||
- Added new "Include" option to restrict indexing to a particular folder [#154]
|
||||
- Added a new view for song properties (Such as Bitrate)
|
||||
- Folders on external drives can now be excluded on Android Q+ [#134]
|
||||
- The playback bar now has a new design, with an improved progress
|
||||
indicator and a skip action
|
||||
- The playback bar now has a new design, with an improved progress indicator and a
|
||||
skip action
|
||||
- When playing, covers now shows an animated indicator
|
||||
|
||||
#### What's Improved
|
||||
|
@ -14,6 +16,7 @@ indicator and a skip action
|
|||
- The toolbar layout is now consistent with Material Design 3
|
||||
- Genre parsing now handles multiple integer values and cover/remix indicators (May wipe playback state)
|
||||
- "Rounded album covers" option is no longer dependent on "Show album covers" option
|
||||
- Added song actions to the playback panel
|
||||
|
||||
#### What's Fixed
|
||||
- Playback bar now picks the larger inset in case that gesture inset is missing [#149]
|
||||
|
@ -25,6 +28,7 @@ indicator and a skip action
|
|||
- Moved music loading to a foreground service
|
||||
- Phased out `ImageButton` for `MaterialButton`
|
||||
- Unified icon sizing
|
||||
- Added original date support to ExoPlayer parser (Not exposed in app)
|
||||
|
||||
## v2.3.1
|
||||
|
||||
|
|
|
@ -70,6 +70,7 @@ sealed class Dir {
|
|||
/**
|
||||
* Represents a mime type as it is loaded by Auxio. [fromExtension] is based on the file extension
|
||||
* should always exist, while [fromFormat] is based on the file itself and may not be available.
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
data class MimeType(val fromExtension: String, val fromFormat: String?) {
|
||||
fun resolveName(context: Context): String {
|
||||
|
@ -86,18 +87,26 @@ data class MimeType(val fromExtension: String, val fromFormat: String?) {
|
|||
// We have special names for the most common formats.
|
||||
val readableStringRes =
|
||||
when (readableMime) {
|
||||
// Classic formats
|
||||
// MPEG formats
|
||||
// While MP4 is AAC, it's considered separate given how common it is.
|
||||
"audio/mpeg",
|
||||
"audio/mp3" -> R.string.cdc_mp3
|
||||
"audio/mp4",
|
||||
"audio/mp4a-latm",
|
||||
"audio/mpeg4-generic" -> R.string.cdc_mp4
|
||||
|
||||
// Free formats
|
||||
// Generic Ogg is included here as it's actually formatted as "Ogg", not "OGG"
|
||||
"audio/ogg",
|
||||
"application/ogg" -> R.string.cdc_ogg
|
||||
"audio/vorbis" -> R.string.cdc_ogg_vorbis
|
||||
"audio/opus" -> R.string.cdc_ogg_opus
|
||||
"audio/flac" -> R.string.cdc_flac
|
||||
|
||||
// MP4, 3GPP, M4A, etc. are all based on AAC
|
||||
"audio/mp4",
|
||||
"audio/mp4a-latm",
|
||||
"audio/mpeg4-generic",
|
||||
// The other AAC containers have a generic name
|
||||
"audio/aac",
|
||||
"audio/aacp",
|
||||
"audio/aac-adts",
|
||||
"audio/3gpp",
|
||||
"audio/3gpp2", -> R.string.cdc_aac
|
||||
|
||||
|
@ -107,6 +116,8 @@ data class MimeType(val fromExtension: String, val fromFormat: String?) {
|
|||
"audio/wave",
|
||||
"audio/vnd.wave" -> R.string.cdc_wav
|
||||
"audio/x-ms-wma" -> R.string.cdc_wma
|
||||
|
||||
// Don't know
|
||||
else -> -1
|
||||
}
|
||||
|
|
@ -203,6 +203,8 @@ class Task(context: Context, private val audio: MediaStoreBackend.Audio) {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Release types
|
||||
|
||||
private fun populateId3v2(tags: Map<String, String>) {
|
||||
// Title
|
||||
tags["TIT2"]?.let { audio.title = it }
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.oxycblt.auxio.music.Path
|
|||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.music.albumCoverUri
|
||||
import org.oxycblt.auxio.music.audioUri
|
||||
import org.oxycblt.auxio.music.dirs.MusicDirs
|
||||
import org.oxycblt.auxio.music.id3GenreName
|
||||
import org.oxycblt.auxio.music.no
|
||||
import org.oxycblt.auxio.music.queryCursor
|
||||
|
@ -122,7 +123,7 @@ abstract class MediaStoreBackend : Indexer.Backend {
|
|||
|
||||
override fun query(context: Context): Cursor {
|
||||
val settingsManager = SettingsManager.getInstance()
|
||||
val selector = buildExcludedSelector(settingsManager.excludedDirs)
|
||||
val selector = buildMusicDirsSelector(settingsManager.musicDirs)
|
||||
|
||||
return requireNotNull(
|
||||
context.contentResolverSafe.queryCursor(
|
||||
|
@ -187,7 +188,7 @@ abstract class MediaStoreBackend : Indexer.Backend {
|
|||
open val projection: Array<String>
|
||||
get() = BASE_PROJECTION
|
||||
|
||||
abstract fun buildExcludedSelector(dirs: List<Dir.Relative>): Selector
|
||||
abstract fun buildMusicDirsSelector(dirs: MusicDirs): Selector
|
||||
|
||||
/**
|
||||
* Build an [Audio] based on the current cursor values. Each implementation should try to obtain
|
||||
|
@ -367,19 +368,25 @@ open class Api21MediaStoreBackend : MediaStoreBackend() {
|
|||
super.projection +
|
||||
arrayOf(MediaStore.Audio.AudioColumns.TRACK, MediaStore.Audio.AudioColumns.DATA)
|
||||
|
||||
override fun buildExcludedSelector(dirs: List<Dir.Relative>): Selector {
|
||||
override fun buildMusicDirsSelector(dirs: MusicDirs): Selector {
|
||||
val base = Environment.getExternalStorageDirectory().absolutePath
|
||||
var selector = BASE_SELECTOR
|
||||
val args = mutableListOf<String>()
|
||||
|
||||
// Apply the excluded directories by filtering out specific DATA values.
|
||||
for (dir in dirs) {
|
||||
// Apply directories by filtering out specific DATA values.
|
||||
for (dir in dirs.dirs) {
|
||||
if (dir.volume is Dir.Volume.Secondary) {
|
||||
logW("Cannot exclude directories on secondary drives")
|
||||
continue
|
||||
// Should never happen.
|
||||
throw IllegalStateException()
|
||||
}
|
||||
|
||||
selector += " AND ${MediaStore.Audio.Media.DATA} NOT LIKE ?"
|
||||
selector +=
|
||||
if (dirs.shouldInclude) {
|
||||
" AND ${MediaStore.Audio.Media.DATA} LIKE ?"
|
||||
} else {
|
||||
" AND ${MediaStore.Audio.Media.DATA} NOT LIKE ?"
|
||||
}
|
||||
|
||||
args += "${base}/${dir.relativePath}%"
|
||||
}
|
||||
|
||||
|
@ -442,17 +449,22 @@ open class Api29MediaStoreBackend : Api21MediaStoreBackend() {
|
|||
MediaStore.Audio.AudioColumns.VOLUME_NAME,
|
||||
MediaStore.Audio.AudioColumns.RELATIVE_PATH)
|
||||
|
||||
override fun buildExcludedSelector(dirs: List<Dir.Relative>): Selector {
|
||||
override fun buildMusicDirsSelector(dirs: MusicDirs): Selector {
|
||||
var selector = BASE_SELECTOR
|
||||
val args = mutableListOf<String>()
|
||||
|
||||
// Starting in Android Q, we finally have access to the volume name. This allows
|
||||
// use to properly exclude folders on secondary devices such as SD cards.
|
||||
|
||||
for (dir in dirs) {
|
||||
for (dir in dirs.dirs) {
|
||||
selector +=
|
||||
" AND NOT (${MediaStore.Audio.AudioColumns.VOLUME_NAME} LIKE ? " +
|
||||
"AND ${MediaStore.Audio.AudioColumns.RELATIVE_PATH} LIKE ?)"
|
||||
if (dirs.shouldInclude) {
|
||||
" AND (${MediaStore.Audio.AudioColumns.VOLUME_NAME} LIKE ? " +
|
||||
"AND ${MediaStore.Audio.AudioColumns.RELATIVE_PATH} LIKE ?)"
|
||||
} else {
|
||||
" AND NOT (${MediaStore.Audio.AudioColumns.VOLUME_NAME} LIKE ? " +
|
||||
"AND ${MediaStore.Audio.AudioColumns.RELATIVE_PATH} LIKE ?)"
|
||||
}
|
||||
|
||||
// Assume that volume names are always lowercase counterparts to the volume
|
||||
// name stored in-app. I have no idea how well this holds up on other devices.
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.music.excluded
|
||||
package org.oxycblt.auxio.music.dirs
|
||||
|
||||
import android.content.Context
|
||||
import org.oxycblt.auxio.databinding.ItemExcludedDirBinding
|
||||
import org.oxycblt.auxio.databinding.ItemMusicDirBinding
|
||||
import org.oxycblt.auxio.music.Dir
|
||||
import org.oxycblt.auxio.ui.BackingData
|
||||
import org.oxycblt.auxio.ui.BindingViewHolder
|
||||
|
@ -31,16 +31,16 @@ import org.oxycblt.auxio.util.textSafe
|
|||
* Adapter that shows the excluded directories and their "Clear" button.
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class ExcludedAdapter(listener: Listener) :
|
||||
MonoAdapter<Dir.Relative, ExcludedAdapter.Listener, ExcludedViewHolder>(listener) {
|
||||
class MusicDirAdapter(listener: Listener) :
|
||||
MonoAdapter<Dir.Relative, MusicDirAdapter.Listener, MusicDirViewHolder>(listener) {
|
||||
override val data = ExcludedBackingData(this)
|
||||
override val creator = ExcludedViewHolder.CREATOR
|
||||
override val creator = MusicDirViewHolder.CREATOR
|
||||
|
||||
interface Listener {
|
||||
fun onRemoveDirectory(dir: Dir.Relative)
|
||||
}
|
||||
|
||||
class ExcludedBackingData(private val adapter: ExcludedAdapter) : BackingData<Dir.Relative>() {
|
||||
class ExcludedBackingData(private val adapter: MusicDirAdapter) : BackingData<Dir.Relative>() {
|
||||
private val _currentList = mutableListOf<Dir.Relative>()
|
||||
val currentList: List<Dir.Relative> = _currentList
|
||||
|
||||
|
@ -70,22 +70,22 @@ class ExcludedAdapter(listener: Listener) :
|
|||
}
|
||||
}
|
||||
|
||||
/** The viewholder for [ExcludedAdapter]. Not intended for use in other adapters. */
|
||||
class ExcludedViewHolder private constructor(private val binding: ItemExcludedDirBinding) :
|
||||
BindingViewHolder<Dir.Relative, ExcludedAdapter.Listener>(binding.root) {
|
||||
override fun bind(item: Dir.Relative, listener: ExcludedAdapter.Listener) {
|
||||
binding.excludedPath.textSafe = item.resolveName(binding.context)
|
||||
binding.excludedClear.setOnClickListener { listener.onRemoveDirectory(item) }
|
||||
/** The viewholder for [MusicDirAdapter]. Not intended for use in other adapters. */
|
||||
class MusicDirViewHolder private constructor(private val binding: ItemMusicDirBinding) :
|
||||
BindingViewHolder<Dir.Relative, MusicDirAdapter.Listener>(binding.root) {
|
||||
override fun bind(item: Dir.Relative, listener: MusicDirAdapter.Listener) {
|
||||
binding.dirPath.textSafe = item.resolveName(binding.context)
|
||||
binding.dirDelete.setOnClickListener { listener.onRemoveDirectory(item) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
val CREATOR =
|
||||
object : Creator<ExcludedViewHolder> {
|
||||
object : Creator<MusicDirViewHolder> {
|
||||
override val viewType: Int
|
||||
get() = throw UnsupportedOperationException()
|
||||
|
||||
override fun create(context: Context) =
|
||||
ExcludedViewHolder(ItemExcludedDirBinding.inflate(context.inflater))
|
||||
MusicDirViewHolder(ItemMusicDirBinding.inflate(context.inflater))
|
||||
}
|
||||
}
|
||||
}
|
65
app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirs.kt
Normal file
65
app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirs.kt
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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.dirs
|
||||
|
||||
import android.os.Build
|
||||
import java.io.File
|
||||
import org.oxycblt.auxio.music.Dir
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.logW
|
||||
|
||||
data class MusicDirs(val dirs: List<Dir.Relative>, val shouldInclude: Boolean) {
|
||||
companion object {
|
||||
private const val VOLUME_PRIMARY_NAME = "primary"
|
||||
|
||||
fun parseDir(dir: String): Dir.Relative? {
|
||||
logD("Parse from string $dir")
|
||||
|
||||
val split = dir.split(File.pathSeparator, limit = 2)
|
||||
|
||||
val volume =
|
||||
when (split[0]) {
|
||||
VOLUME_PRIMARY_NAME -> Dir.Volume.Primary
|
||||
else ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
Dir.Volume.Secondary(split[0])
|
||||
} else {
|
||||
// While Android Q provides a stable way of accessing volumes, we can't
|
||||
// trust that DATA provides a stable volume scheme on older versions, so
|
||||
// external volumes are not supported.
|
||||
logW("Cannot use secondary volumes below Android 10")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
val relativePath = split.getOrNull(1) ?: return null
|
||||
|
||||
return Dir.Relative(volume, relativePath)
|
||||
}
|
||||
|
||||
fun toDir(dir: Dir.Relative): String {
|
||||
val volume =
|
||||
when (dir.volume) {
|
||||
is Dir.Volume.Primary -> VOLUME_PRIMARY_NAME
|
||||
is Dir.Volume.Secondary -> dir.volume.name
|
||||
}
|
||||
|
||||
return "${volume}:${dir.relativePath}"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.music.excluded
|
||||
package org.oxycblt.auxio.music.dirs
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
|
@ -25,42 +25,41 @@ import androidx.activity.result.contract.ActivityResultContracts
|
|||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import kotlinx.coroutines.delay
|
||||
import org.oxycblt.auxio.BuildConfig
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.databinding.DialogExcludedBinding
|
||||
import org.oxycblt.auxio.databinding.DialogMusicDirsBinding
|
||||
import org.oxycblt.auxio.music.Dir
|
||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.ui.ViewBindingDialogFragment
|
||||
import org.oxycblt.auxio.util.hardRestart
|
||||
import org.oxycblt.auxio.util.launch
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.showToast
|
||||
|
||||
/**
|
||||
* Dialog that manages the currently excluded directories.
|
||||
* Dialog that manages the music dirs setting.
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class ExcludedDialog :
|
||||
ViewBindingDialogFragment<DialogExcludedBinding>(), ExcludedAdapter.Listener {
|
||||
class MusicDirsDialog :
|
||||
ViewBindingDialogFragment<DialogMusicDirsBinding>(), MusicDirAdapter.Listener {
|
||||
private val settingsManager = SettingsManager.getInstance()
|
||||
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val excludedAdapter = ExcludedAdapter(this)
|
||||
private val dirAdapter = MusicDirAdapter(this)
|
||||
|
||||
override fun onCreateBinding(inflater: LayoutInflater) = DialogExcludedBinding.inflate(inflater)
|
||||
override fun onCreateBinding(inflater: LayoutInflater) =
|
||||
DialogMusicDirsBinding.inflate(inflater)
|
||||
|
||||
override fun onConfigDialog(builder: AlertDialog.Builder) {
|
||||
// Don't set the click listener here, we do some custom magic in onCreateView instead.
|
||||
builder
|
||||
.setTitle(R.string.set_excluded)
|
||||
.setTitle(R.string.set_dirs)
|
||||
.setNeutralButton(R.string.lbl_add, null)
|
||||
.setPositiveButton(R.string.lbl_save, null)
|
||||
.setNegativeButton(R.string.lbl_cancel, null)
|
||||
}
|
||||
|
||||
override fun onBindingCreated(binding: DialogExcludedBinding, savedInstanceState: Bundle?) {
|
||||
override fun onBindingCreated(binding: DialogMusicDirsBinding, savedInstanceState: Bundle?) {
|
||||
val launcher =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocumentTree(), ::addDocTreePath)
|
||||
|
||||
|
@ -77,7 +76,10 @@ class ExcludedDialog :
|
|||
}
|
||||
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.setOnClickListener {
|
||||
if (settingsManager.excludedDirs != excludedAdapter.data.currentList) {
|
||||
val dirs = settingsManager.musicDirs
|
||||
|
||||
if (dirs.dirs != dirAdapter.data.currentList ||
|
||||
dirs.shouldInclude != isInclude(requireBinding())) {
|
||||
logD("Committing changes")
|
||||
saveAndRestart()
|
||||
} else {
|
||||
|
@ -87,34 +89,55 @@ class ExcludedDialog :
|
|||
}
|
||||
}
|
||||
|
||||
binding.excludedRecycler.apply {
|
||||
adapter = excludedAdapter
|
||||
binding.dirsRecycler.apply {
|
||||
adapter = dirAdapter
|
||||
itemAnimator = null
|
||||
}
|
||||
|
||||
val dirs =
|
||||
savedInstanceState
|
||||
?.getStringArrayList(KEY_PENDING_DIRS)
|
||||
?.mapNotNull(ExcludedDirectories::fromString)
|
||||
?: settingsManager.excludedDirs
|
||||
var dirs = settingsManager.musicDirs
|
||||
|
||||
excludedAdapter.data.addAll(dirs)
|
||||
requireBinding().excludedEmpty.isVisible = dirs.isEmpty()
|
||||
if (savedInstanceState != null) {
|
||||
val pendingDirs = savedInstanceState.getStringArrayList(KEY_PENDING_DIRS)
|
||||
|
||||
if (pendingDirs != null) {
|
||||
dirs =
|
||||
MusicDirs(
|
||||
pendingDirs.mapNotNull(MusicDirs::parseDir),
|
||||
savedInstanceState.getBoolean(KEY_PENDING_MODE))
|
||||
}
|
||||
}
|
||||
|
||||
dirAdapter.data.addAll(dirs.dirs)
|
||||
requireBinding().dirsEmpty.isVisible = dirs.dirs.isEmpty()
|
||||
|
||||
binding.folderModeGroup.apply {
|
||||
check(
|
||||
if (dirs.shouldInclude) {
|
||||
R.id.dirs_mode_include
|
||||
} else {
|
||||
R.id.dirs_mode_exclude
|
||||
})
|
||||
|
||||
updateMode()
|
||||
addOnButtonCheckedListener { _, _, _ -> updateMode() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putStringArrayList(
|
||||
KEY_PENDING_DIRS, ArrayList(excludedAdapter.data.currentList.map { it.toString() }))
|
||||
KEY_PENDING_DIRS, ArrayList(dirAdapter.data.currentList.map { it.toString() }))
|
||||
outState.putBoolean(KEY_PENDING_MODE, isInclude(requireBinding()))
|
||||
}
|
||||
|
||||
override fun onDestroyBinding(binding: DialogExcludedBinding) {
|
||||
override fun onDestroyBinding(binding: DialogMusicDirsBinding) {
|
||||
super.onDestroyBinding(binding)
|
||||
binding.excludedRecycler.adapter = null
|
||||
binding.dirsRecycler.adapter = null
|
||||
}
|
||||
|
||||
override fun onRemoveDirectory(dir: Dir.Relative) {
|
||||
excludedAdapter.data.remove(dir)
|
||||
dirAdapter.data.remove(dir)
|
||||
requireBinding().dirsEmpty.isVisible = dirAdapter.data.currentList.isEmpty()
|
||||
}
|
||||
|
||||
private fun addDocTreePath(uri: Uri?) {
|
||||
|
@ -126,8 +149,8 @@ class ExcludedDialog :
|
|||
|
||||
val dir = parseExcludedUri(uri)
|
||||
if (dir != null) {
|
||||
excludedAdapter.data.add(dir)
|
||||
requireBinding().excludedEmpty.isVisible = false
|
||||
dirAdapter.data.add(dir)
|
||||
requireBinding().dirsEmpty.isVisible = false
|
||||
} else {
|
||||
requireContext().showToast(R.string.err_bad_dir)
|
||||
}
|
||||
|
@ -143,22 +166,31 @@ class ExcludedDialog :
|
|||
val treeUri = DocumentsContract.getTreeDocumentId(docUri)
|
||||
|
||||
// Parsing handles the rest
|
||||
return ExcludedDirectories.fromString(treeUri)
|
||||
return MusicDirs.parseDir(treeUri)
|
||||
}
|
||||
|
||||
private fun saveAndRestart() {
|
||||
settingsManager.excludedDirs = excludedAdapter.data.currentList
|
||||
|
||||
// TODO: Dumb stopgap measure until automatic rescanning, REMOVE THIS BEFORE
|
||||
// MAKING ANY RELEASE!!!!!!
|
||||
launch {
|
||||
delay(1000)
|
||||
playbackModel.savePlaybackState(requireContext()) { requireContext().hardRestart() }
|
||||
private fun updateMode() {
|
||||
val binding = requireBinding()
|
||||
if (isInclude(binding)) {
|
||||
binding.dirsModeDesc.setText(R.string.set_dirs_mode_include_desc)
|
||||
} else {
|
||||
binding.dirsModeDesc.setText(R.string.set_dirs_mode_exclude_desc)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isInclude(binding: DialogMusicDirsBinding) =
|
||||
binding.folderModeGroup.checkedButtonId == R.id.dirs_mode_include
|
||||
|
||||
private fun saveAndRestart() {
|
||||
settingsManager.musicDirs =
|
||||
MusicDirs(dirAdapter.data.currentList, isInclude(requireBinding()))
|
||||
|
||||
playbackModel.savePlaybackState(requireContext()) { requireContext().hardRestart() }
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = BuildConfig.APPLICATION_ID + ".tag.EXCLUDED"
|
||||
const val KEY_PENDING_DIRS = BuildConfig.APPLICATION_ID + ".key.PENDING_DIRS"
|
||||
const val KEY_PENDING_MODE = BuildConfig.APPLICATION_ID + ".key.SHOULD_INCLUDE"
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
/*
|
||||
* 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.excluded
|
||||
|
||||
import android.os.Build
|
||||
import java.io.File
|
||||
import org.oxycblt.auxio.music.Dir
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.logW
|
||||
|
||||
object ExcludedDirectories {
|
||||
private const val VOLUME_PRIMARY_NAME = "primary"
|
||||
|
||||
fun fromString(dir: String): Dir.Relative? {
|
||||
logD("Parse from string $dir")
|
||||
|
||||
val split = dir.split(File.pathSeparator, limit = 2)
|
||||
|
||||
val volume =
|
||||
when (split[0]) {
|
||||
VOLUME_PRIMARY_NAME -> Dir.Volume.Primary
|
||||
else ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
Dir.Volume.Secondary(split[0])
|
||||
} else {
|
||||
// While Android Q provides a stable way of accessing volumes, we can't
|
||||
// trust
|
||||
// that DATA provides a stable volume scheme on older versions, so external
|
||||
// volumes are not supported.
|
||||
logW("Cannot use secondary volumes below Android 10")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
val relativePath = split.getOrNull(1) ?: return null
|
||||
|
||||
return Dir.Relative(volume, relativePath)
|
||||
}
|
||||
|
||||
fun toString(dir: Dir.Relative): String {
|
||||
val volume =
|
||||
when (dir.volume) {
|
||||
is Dir.Volume.Primary -> VOLUME_PRIMARY_NAME
|
||||
is Dir.Volume.Secondary -> dir.volume.name
|
||||
}
|
||||
|
||||
return "${volume}:${dir.relativePath}"
|
||||
}
|
||||
}
|
|
@ -23,7 +23,6 @@ import com.google.android.exoplayer2.audio.BaseAudioProcessor
|
|||
import com.google.android.exoplayer2.metadata.Metadata
|
||||
import com.google.android.exoplayer2.metadata.id3.TextInformationFrame
|
||||
import com.google.android.exoplayer2.metadata.vorbis.VorbisComment
|
||||
import java.lang.UnsupportedOperationException
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.math.pow
|
||||
import org.oxycblt.auxio.music.Album
|
||||
|
@ -79,7 +78,7 @@ class ReplayGainAudioProcessor : BaseAudioProcessor() {
|
|||
// ReplayGain is configurable, so determine what to do based off of the mode.
|
||||
val useAlbumGain =
|
||||
when (settingsManager.replayGainMode) {
|
||||
ReplayGainMode.OFF -> throw UnsupportedOperationException()
|
||||
ReplayGainMode.OFF -> throw IllegalStateException()
|
||||
|
||||
// User wants track gain to be preferred. Default to album gain only if
|
||||
// there is no track gain.
|
||||
|
@ -226,8 +225,7 @@ class ReplayGainAudioProcessor : BaseAudioProcessor() {
|
|||
val buffer = replaceOutputBuffer(size)
|
||||
|
||||
if (volume == 1f) {
|
||||
// No need to apply ReplayGain, do a mem move using put instead of
|
||||
// a for loop (the latter is not efficient)
|
||||
// No need to apply ReplayGain.
|
||||
buffer.put(inputBuffer.slice())
|
||||
} else {
|
||||
for (i in position until limit step 2) {
|
||||
|
|
|
@ -36,6 +36,12 @@ import org.oxycblt.auxio.util.logD
|
|||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
||||
/**
|
||||
* The component managing the [MediaSessionCompat] instance.
|
||||
*
|
||||
* I really don't like how I have to do this, but until I can feasibly work with the ExoPlayer queue
|
||||
* system using something like MediaSessionConnector is more or less impossible.
|
||||
*
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class MediaSessionComponent(private val context: Context, private val player: Player) :
|
||||
Player.Listener,
|
||||
|
|
|
@ -31,7 +31,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import coil.Coil
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.home.tabs.TabCustomizeDialog
|
||||
import org.oxycblt.auxio.music.excluded.ExcludedDialog
|
||||
import org.oxycblt.auxio.music.dirs.MusicDirsDialog
|
||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.playback.replaygain.PreAmpCustomizeDialog
|
||||
import org.oxycblt.auxio.playback.replaygain.ReplayGainMode
|
||||
|
@ -193,10 +193,10 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
|||
true
|
||||
}
|
||||
}
|
||||
SettingsManager.KEY_EXCLUDED -> {
|
||||
SettingsManager.KEY_MUSIC_DIRS -> {
|
||||
onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
ExcludedDialog().show(childFragmentManager, ExcludedDialog.TAG)
|
||||
MusicDirsDialog().show(childFragmentManager, MusicDirsDialog.TAG)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,8 +23,7 @@ import androidx.appcompat.app.AppCompatDelegate
|
|||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.oxycblt.auxio.home.tabs.Tab
|
||||
import org.oxycblt.auxio.music.Dir
|
||||
import org.oxycblt.auxio.music.excluded.ExcludedDirectories
|
||||
import org.oxycblt.auxio.music.dirs.MusicDirs
|
||||
import org.oxycblt.auxio.playback.replaygain.ReplayGainMode
|
||||
import org.oxycblt.auxio.playback.replaygain.ReplayGainPreAmp
|
||||
import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||
|
@ -138,15 +137,22 @@ class SettingsManager private constructor(context: Context) :
|
|||
val pauseOnRepeat: Boolean
|
||||
get() = inner.getBoolean(KEY_PAUSE_ON_REPEAT, false)
|
||||
|
||||
/** The list of directories excluded from indexing. */
|
||||
var excludedDirs: List<Dir.Relative>
|
||||
get() =
|
||||
(inner.getStringSet(KEY_EXCLUDED, null) ?: emptySet()).mapNotNull(
|
||||
ExcludedDirectories::fromString)
|
||||
/** The list of directories that music should be hidden/loaded from. */
|
||||
var musicDirs: MusicDirs
|
||||
get() {
|
||||
val dirs =
|
||||
(inner.getStringSet(KEY_MUSIC_DIRS, null) ?: emptySet()).mapNotNull(
|
||||
MusicDirs::parseDir)
|
||||
|
||||
return MusicDirs(dirs, inner.getBoolean(KEY_SHOULD_INCLUDE, false))
|
||||
}
|
||||
set(value) {
|
||||
inner.edit {
|
||||
putStringSet(KEY_EXCLUDED, value.map(ExcludedDirectories::toString).toSet())
|
||||
apply()
|
||||
putStringSet(KEY_MUSIC_DIRS, value.dirs.map(MusicDirs::toDir).toSet())
|
||||
putBoolean(KEY_SHOULD_INCLUDE, value.shouldInclude)
|
||||
|
||||
// TODO: This is a stopgap measure before automatic rescanning, remove
|
||||
commit()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -252,12 +258,12 @@ class SettingsManager private constructor(context: Context) :
|
|||
init {
|
||||
inner.registerOnSharedPreferenceChangeListener(this)
|
||||
|
||||
if (!inner.contains(KEY_EXCLUDED)) {
|
||||
if (!inner.contains(KEY_MUSIC_DIRS)) {
|
||||
logD("Attempting to migrate excluded directories")
|
||||
// We need to migrate this setting now while we have a context. Note that while
|
||||
// this does do IO work, the old excluded directory database is so small as to make
|
||||
// it negligible.
|
||||
excludedDirs = handleExcludedCompat(context)
|
||||
musicDirs = MusicDirs(handleExcludedCompat(context), false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -325,7 +331,8 @@ class SettingsManager private constructor(context: Context) :
|
|||
|
||||
const val KEY_SAVE_STATE = "auxio_save_state"
|
||||
const val KEY_REINDEX = "auxio_reindex"
|
||||
const val KEY_EXCLUDED = "auxio_excluded_dirs"
|
||||
const val KEY_MUSIC_DIRS = "auxio_music_dirs"
|
||||
const val KEY_SHOULD_INCLUDE = "auxio_include_dirs"
|
||||
|
||||
const val KEY_SEARCH_FILTER_MODE = "KEY_SEARCH_FILTER"
|
||||
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="@dimen/spacing_medium">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/excluded_recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="1"
|
||||
android:overScrollMode="never"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:itemCount="1"
|
||||
tools:listitem="@layout/item_excluded_dir" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/excluded_empty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/spacing_medium"
|
||||
android:paddingTop="@dimen/spacing_medium"
|
||||
android:paddingEnd="@dimen/spacing_medium"
|
||||
android:paddingBottom="@dimen/spacing_medium"
|
||||
android:text="@string/err_no_dirs"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.Auxio.TitleMidLarge"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
</LinearLayout>
|
93
app/src/main/res/layout/dialog_music_dirs.xml
Normal file
93
app/src/main/res/layout/dialog_music_dirs.xml
Normal file
|
@ -0,0 +1,93 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
style="@style/Widget.Auxio.Dialog.NestedScrollView">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/dirs_recycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="1"
|
||||
android:overScrollMode="never"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:itemCount="1"
|
||||
tools:listitem="@layout/item_music_dir" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dirs_empty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/spacing_mid_large"
|
||||
android:paddingTop="@dimen/spacing_medium"
|
||||
android:paddingEnd="@dimen/spacing_mid_large"
|
||||
android:paddingBottom="@dimen/spacing_medium"
|
||||
android:text="@string/err_no_dirs"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.Auxio.TitleMidLarge"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="@dimen/spacing_mid_large"
|
||||
android:paddingEnd="@dimen/spacing_mid_large"
|
||||
style="@style/Widget.Auxio.TextView.Header"
|
||||
android:text="@string/set_dirs_mode" />
|
||||
|
||||
<com.google.android.material.divider.MaterialDivider
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<com.google.android.material.button.MaterialButtonToggleGroup
|
||||
android:id="@+id/folder_mode_group"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/spacing_mid_large"
|
||||
android:layout_marginEnd="@dimen/spacing_mid_large"
|
||||
android:layout_marginTop="@dimen/spacing_medium"
|
||||
android:gravity="center"
|
||||
app:singleSelection="true"
|
||||
app:selectionRequired="true"
|
||||
app:checkedButton="@+id/dirs_mode_exclude">
|
||||
|
||||
<Button
|
||||
android:id="@+id/dirs_mode_exclude"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/set_dirs_mode_exclude"
|
||||
style="@style/Widget.Auxio.Button.Secondary" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/dirs_mode_include"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/set_dirs_mode_include"
|
||||
style="@style/Widget.Auxio.Button.Secondary" />
|
||||
|
||||
</com.google.android.material.button.MaterialButtonToggleGroup>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dirs_mode_desc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/spacing_mid_large"
|
||||
android:layout_marginEnd="@dimen/spacing_mid_large"
|
||||
android:layout_marginTop="@dimen/spacing_small"
|
||||
tools:text="Mode description" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
|
@ -7,7 +7,7 @@
|
|||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/excluded_path"
|
||||
android:id="@+id/dir_path"
|
||||
style="@style/Widget.Auxio.TextView.Item.Primary"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -19,18 +19,18 @@
|
|||
android:paddingBottom="@dimen/spacing_small"
|
||||
android:textAppearance="@style/TextAppearance.Auxio.BodyLarge"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/excluded_clear"
|
||||
app:layout_constraintEnd_toStartOf="@+id/dir_delete"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="/storage/emulated/0/directory" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/excluded_clear"
|
||||
android:id="@+id/dir_delete"
|
||||
style="@style/Widget.Auxio.Button.Icon.Small"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/spacing_button_dialog"
|
||||
android:contentDescription="@string/desc_blacklist_delete"
|
||||
android:contentDescription="@string/desc_music_dir_delete"
|
||||
app:icon="@drawable/ic_delete"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
|
@ -99,8 +99,6 @@
|
|||
<string name="set_content">محتوى</string>
|
||||
<string name="set_save">حفظ حالة التشغيل</string>
|
||||
<string name="set_save_desc">حفظ حالة التشغيل الحالية الآن</string>
|
||||
<string name="set_excluded">استبعاد مجلدات</string>
|
||||
<string name="set_excluded_desc">محتوى المجلدات المستبعدة يتم اخفائها من مكتبتك</string>
|
||||
|
||||
<!-- Error Namespace | Error Labels -->
|
||||
<string name="err_no_music">لم يتم ايجاد موسيقى</string>
|
||||
|
@ -126,7 +124,7 @@
|
|||
<string name="desc_queue_handle">نقل اغنية من الطابور</string>
|
||||
<string name="desc_tab_handle">تحريك التبويت</string>
|
||||
<string name="desc_clear_search">إزالة كلمة البحث</string>
|
||||
<string name="desc_blacklist_delete">إزالة المجلد المستبعد</string>
|
||||
<string name="desc_music_dir_delete">إزالة المجلد المستبعد</string>
|
||||
|
||||
<string name="desc_auxio_icon">ايقونة اوكسيو</string>
|
||||
<string name="desc_no_cover">غلاف الالبوم</string>
|
||||
|
|
|
@ -117,8 +117,6 @@
|
|||
<string name="set_save_desc">Uložit aktuální stav přehrávání</string>
|
||||
<string name="set_reindex">Znovu načíst hudbu</string>
|
||||
<string name="set_reindex_desc">Aplikace bude restartována</string>
|
||||
<string name="set_excluded">Vyloučené složky</string>
|
||||
<string name="set_excluded_desc">Obsah vyloučených složek je skrytý z vaší knihovny</string>
|
||||
|
||||
<!-- Error Namespace | Error Labels -->
|
||||
<string name="err_no_music">Nenalezena žádná hudba</string>
|
||||
|
@ -146,7 +144,7 @@
|
|||
<string name="desc_queue_handle">Přesunout tuto skladbu ve frontě</string>
|
||||
<string name="desc_tab_handle">Přesunout tuto kartu</string>
|
||||
<string name="desc_clear_search">Vymazat hledání</string>
|
||||
<string name="desc_blacklist_delete">Odebrat vyloučený adresář</string>
|
||||
<string name="desc_music_dir_delete">Odebrat vyloučený adresář</string>
|
||||
|
||||
<string name="desc_auxio_icon">Ikona Auxio</string>
|
||||
<string name="desc_no_cover">Obal alba</string>
|
||||
|
|
|
@ -95,8 +95,6 @@
|
|||
<string name="set_save_desc">Den aktuellen Wiedergabezustand speichern</string>
|
||||
<string name="set_reindex">Musik neu laden</string>
|
||||
<string name="set_reindex_desc">Startet die App neu</string>
|
||||
<string name="set_excluded">Ausgeschlossene Ordner</string>
|
||||
<string name="set_excluded_desc">Die Inhalte der ausgeschlossenen Ordner werden nicht deiner Musikbibliothek angezeigt</string>
|
||||
|
||||
<!-- Error Namespace | Error Labels -->
|
||||
<string name="err_no_music">Keine Musik gefunden</string>
|
||||
|
@ -168,7 +166,7 @@
|
|||
<string name="set_repeat_pause_desc">Pausieren, wenn ein Song wiederholt wird</string>
|
||||
<string name="desc_shuffle">Zufällig an- oder ausschalten</string>
|
||||
<string name="desc_queue_handle">Lied in der Warteschlange verschieben</string>
|
||||
<string name="desc_blacklist_delete">Ausgeschlossenes Verzechnis entfernen</string>
|
||||
<string name="desc_music_dir_delete">Ausgeschlossenes Verzechnis entfernen</string>
|
||||
<string name="desc_no_cover">Albumcover</string>
|
||||
<string name="def_playback">Keine Musik wird gespielt</string>
|
||||
<string name="def_widget_song">Liedname</string>
|
||||
|
|
|
@ -101,8 +101,6 @@
|
|||
<string name="set_save_desc">Guardar el estado de reproduccion ahora</string>
|
||||
<string name="set_reindex">Recargar música</string>
|
||||
<string name="set_reindex_desc">Se reiniciará la aplicación</string>
|
||||
<string name="set_excluded">Directorios excluidos</string>
|
||||
<string name="set_excluded_desc">El contenido de los directorios excluidos no se mostrará</string>
|
||||
|
||||
<!-- Error Namespace | Error Labels -->
|
||||
<string name="err_no_music">Sin música</string>
|
||||
|
@ -129,7 +127,7 @@
|
|||
<string name="desc_queue_handle">Mover canción en la cola</string>
|
||||
<string name="desc_tab_handle">Mover pestaña</string>
|
||||
<string name="desc_clear_search">Borrar historial de búsqueda</string>
|
||||
<string name="desc_blacklist_delete">Quitar directorio excluido</string>
|
||||
<string name="desc_music_dir_delete">Quitar directorio excluido</string>
|
||||
|
||||
<string name="desc_auxio_icon">Icono de Auxio</string>
|
||||
<string name="desc_no_cover">Carátula de álbum</string>
|
||||
|
|
|
@ -102,8 +102,6 @@
|
|||
<string name="set_save_desc">Salva lo stato di riproduzione corrente</string>
|
||||
<string name="set_reindex">Ricarica musica</string>
|
||||
<string name="set_reindex_desc">L\'applicazione sarà riavviata</string>
|
||||
<string name="set_excluded">Cartelle escluse</string>
|
||||
<string name="set_excluded_desc">Il contenuto delle cartelle escluse sarà nascosto dalla tua libreria</string>
|
||||
|
||||
<string name="lbl_off">Spento</string>
|
||||
|
||||
|
@ -132,7 +130,7 @@
|
|||
<string name="desc_queue_handle">Muove questa canzone della coda</string>
|
||||
<string name="desc_tab_handle">Muove questa scheda</string>
|
||||
<string name="desc_clear_search">Cancella la query di ricerca</string>
|
||||
<string name="desc_blacklist_delete">Rimuove cartella esclusa</string>
|
||||
<string name="desc_music_dir_delete">Rimuove cartella esclusa</string>
|
||||
|
||||
<string name="desc_auxio_icon">Icona Auxio</string>
|
||||
<string name="desc_no_cover">Copertina disco</string>
|
||||
|
|
|
@ -115,8 +115,6 @@
|
|||
<string name="set_save_desc">현재 재생 상태를 지금 저장</string>
|
||||
<string name="set_reindex">음악 다시 불러오기</string>
|
||||
<string name="set_reindex_desc">앱이 다시 시작됩니다.</string>
|
||||
<string name="set_excluded">폴더 제외</string>
|
||||
<string name="set_excluded_desc">제외한 폴더는 라이브러리에서 숨겨집니다.</string>
|
||||
|
||||
<!-- Error Namespace | Error Labels -->
|
||||
<string name="err_no_music">음악 없음</string>
|
||||
|
@ -144,7 +142,7 @@
|
|||
<string name="desc_queue_handle">이 대기열의 음악 이동</string>
|
||||
<string name="desc_tab_handle">이 탭 이동</string>
|
||||
<string name="desc_clear_search">검색 기록 삭제</string>
|
||||
<string name="desc_blacklist_delete">제외한 디렉터리 제거</string>
|
||||
<string name="desc_music_dir_delete">제외한 디렉터리 제거</string>
|
||||
|
||||
<string name="desc_auxio_icon">Auxio 아이콘</string>
|
||||
<string name="desc_no_cover">앨범 커버</string>
|
||||
|
|
|
@ -81,8 +81,6 @@
|
|||
<string name="set_content">Inhoud</string>
|
||||
<string name="set_save">Afspeelstatus opslaan</string>
|
||||
<string name="set_save_desc">Sla de huidige afspeelstatus nu op </string>
|
||||
<string name="set_excluded">Uitgesloten mappen </string>
|
||||
<string name="set_excluded_desc">De inhoud van uitgesloten mappen wordt verborgen voor uw bibliotheek</string>
|
||||
|
||||
<!-- Error Namespace | Error Labels -->
|
||||
<string name="err_no_music">Geen muziek aangetroffen</string>
|
||||
|
@ -103,7 +101,7 @@
|
|||
<string name="desc_change_repeat">Herhaalfunctie wijzigen</string>
|
||||
|
||||
<string name="desc_clear_search">Zoekopdracht wissen</string>
|
||||
<string name="desc_blacklist_delete">Verwijder uitgesloten map</string>
|
||||
<string name="desc_music_dir_delete">Verwijder uitgesloten map</string>
|
||||
|
||||
<string name="desc_auxio_icon">Auxio pictogram</string>
|
||||
<string name="desc_album_cover">Artist Image voor %s</string>
|
||||
|
|
|
@ -103,8 +103,6 @@
|
|||
<string name="set_save_desc">Запоминать позицию в треке</string>
|
||||
<string name="set_reindex">Перезагрузить музыку</string>
|
||||
<string name="set_reindex_desc">Это перезапустит приложение</string>
|
||||
<string name="set_excluded">Исключённые папки</string>
|
||||
<string name="set_excluded_desc">Содержимое исключённых папок будет скрыто из библиотеки</string>
|
||||
|
||||
<!-- Error Namespace | Error Labels -->
|
||||
<string name="err_no_music">Треков нет</string>
|
||||
|
@ -131,7 +129,7 @@
|
|||
<string name="desc_queue_handle">Переместить трек в очереди</string>
|
||||
<string name="desc_tab_handle">Переместить вкладку</string>
|
||||
<string name="desc_clear_search">Очистить поисковый запрос</string>
|
||||
<string name="desc_blacklist_delete">Удалить исключенную папку</string>
|
||||
<string name="desc_music_dir_delete">Удалить исключенную папку</string>
|
||||
|
||||
<string name="desc_auxio_icon">Иконка Auxio</string>
|
||||
<string name="desc_no_cover">Обложка альбома</string>
|
||||
|
|
|
@ -101,8 +101,6 @@
|
|||
<string name="set_save_desc">立即保存当前播放状态</string>
|
||||
<string name="set_reindex">重新加载音乐</string>
|
||||
<string name="set_reindex_desc">将会重启应用</string>
|
||||
<string name="set_excluded">排除文件夹</string>
|
||||
<string name="set_excluded_desc">被排除文件夹的内容将从媒体库中隐藏</string>
|
||||
|
||||
<string name="lbl_off">关闭</string>
|
||||
|
||||
|
@ -131,7 +129,7 @@
|
|||
<string name="desc_queue_handle">移动队列曲目</string>
|
||||
<string name="desc_tab_handle">移动该标签</string>
|
||||
<string name="desc_clear_search">清除搜索队列</string>
|
||||
<string name="desc_blacklist_delete">移除排除路径</string>
|
||||
<string name="desc_music_dir_delete">移除排除路径</string>
|
||||
|
||||
<string name="desc_auxio_icon">Auxio 图标</string>
|
||||
<string name="desc_no_cover">专辑封面</string>
|
||||
|
|
|
@ -126,8 +126,13 @@
|
|||
<string name="set_save_desc">Save the current playback state now</string>
|
||||
<string name="set_reindex">Reload music</string>
|
||||
<string name="set_reindex_desc">Will restart app</string>
|
||||
<string name="set_excluded">Excluded folders</string>
|
||||
<string name="set_excluded_desc">The content of excluded folders is hidden from your library</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_mode">Mode</string>
|
||||
<string name="set_dirs_mode_exclude">Exclude</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_desc">Music will <b>only</b> be loaded from the folders you add.</string>
|
||||
|
||||
<!-- Error Namespace | Error Labels -->
|
||||
<string name="err_no_music">No music found</string>
|
||||
|
@ -155,7 +160,7 @@
|
|||
<string name="desc_queue_handle">Move this queue song</string>
|
||||
<string name="desc_tab_handle">Move this tab</string>
|
||||
<string name="desc_clear_search">Clear search query</string>
|
||||
<string name="desc_blacklist_delete">Remove excluded directory</string>
|
||||
<string name="desc_music_dir_delete">Remove directory</string>
|
||||
|
||||
<string name="desc_auxio_icon">Auxio icon</string>
|
||||
<string name="desc_no_cover">Album cover</string>
|
||||
|
@ -176,13 +181,14 @@
|
|||
<string name="def_widget_artist">Artist Name</string>
|
||||
|
||||
<!-- Codec Namespace | Format names -->
|
||||
<string name="cdc_mp3">MPEG-1 Layer 3 (MP3)</string>
|
||||
<string name="cdc_ogg">OGG</string>
|
||||
<string name="cdc_ogg_vorbis">OGG Vorbis</string>
|
||||
<string name="cdc_ogg_opus">OGG Opus</string>
|
||||
<string name="cdc_mp3">MPEG-1 Layer 3 </string>
|
||||
<string name="cdc_mp4">MPEG-4 Audio (AAC)</string>
|
||||
<string name="cdc_ogg">Ogg</string>
|
||||
<string name="cdc_ogg_vorbis">Ogg Vorbis</string>
|
||||
<string name="cdc_ogg_opus">Ogg Opus</string>
|
||||
<string name="cdc_flac">Free Lossless Audio Codec (FLAC)</string>
|
||||
<string name="cdc_aac">Advanced Audio Coding (AAC)</string>
|
||||
<string name="cdc_wav">Microsoft WAV</string>
|
||||
<string name="cdc_wav">Microsoft WAVE</string>
|
||||
<string name="cdc_wma">Windows Media Audio (WMA)</string>
|
||||
|
||||
<!-- Color Label namespace | Accent names -->
|
||||
|
|
|
@ -235,6 +235,7 @@
|
|||
|
||||
<style name="Widget.Auxio.Button.Secondary" parent="Widget.Material3.Button.OutlinedButton">
|
||||
<item name="android:textAppearance">@style/TextAppearance.Auxio.LabelLarger</item>
|
||||
<item name="strokeColor">?attr/colorOutline</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Auxio.FloatingActionButton.PlayPause" parent="Widget.Material3.FloatingActionButton.Secondary">
|
||||
|
|
|
@ -158,9 +158,9 @@
|
|||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="auxio_excluded_dirs"
|
||||
app:summary="@string/set_excluded_desc"
|
||||
app:title="@string/set_excluded" />
|
||||
app:key="auxio_music_dirs"
|
||||
app:summary="@string/set_dirs_desc"
|
||||
app:title="@string/set_dirs" />
|
||||
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
|
@ -1,6 +1,6 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.6.21'
|
||||
ext.kotlin_version = '1.7.0'
|
||||
ext.navigation_version = "2.4.2"
|
||||
|
||||
repositories {
|
||||
|
|
Loading…
Reference in a new issue