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:
OxygenCobalt 2022-06-12 15:51:05 -06:00
parent 53f3d0faef
commit 9b13b4c94e
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
28 changed files with 351 additions and 230 deletions

View file

@ -3,10 +3,12 @@
## dev [v2.3.2, v2.4.0, or v3.0.0] ## dev [v2.3.2, v2.4.0, or v3.0.0]
#### What's New #### 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) - 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
- The playback bar now has a new design, with an improved progress skip action
indicator and a skip action
- When playing, covers now shows an animated indicator - When playing, covers now shows an animated indicator
#### What's Improved #### What's Improved
@ -14,6 +16,7 @@ indicator and a skip action
- The toolbar layout is now consistent with Material Design 3 - 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) - 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 - "Rounded album covers" option is no longer dependent on "Show album covers" option
- Added song actions to the playback panel
#### What's Fixed #### What's Fixed
- Playback bar now picks the larger inset in case that gesture inset is missing [#149] - 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 - Moved music loading to a foreground service
- Phased out `ImageButton` for `MaterialButton` - Phased out `ImageButton` for `MaterialButton`
- Unified icon sizing - Unified icon sizing
- Added original date support to ExoPlayer parser (Not exposed in app)
## v2.3.1 ## v2.3.1

View file

@ -70,6 +70,7 @@ sealed class Dir {
/** /**
* Represents a mime type as it is loaded by Auxio. [fromExtension] is based on the file extension * 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. * 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?) { data class MimeType(val fromExtension: String, val fromFormat: String?) {
fun resolveName(context: Context): 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. // We have special names for the most common formats.
val readableStringRes = val readableStringRes =
when (readableMime) { when (readableMime) {
// Classic formats // MPEG formats
// While MP4 is AAC, it's considered separate given how common it is.
"audio/mpeg", "audio/mpeg",
"audio/mp3" -> R.string.cdc_mp3 "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/vorbis" -> R.string.cdc_ogg_vorbis
"audio/opus" -> R.string.cdc_ogg_opus "audio/opus" -> R.string.cdc_ogg_opus
"audio/flac" -> R.string.cdc_flac "audio/flac" -> R.string.cdc_flac
// MP4, 3GPP, M4A, etc. are all based on AAC // The other AAC containers have a generic name
"audio/mp4",
"audio/mp4a-latm",
"audio/mpeg4-generic",
"audio/aac", "audio/aac",
"audio/aacp",
"audio/aac-adts",
"audio/3gpp", "audio/3gpp",
"audio/3gpp2", -> R.string.cdc_aac "audio/3gpp2", -> R.string.cdc_aac
@ -107,6 +116,8 @@ data class MimeType(val fromExtension: String, val fromFormat: String?) {
"audio/wave", "audio/wave",
"audio/vnd.wave" -> R.string.cdc_wav "audio/vnd.wave" -> R.string.cdc_wav
"audio/x-ms-wma" -> R.string.cdc_wma "audio/x-ms-wma" -> R.string.cdc_wma
// Don't know
else -> -1 else -> -1
} }

View file

@ -203,6 +203,8 @@ class Task(context: Context, private val audio: MediaStoreBackend.Audio) {
} }
} }
// TODO: Release types
private fun populateId3v2(tags: Map<String, String>) { private fun populateId3v2(tags: Map<String, String>) {
// Title // Title
tags["TIT2"]?.let { audio.title = it } tags["TIT2"]?.let { audio.title = it }

View file

@ -33,6 +33,7 @@ import org.oxycblt.auxio.music.Path
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.albumCoverUri import org.oxycblt.auxio.music.albumCoverUri
import org.oxycblt.auxio.music.audioUri import org.oxycblt.auxio.music.audioUri
import org.oxycblt.auxio.music.dirs.MusicDirs
import org.oxycblt.auxio.music.id3GenreName import org.oxycblt.auxio.music.id3GenreName
import org.oxycblt.auxio.music.no import org.oxycblt.auxio.music.no
import org.oxycblt.auxio.music.queryCursor import org.oxycblt.auxio.music.queryCursor
@ -122,7 +123,7 @@ abstract class MediaStoreBackend : Indexer.Backend {
override fun query(context: Context): Cursor { override fun query(context: Context): Cursor {
val settingsManager = SettingsManager.getInstance() val settingsManager = SettingsManager.getInstance()
val selector = buildExcludedSelector(settingsManager.excludedDirs) val selector = buildMusicDirsSelector(settingsManager.musicDirs)
return requireNotNull( return requireNotNull(
context.contentResolverSafe.queryCursor( context.contentResolverSafe.queryCursor(
@ -187,7 +188,7 @@ abstract class MediaStoreBackend : Indexer.Backend {
open val projection: Array<String> open val projection: Array<String>
get() = BASE_PROJECTION 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 * 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 + super.projection +
arrayOf(MediaStore.Audio.AudioColumns.TRACK, MediaStore.Audio.AudioColumns.DATA) 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 val base = Environment.getExternalStorageDirectory().absolutePath
var selector = BASE_SELECTOR var selector = BASE_SELECTOR
val args = mutableListOf<String>() val args = mutableListOf<String>()
// Apply the excluded directories by filtering out specific DATA values. // Apply directories by filtering out specific DATA values.
for (dir in dirs) { for (dir in dirs.dirs) {
if (dir.volume is Dir.Volume.Secondary) { if (dir.volume is Dir.Volume.Secondary) {
logW("Cannot exclude directories on secondary drives") // Should never happen.
continue 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}%" args += "${base}/${dir.relativePath}%"
} }
@ -442,17 +449,22 @@ open class Api29MediaStoreBackend : Api21MediaStoreBackend() {
MediaStore.Audio.AudioColumns.VOLUME_NAME, MediaStore.Audio.AudioColumns.VOLUME_NAME,
MediaStore.Audio.AudioColumns.RELATIVE_PATH) MediaStore.Audio.AudioColumns.RELATIVE_PATH)
override fun buildExcludedSelector(dirs: List<Dir.Relative>): Selector { override fun buildMusicDirsSelector(dirs: MusicDirs): Selector {
var selector = BASE_SELECTOR var selector = BASE_SELECTOR
val args = mutableListOf<String>() val args = mutableListOf<String>()
// Starting in Android Q, we finally have access to the volume name. This allows // 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. // use to properly exclude folders on secondary devices such as SD cards.
for (dir in dirs) { for (dir in dirs.dirs) {
selector += selector +=
" AND NOT (${MediaStore.Audio.AudioColumns.VOLUME_NAME} LIKE ? " + if (dirs.shouldInclude) {
"AND ${MediaStore.Audio.AudioColumns.RELATIVE_PATH} LIKE ?)" " 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 // 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. // name stored in-app. I have no idea how well this holds up on other devices.

View file

@ -15,10 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * 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 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.music.Dir
import org.oxycblt.auxio.ui.BackingData import org.oxycblt.auxio.ui.BackingData
import org.oxycblt.auxio.ui.BindingViewHolder 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. * Adapter that shows the excluded directories and their "Clear" button.
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class ExcludedAdapter(listener: Listener) : class MusicDirAdapter(listener: Listener) :
MonoAdapter<Dir.Relative, ExcludedAdapter.Listener, ExcludedViewHolder>(listener) { MonoAdapter<Dir.Relative, MusicDirAdapter.Listener, MusicDirViewHolder>(listener) {
override val data = ExcludedBackingData(this) override val data = ExcludedBackingData(this)
override val creator = ExcludedViewHolder.CREATOR override val creator = MusicDirViewHolder.CREATOR
interface Listener { interface Listener {
fun onRemoveDirectory(dir: Dir.Relative) 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>() private val _currentList = mutableListOf<Dir.Relative>()
val currentList: List<Dir.Relative> = _currentList 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. */ /** The viewholder for [MusicDirAdapter]. Not intended for use in other adapters. */
class ExcludedViewHolder private constructor(private val binding: ItemExcludedDirBinding) : class MusicDirViewHolder private constructor(private val binding: ItemMusicDirBinding) :
BindingViewHolder<Dir.Relative, ExcludedAdapter.Listener>(binding.root) { BindingViewHolder<Dir.Relative, MusicDirAdapter.Listener>(binding.root) {
override fun bind(item: Dir.Relative, listener: ExcludedAdapter.Listener) { override fun bind(item: Dir.Relative, listener: MusicDirAdapter.Listener) {
binding.excludedPath.textSafe = item.resolveName(binding.context) binding.dirPath.textSafe = item.resolveName(binding.context)
binding.excludedClear.setOnClickListener { listener.onRemoveDirectory(item) } binding.dirDelete.setOnClickListener { listener.onRemoveDirectory(item) }
} }
companion object { companion object {
val CREATOR = val CREATOR =
object : Creator<ExcludedViewHolder> { object : Creator<MusicDirViewHolder> {
override val viewType: Int override val viewType: Int
get() = throw UnsupportedOperationException() get() = throw UnsupportedOperationException()
override fun create(context: Context) = override fun create(context: Context) =
ExcludedViewHolder(ItemExcludedDirBinding.inflate(context.inflater)) MusicDirViewHolder(ItemMusicDirBinding.inflate(context.inflater))
} }
} }
} }

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

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * 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.net.Uri
import android.os.Bundle import android.os.Bundle
@ -25,42 +25,41 @@ 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 androidx.fragment.app.activityViewModels
import kotlinx.coroutines.delay
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.DialogExcludedBinding import org.oxycblt.auxio.databinding.DialogMusicDirsBinding
import org.oxycblt.auxio.music.Dir import org.oxycblt.auxio.music.Dir
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.ui.ViewBindingDialogFragment import org.oxycblt.auxio.ui.ViewBindingDialogFragment
import org.oxycblt.auxio.util.hardRestart import org.oxycblt.auxio.util.hardRestart
import org.oxycblt.auxio.util.launch
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.showToast
/** /**
* Dialog that manages the currently excluded directories. * Dialog that manages the music dirs setting.
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class ExcludedDialog : class MusicDirsDialog :
ViewBindingDialogFragment<DialogExcludedBinding>(), ExcludedAdapter.Listener { ViewBindingDialogFragment<DialogMusicDirsBinding>(), MusicDirAdapter.Listener {
private val settingsManager = SettingsManager.getInstance() private val settingsManager = SettingsManager.getInstance()
private val playbackModel: PlaybackViewModel by activityViewModels() 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) { override fun onConfigDialog(builder: AlertDialog.Builder) {
// Don't set the click listener here, we do some custom magic in onCreateView instead. // Don't set the click listener here, we do some custom magic in onCreateView instead.
builder builder
.setTitle(R.string.set_excluded) .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, null)
.setNegativeButton(R.string.lbl_cancel, null) .setNegativeButton(R.string.lbl_cancel, null)
} }
override fun onBindingCreated(binding: DialogExcludedBinding, savedInstanceState: Bundle?) { override fun onBindingCreated(binding: DialogMusicDirsBinding, savedInstanceState: Bundle?) {
val launcher = val launcher =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree(), ::addDocTreePath) registerForActivityResult(ActivityResultContracts.OpenDocumentTree(), ::addDocTreePath)
@ -77,7 +76,10 @@ class ExcludedDialog :
} }
dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.setOnClickListener { 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") logD("Committing changes")
saveAndRestart() saveAndRestart()
} else { } else {
@ -87,34 +89,55 @@ class ExcludedDialog :
} }
} }
binding.excludedRecycler.apply { binding.dirsRecycler.apply {
adapter = excludedAdapter adapter = dirAdapter
itemAnimator = null itemAnimator = null
} }
val dirs = var dirs = settingsManager.musicDirs
savedInstanceState
?.getStringArrayList(KEY_PENDING_DIRS)
?.mapNotNull(ExcludedDirectories::fromString)
?: settingsManager.excludedDirs
excludedAdapter.data.addAll(dirs) if (savedInstanceState != null) {
requireBinding().excludedEmpty.isVisible = dirs.isEmpty() 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) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
outState.putStringArrayList( 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) super.onDestroyBinding(binding)
binding.excludedRecycler.adapter = null binding.dirsRecycler.adapter = null
} }
override fun onRemoveDirectory(dir: Dir.Relative) { 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?) { private fun addDocTreePath(uri: Uri?) {
@ -126,8 +149,8 @@ class ExcludedDialog :
val dir = parseExcludedUri(uri) val dir = parseExcludedUri(uri)
if (dir != null) { if (dir != null) {
excludedAdapter.data.add(dir) dirAdapter.data.add(dir)
requireBinding().excludedEmpty.isVisible = false requireBinding().dirsEmpty.isVisible = false
} else { } else {
requireContext().showToast(R.string.err_bad_dir) requireContext().showToast(R.string.err_bad_dir)
} }
@ -143,22 +166,31 @@ class ExcludedDialog :
val treeUri = DocumentsContract.getTreeDocumentId(docUri) val treeUri = DocumentsContract.getTreeDocumentId(docUri)
// Parsing handles the rest // Parsing handles the rest
return ExcludedDirectories.fromString(treeUri) return MusicDirs.parseDir(treeUri)
} }
private fun saveAndRestart() { private fun updateMode() {
settingsManager.excludedDirs = excludedAdapter.data.currentList val binding = requireBinding()
if (isInclude(binding)) {
// TODO: Dumb stopgap measure until automatic rescanning, REMOVE THIS BEFORE binding.dirsModeDesc.setText(R.string.set_dirs_mode_include_desc)
// MAKING ANY RELEASE!!!!!! } else {
launch { binding.dirsModeDesc.setText(R.string.set_dirs_mode_exclude_desc)
delay(1000)
playbackModel.savePlaybackState(requireContext()) { requireContext().hardRestart() }
} }
} }
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 { 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"
const val KEY_PENDING_MODE = BuildConfig.APPLICATION_ID + ".key.SHOULD_INCLUDE"
} }
} }

View file

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

View file

@ -23,7 +23,6 @@ import com.google.android.exoplayer2.audio.BaseAudioProcessor
import com.google.android.exoplayer2.metadata.Metadata import com.google.android.exoplayer2.metadata.Metadata
import com.google.android.exoplayer2.metadata.id3.TextInformationFrame import com.google.android.exoplayer2.metadata.id3.TextInformationFrame
import com.google.android.exoplayer2.metadata.vorbis.VorbisComment import com.google.android.exoplayer2.metadata.vorbis.VorbisComment
import java.lang.UnsupportedOperationException
import java.nio.ByteBuffer import java.nio.ByteBuffer
import kotlin.math.pow import kotlin.math.pow
import org.oxycblt.auxio.music.Album 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. // ReplayGain is configurable, so determine what to do based off of the mode.
val useAlbumGain = val useAlbumGain =
when (settingsManager.replayGainMode) { when (settingsManager.replayGainMode) {
ReplayGainMode.OFF -> throw UnsupportedOperationException() ReplayGainMode.OFF -> throw IllegalStateException()
// User wants track gain to be preferred. Default to album gain only if // User wants track gain to be preferred. Default to album gain only if
// there is no track gain. // there is no track gain.
@ -226,8 +225,7 @@ class ReplayGainAudioProcessor : BaseAudioProcessor() {
val buffer = replaceOutputBuffer(size) val buffer = replaceOutputBuffer(size)
if (volume == 1f) { if (volume == 1f) {
// No need to apply ReplayGain, do a mem move using put instead of // No need to apply ReplayGain.
// a for loop (the latter is not efficient)
buffer.put(inputBuffer.slice()) buffer.put(inputBuffer.slice())
} else { } else {
for (i in position until limit step 2) { for (i in position until limit step 2) {

View file

@ -36,6 +36,12 @@ import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.unlikelyToBeNull 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) : class MediaSessionComponent(private val context: Context, private val player: Player) :
Player.Listener, Player.Listener,

View file

@ -31,7 +31,7 @@ import androidx.recyclerview.widget.RecyclerView
import coil.Coil import coil.Coil
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.home.tabs.TabCustomizeDialog 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.PlaybackViewModel
import org.oxycblt.auxio.playback.replaygain.PreAmpCustomizeDialog import org.oxycblt.auxio.playback.replaygain.PreAmpCustomizeDialog
import org.oxycblt.auxio.playback.replaygain.ReplayGainMode import org.oxycblt.auxio.playback.replaygain.ReplayGainMode
@ -193,10 +193,10 @@ class SettingsListFragment : PreferenceFragmentCompat() {
true true
} }
} }
SettingsManager.KEY_EXCLUDED -> { SettingsManager.KEY_MUSIC_DIRS -> {
onPreferenceClickListener = onPreferenceClickListener =
Preference.OnPreferenceClickListener { Preference.OnPreferenceClickListener {
ExcludedDialog().show(childFragmentManager, ExcludedDialog.TAG) MusicDirsDialog().show(childFragmentManager, MusicDirsDialog.TAG)
true true
} }
} }

View file

@ -23,8 +23,7 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.edit import androidx.core.content.edit
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import org.oxycblt.auxio.home.tabs.Tab import org.oxycblt.auxio.home.tabs.Tab
import org.oxycblt.auxio.music.Dir import org.oxycblt.auxio.music.dirs.MusicDirs
import org.oxycblt.auxio.music.excluded.ExcludedDirectories
import org.oxycblt.auxio.playback.replaygain.ReplayGainMode import org.oxycblt.auxio.playback.replaygain.ReplayGainMode
import org.oxycblt.auxio.playback.replaygain.ReplayGainPreAmp import org.oxycblt.auxio.playback.replaygain.ReplayGainPreAmp
import org.oxycblt.auxio.playback.state.PlaybackMode import org.oxycblt.auxio.playback.state.PlaybackMode
@ -138,15 +137,22 @@ class SettingsManager private constructor(context: Context) :
val pauseOnRepeat: Boolean val pauseOnRepeat: Boolean
get() = inner.getBoolean(KEY_PAUSE_ON_REPEAT, false) get() = inner.getBoolean(KEY_PAUSE_ON_REPEAT, false)
/** The list of directories excluded from indexing. */ /** The list of directories that music should be hidden/loaded from. */
var excludedDirs: List<Dir.Relative> var musicDirs: MusicDirs
get() = get() {
(inner.getStringSet(KEY_EXCLUDED, null) ?: emptySet()).mapNotNull( val dirs =
ExcludedDirectories::fromString) (inner.getStringSet(KEY_MUSIC_DIRS, null) ?: emptySet()).mapNotNull(
MusicDirs::parseDir)
return MusicDirs(dirs, inner.getBoolean(KEY_SHOULD_INCLUDE, false))
}
set(value) { set(value) {
inner.edit { inner.edit {
putStringSet(KEY_EXCLUDED, value.map(ExcludedDirectories::toString).toSet()) putStringSet(KEY_MUSIC_DIRS, value.dirs.map(MusicDirs::toDir).toSet())
apply() 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 { init {
inner.registerOnSharedPreferenceChangeListener(this) inner.registerOnSharedPreferenceChangeListener(this)
if (!inner.contains(KEY_EXCLUDED)) { if (!inner.contains(KEY_MUSIC_DIRS)) {
logD("Attempting to migrate excluded directories") logD("Attempting to migrate excluded directories")
// We need to migrate this setting now while we have a context. Note that while // 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 // this does do IO work, the old excluded directory database is so small as to make
// it negligible. // 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_SAVE_STATE = "auxio_save_state"
const val KEY_REINDEX = "auxio_reindex" 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" const val KEY_SEARCH_FILTER_MODE = "KEY_SEARCH_FILTER"

View file

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

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

View file

@ -7,7 +7,7 @@
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<TextView <TextView
android:id="@+id/excluded_path" android:id="@+id/dir_path"
style="@style/Widget.Auxio.TextView.Item.Primary" style="@style/Widget.Auxio.TextView.Item.Primary"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -19,18 +19,18 @@
android:paddingBottom="@dimen/spacing_small" android:paddingBottom="@dimen/spacing_small"
android:textAppearance="@style/TextAppearance.Auxio.BodyLarge" android:textAppearance="@style/TextAppearance.Auxio.BodyLarge"
app:layout_constraintBottom_toBottomOf="parent" 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_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="/storage/emulated/0/directory" /> tools:text="/storage/emulated/0/directory" />
<Button <Button
android:id="@+id/excluded_clear" android:id="@+id/dir_delete"
style="@style/Widget.Auxio.Button.Icon.Small" style="@style/Widget.Auxio.Button.Icon.Small"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/spacing_button_dialog" 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:icon="@drawable/ic_delete"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View file

@ -99,8 +99,6 @@
<string name="set_content">محتوى</string> <string name="set_content">محتوى</string>
<string name="set_save">حفظ حالة التشغيل</string> <string name="set_save">حفظ حالة التشغيل</string>
<string name="set_save_desc">حفظ حالة التشغيل الحالية الآن</string> <string name="set_save_desc">حفظ حالة التشغيل الحالية الآن</string>
<string name="set_excluded">استبعاد مجلدات</string>
<string name="set_excluded_desc">محتوى المجلدات المستبعدة يتم اخفائها من مكتبتك</string>
<!-- Error Namespace | Error Labels --> <!-- Error Namespace | Error Labels -->
<string name="err_no_music">لم يتم ايجاد موسيقى</string> <string name="err_no_music">لم يتم ايجاد موسيقى</string>
@ -126,7 +124,7 @@
<string name="desc_queue_handle">نقل اغنية من الطابور</string> <string name="desc_queue_handle">نقل اغنية من الطابور</string>
<string name="desc_tab_handle">تحريك التبويت</string> <string name="desc_tab_handle">تحريك التبويت</string>
<string name="desc_clear_search">إزالة كلمة البحث</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_auxio_icon">ايقونة اوكسيو</string>
<string name="desc_no_cover">غلاف الالبوم</string> <string name="desc_no_cover">غلاف الالبوم</string>

View file

@ -117,8 +117,6 @@
<string name="set_save_desc">Uložit aktuální stav přehrávání</string> <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">Znovu načíst hudbu</string>
<string name="set_reindex_desc">Aplikace bude restartována</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 --> <!-- Error Namespace | Error Labels -->
<string name="err_no_music">Nenalezena žádná hudba</string> <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_queue_handle">Přesunout tuto skladbu ve frontě</string>
<string name="desc_tab_handle">Přesunout tuto kartu</string> <string name="desc_tab_handle">Přesunout tuto kartu</string>
<string name="desc_clear_search">Vymazat hledání</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_auxio_icon">Ikona Auxio</string>
<string name="desc_no_cover">Obal alba</string> <string name="desc_no_cover">Obal alba</string>

View file

@ -95,8 +95,6 @@
<string name="set_save_desc">Den aktuellen Wiedergabezustand speichern</string> <string name="set_save_desc">Den aktuellen Wiedergabezustand speichern</string>
<string name="set_reindex">Musik neu laden</string> <string name="set_reindex">Musik neu laden</string>
<string name="set_reindex_desc">Startet die App neu</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 --> <!-- Error Namespace | Error Labels -->
<string name="err_no_music">Keine Musik gefunden</string> <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="set_repeat_pause_desc">Pausieren, wenn ein Song wiederholt wird</string>
<string name="desc_shuffle">Zufällig an- oder ausschalten</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_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="desc_no_cover">Albumcover</string>
<string name="def_playback">Keine Musik wird gespielt</string> <string name="def_playback">Keine Musik wird gespielt</string>
<string name="def_widget_song">Liedname</string> <string name="def_widget_song">Liedname</string>

View file

@ -101,8 +101,6 @@
<string name="set_save_desc">Guardar el estado de reproduccion ahora</string> <string name="set_save_desc">Guardar el estado de reproduccion ahora</string>
<string name="set_reindex">Recargar música</string> <string name="set_reindex">Recargar música</string>
<string name="set_reindex_desc">Se reiniciará la aplicación</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 --> <!-- Error Namespace | Error Labels -->
<string name="err_no_music">Sin música</string> <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_queue_handle">Mover canción en la cola</string>
<string name="desc_tab_handle">Mover pestaña</string> <string name="desc_tab_handle">Mover pestaña</string>
<string name="desc_clear_search">Borrar historial de búsqueda</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_auxio_icon">Icono de Auxio</string>
<string name="desc_no_cover">Carátula de álbum</string> <string name="desc_no_cover">Carátula de álbum</string>

View file

@ -102,8 +102,6 @@
<string name="set_save_desc">Salva lo stato di riproduzione corrente</string> <string name="set_save_desc">Salva lo stato di riproduzione corrente</string>
<string name="set_reindex">Ricarica musica</string> <string name="set_reindex">Ricarica musica</string>
<string name="set_reindex_desc">L\'applicazione sarà riavviata</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> <string name="lbl_off">Spento</string>
@ -132,7 +130,7 @@
<string name="desc_queue_handle">Muove questa canzone della coda</string> <string name="desc_queue_handle">Muove questa canzone della coda</string>
<string name="desc_tab_handle">Muove questa scheda</string> <string name="desc_tab_handle">Muove questa scheda</string>
<string name="desc_clear_search">Cancella la query di ricerca</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_auxio_icon">Icona Auxio</string>
<string name="desc_no_cover">Copertina disco</string> <string name="desc_no_cover">Copertina disco</string>

View file

@ -115,8 +115,6 @@
<string name="set_save_desc">현재 재생 상태를 지금 저장</string> <string name="set_save_desc">현재 재생 상태를 지금 저장</string>
<string name="set_reindex">음악 다시 불러오기</string> <string name="set_reindex">음악 다시 불러오기</string>
<string name="set_reindex_desc">앱이 다시 시작됩니다.</string> <string name="set_reindex_desc">앱이 다시 시작됩니다.</string>
<string name="set_excluded">폴더 제외</string>
<string name="set_excluded_desc">제외한 폴더는 라이브러리에서 숨겨집니다.</string>
<!-- Error Namespace | Error Labels --> <!-- Error Namespace | Error Labels -->
<string name="err_no_music">음악 없음</string> <string name="err_no_music">음악 없음</string>
@ -144,7 +142,7 @@
<string name="desc_queue_handle">이 대기열의 음악 이동</string> <string name="desc_queue_handle">이 대기열의 음악 이동</string>
<string name="desc_tab_handle">이 탭 이동</string> <string name="desc_tab_handle">이 탭 이동</string>
<string name="desc_clear_search">검색 기록 삭제</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_auxio_icon">Auxio 아이콘</string>
<string name="desc_no_cover">앨범 커버</string> <string name="desc_no_cover">앨범 커버</string>

View file

@ -81,8 +81,6 @@
<string name="set_content">Inhoud</string> <string name="set_content">Inhoud</string>
<string name="set_save">Afspeelstatus opslaan</string> <string name="set_save">Afspeelstatus opslaan</string>
<string name="set_save_desc">Sla de huidige afspeelstatus nu op </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 --> <!-- Error Namespace | Error Labels -->
<string name="err_no_music">Geen muziek aangetroffen</string> <string name="err_no_music">Geen muziek aangetroffen</string>
@ -103,7 +101,7 @@
<string name="desc_change_repeat">Herhaalfunctie wijzigen</string> <string name="desc_change_repeat">Herhaalfunctie wijzigen</string>
<string name="desc_clear_search">Zoekopdracht wissen</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_auxio_icon">Auxio pictogram</string>
<string name="desc_album_cover">Artist Image voor %s</string> <string name="desc_album_cover">Artist Image voor %s</string>

View file

@ -103,8 +103,6 @@
<string name="set_save_desc">Запоминать позицию в треке</string> <string name="set_save_desc">Запоминать позицию в треке</string>
<string name="set_reindex">Перезагрузить музыку</string> <string name="set_reindex">Перезагрузить музыку</string>
<string name="set_reindex_desc">Это перезапустит приложение</string> <string name="set_reindex_desc">Это перезапустит приложение</string>
<string name="set_excluded">Исключённые папки</string>
<string name="set_excluded_desc">Содержимое исключённых папок будет скрыто из библиотеки</string>
<!-- Error Namespace | Error Labels --> <!-- Error Namespace | Error Labels -->
<string name="err_no_music">Треков нет</string> <string name="err_no_music">Треков нет</string>
@ -131,7 +129,7 @@
<string name="desc_queue_handle">Переместить трек в очереди</string> <string name="desc_queue_handle">Переместить трек в очереди</string>
<string name="desc_tab_handle">Переместить вкладку</string> <string name="desc_tab_handle">Переместить вкладку</string>
<string name="desc_clear_search">Очистить поисковый запрос</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_auxio_icon">Иконка Auxio</string>
<string name="desc_no_cover">Обложка альбома</string> <string name="desc_no_cover">Обложка альбома</string>

View file

@ -101,8 +101,6 @@
<string name="set_save_desc">立即保存当前播放状态</string> <string name="set_save_desc">立即保存当前播放状态</string>
<string name="set_reindex">重新加载音乐</string> <string name="set_reindex">重新加载音乐</string>
<string name="set_reindex_desc">将会重启应用</string> <string name="set_reindex_desc">将会重启应用</string>
<string name="set_excluded">排除文件夹</string>
<string name="set_excluded_desc">被排除文件夹的内容将从媒体库中隐藏</string>
<string name="lbl_off">关闭</string> <string name="lbl_off">关闭</string>
@ -131,7 +129,7 @@
<string name="desc_queue_handle">移动队列曲目</string> <string name="desc_queue_handle">移动队列曲目</string>
<string name="desc_tab_handle">移动该标签</string> <string name="desc_tab_handle">移动该标签</string>
<string name="desc_clear_search">清除搜索队列</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_auxio_icon">Auxio 图标</string>
<string name="desc_no_cover">专辑封面</string> <string name="desc_no_cover">专辑封面</string>

View file

@ -126,8 +126,13 @@
<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">Will restart app</string>
<string name="set_excluded">Excluded folders</string> <string name="set_dirs">Music folders</string>
<string name="set_excluded_desc">The content of excluded folders is hidden from your library</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 --> <!-- Error Namespace | Error Labels -->
<string name="err_no_music">No music found</string> <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_queue_handle">Move this queue song</string>
<string name="desc_tab_handle">Move this tab</string> <string name="desc_tab_handle">Move this tab</string>
<string name="desc_clear_search">Clear search query</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_auxio_icon">Auxio icon</string>
<string name="desc_no_cover">Album cover</string> <string name="desc_no_cover">Album cover</string>
@ -176,13 +181,14 @@
<string name="def_widget_artist">Artist Name</string> <string name="def_widget_artist">Artist Name</string>
<!-- Codec Namespace | Format names --> <!-- Codec Namespace | Format names -->
<string name="cdc_mp3">MPEG-1 Layer 3 (MP3)</string> <string name="cdc_mp3">MPEG-1 Layer 3 </string>
<string name="cdc_ogg">OGG</string> <string name="cdc_mp4">MPEG-4 Audio (AAC)</string>
<string name="cdc_ogg_vorbis">OGG Vorbis</string> <string name="cdc_ogg">Ogg</string>
<string name="cdc_ogg_opus">OGG Opus</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_flac">Free Lossless Audio Codec (FLAC)</string>
<string name="cdc_aac">Advanced Audio Coding (AAC)</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> <string name="cdc_wma">Windows Media Audio (WMA)</string>
<!-- Color Label namespace | Accent names --> <!-- Color Label namespace | Accent names -->

View file

@ -235,6 +235,7 @@
<style name="Widget.Auxio.Button.Secondary" parent="Widget.Material3.Button.OutlinedButton"> <style name="Widget.Auxio.Button.Secondary" parent="Widget.Material3.Button.OutlinedButton">
<item name="android:textAppearance">@style/TextAppearance.Auxio.LabelLarger</item> <item name="android:textAppearance">@style/TextAppearance.Auxio.LabelLarger</item>
<item name="strokeColor">?attr/colorOutline</item>
</style> </style>
<style name="Widget.Auxio.FloatingActionButton.PlayPause" parent="Widget.Material3.FloatingActionButton.Secondary"> <style name="Widget.Auxio.FloatingActionButton.PlayPause" parent="Widget.Material3.FloatingActionButton.Secondary">

View file

@ -158,9 +158,9 @@
<Preference <Preference
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:key="auxio_excluded_dirs" app:key="auxio_music_dirs"
app:summary="@string/set_excluded_desc" app:summary="@string/set_dirs_desc"
app:title="@string/set_excluded" /> app:title="@string/set_dirs" />
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View file

@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.6.21' ext.kotlin_version = '1.7.0'
ext.navigation_version = "2.4.2" ext.navigation_version = "2.4.2"
repositories { repositories {