playback: add pre-amp customization
Implement a UI frontend for customizing the ReplayGain pre-amp value. This finally completes Auxio's ReplayGain implementation. Not only that, it also shows how Auxio can use positive ReplayGain values, unlike other apps. As a side-note, this also fiddles with the dialog style somewhat. I got carried away. Resolves #114.
This commit is contained in:
parent
519de0e1d5
commit
21fccf1f31
22 changed files with 294 additions and 49 deletions
|
@ -43,8 +43,10 @@ enum class ReplayGainMode {
|
|||
}
|
||||
}
|
||||
|
||||
/** Represents the ReplayGain pre-amp */
|
||||
/** Represents the ReplayGain pre-amp values. */
|
||||
data class ReplayGainPreAmp(
|
||||
/** The value to use when ReplayGain tags are present. */
|
||||
val with: Float,
|
||||
/** The value to use when ReplayGain tags are not present. */
|
||||
val without: Float,
|
||||
)
|
|
@ -59,8 +59,9 @@ class ReplayGainAudioProcessor : BaseAudioProcessor() {
|
|||
// --- REPLAYGAIN PARSING ---
|
||||
|
||||
/**
|
||||
* Updates the rough volume adjustment for [Metadata] with ReplayGain tags. This is based off
|
||||
* Vanilla Music's implementation.
|
||||
* Updates the rough volume adjustment for [Metadata] with ReplayGain tags. This is
|
||||
* tangentially based off Vanilla Music's implementation, but has diverged to a significant
|
||||
* extent.
|
||||
*/
|
||||
fun applyReplayGain(metadata: Metadata?) {
|
||||
if (settingsManager.replayGainMode == ReplayGainMode.OFF) {
|
||||
|
@ -102,10 +103,11 @@ class ReplayGainAudioProcessor : BaseAudioProcessor() {
|
|||
gain.track
|
||||
}
|
||||
|
||||
// Apply the "With tags" adjustment
|
||||
// Apply the adjustment specified when there is ReplayGain tags.
|
||||
resolvedGain + preAmp.with
|
||||
} else {
|
||||
// No gain tags were present, just apply the adjustment without tags.
|
||||
// No ReplayGain tags existed, or no tags were parsable, or there was no metadata
|
||||
// in the first place. Return the gain to use when there is no ReplayGain value.
|
||||
logD("No ReplayGain tags present ")
|
||||
preAmp.without
|
||||
}
|
||||
|
@ -157,7 +159,8 @@ class ReplayGainAudioProcessor : BaseAudioProcessor() {
|
|||
found = true
|
||||
}
|
||||
|
||||
// Case 2: R128 ReplayGain, most commonly found on FLAC files.
|
||||
// Case 2: R128 ReplayGain, most commonly found on FLAC files and other lossless
|
||||
// encodings to increase precision in volume adjustments.
|
||||
// While technically there is the R128 base gain in Opus files, that is automatically
|
||||
// applied by the media framework [which ExoPlayer relies on]. The only reason we would
|
||||
// want to read it is to zero previous ReplayGain values for being invalid, however there
|
||||
|
@ -215,8 +218,8 @@ class ReplayGainAudioProcessor : BaseAudioProcessor() {
|
|||
} else {
|
||||
for (i in position until limit step 2) {
|
||||
// Ensure we clamp the values to the minimum and maximum values possible
|
||||
// for the encoding. This prevents issues where samples amplified beyond
|
||||
// 1 << 16 will end up becoming truncated during the conversion to a short,
|
||||
// for the encoding. This prevents issues where samples amplified beyond
|
||||
// 1 << 16 will end up becoming truncated during the conversion to a short,
|
||||
// resulting in popping.
|
||||
var sample = inputBuffer.getLeShort(i)
|
||||
sample =
|
||||
|
@ -232,10 +235,17 @@ class ReplayGainAudioProcessor : BaseAudioProcessor() {
|
|||
buffer.flip()
|
||||
}
|
||||
|
||||
// Normally, ByteBuffer endianness is determined by object state, which is possibly
|
||||
// the most java thing I have ever heard. Instead of mutating that state and accidentally
|
||||
// breaking downstream parsers of audio data, we have our own methods to always parse a
|
||||
// little-endian value.
|
||||
|
||||
/** Always get a little-endian short value from a [ByteBuffer] */
|
||||
private fun ByteBuffer.getLeShort(at: Int): Short {
|
||||
return get(at + 1).toInt().shl(8).or(get(at).toInt().and(0xFF)).toShort()
|
||||
}
|
||||
|
||||
/** Always place a little-endian short value into a [ByteBuffer]. */
|
||||
private fun ByteBuffer.putLeShort(short: Short) {
|
||||
put(short.toByte())
|
||||
put(short.toInt().shr(8).toByte())
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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.playback.replaygain
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import kotlin.math.abs
|
||||
import org.oxycblt.auxio.BuildConfig
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.databinding.DialogPreAmpBinding
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.ui.ViewBindingDialogFragment
|
||||
import org.oxycblt.auxio.util.textSafe
|
||||
|
||||
/**
|
||||
* The dialog for customizing the ReplayGain pre-amp values.
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class ReplayGainDialog : ViewBindingDialogFragment<DialogPreAmpBinding>() {
|
||||
private val settingsManager = SettingsManager.getInstance()
|
||||
|
||||
override fun onCreateBinding(inflater: LayoutInflater) = DialogPreAmpBinding.inflate(inflater)
|
||||
|
||||
override fun onConfigDialog(builder: AlertDialog.Builder) {
|
||||
builder
|
||||
.setTitle(R.string.set_pre_amp)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
val binding = requireBinding()
|
||||
settingsManager.replayGainPreAmp =
|
||||
ReplayGainPreAmp(binding.withTagsSlider.value, binding.withoutTagsSlider.value)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
}
|
||||
|
||||
override fun onBindingCreated(binding: DialogPreAmpBinding, savedInstanceState: Bundle?) {
|
||||
if (savedInstanceState == null) {
|
||||
// First initialization, we need to supply the sliders with the values from
|
||||
// settings. After this, the sliders save their own state, so we do not need to
|
||||
// do any restore behavior.
|
||||
val preAmp = settingsManager.replayGainPreAmp
|
||||
binding.withTagsSlider.value = preAmp.with
|
||||
binding.withoutTagsSlider.value = preAmp.without
|
||||
}
|
||||
|
||||
// The listener always fires when the Slider restores it's own state, *except* when
|
||||
// it's at it's default value. Then it doesn't. Just initialize the ticker ourselves.
|
||||
updateTicker(binding.withTagsTicker, binding.withTagsSlider.value)
|
||||
binding.withTagsSlider.addOnChangeListener { _, value, _ ->
|
||||
updateTicker(binding.withTagsTicker, value)
|
||||
}
|
||||
|
||||
updateTicker(binding.withoutTagsTicker, binding.withoutTagsSlider.value)
|
||||
binding.withoutTagsSlider.addOnChangeListener { _, value, _ ->
|
||||
updateTicker(binding.withoutTagsTicker, value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTicker(ticker: TextView, valueDb: Float) {
|
||||
// It is more clear to prepend a +/- before the pre-amp value to make it easier to
|
||||
// gauge how much it may be increasing the volume, however android does not add +
|
||||
// to positive float values when formatting them in a string. Instead, add it ourselves.
|
||||
ticker.textSafe =
|
||||
if (valueDb >= 0) {
|
||||
getString(R.string.fmt_db_pos, valueDb)
|
||||
} else {
|
||||
getString(R.string.fmt_db_neg, abs(valueDb))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = BuildConfig.APPLICATION_ID + ".tag.PRE_AMP_CUSTOMIZE"
|
||||
}
|
||||
}
|
|
@ -35,7 +35,6 @@ import org.oxycblt.auxio.music.MusicParent
|
|||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.playback.state.RepeatMode
|
||||
import org.oxycblt.auxio.util.getSystemServiceSafe
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.newBroadcastIntent
|
||||
import org.oxycblt.auxio.util.newMainIntent
|
||||
|
||||
|
|
|
@ -33,6 +33,8 @@ import org.oxycblt.auxio.R
|
|||
import org.oxycblt.auxio.home.tabs.TabCustomizeDialog
|
||||
import org.oxycblt.auxio.music.excluded.ExcludedDialog
|
||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.playback.replaygain.ReplayGainDialog
|
||||
import org.oxycblt.auxio.playback.replaygain.ReplayGainMode
|
||||
import org.oxycblt.auxio.settings.pref.IntListPreference
|
||||
import org.oxycblt.auxio.settings.pref.IntListPreferenceDialog
|
||||
import org.oxycblt.auxio.ui.accent.AccentCustomizeDialog
|
||||
|
@ -88,6 +90,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
|||
// to adequately supply a "target fragment" (otherwise we will crash since the dialog
|
||||
// requires one), and then we need to actually show the dialog, making sure we use
|
||||
// the parent FragmentManager as again, it will crash if we don't.
|
||||
//
|
||||
// Fragments were a mistake.
|
||||
val dialog = IntListPreferenceDialog.from(preference)
|
||||
dialog.setTargetFragment(this, 0)
|
||||
|
@ -150,7 +153,22 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
|||
onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _, _ ->
|
||||
Coil.imageLoader(requireContext()).apply { this.memoryCache?.clear() }
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
SettingsManager.KEY_REPLAY_GAIN -> {
|
||||
notifyDependencyChange(settingsManager.replayGainMode == ReplayGainMode.OFF)
|
||||
onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _, value ->
|
||||
notifyDependencyChange(
|
||||
ReplayGainMode.fromIntCode(value as Int) == ReplayGainMode.OFF)
|
||||
true
|
||||
}
|
||||
}
|
||||
SettingsManager.KEY_PRE_AMP -> {
|
||||
onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
ReplayGainDialog().show(childFragmentManager, ReplayGainDialog.TAG)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,9 +23,9 @@ 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.playback.state.PlaybackMode
|
||||
import org.oxycblt.auxio.playback.replaygain.ReplayGainMode
|
||||
import org.oxycblt.auxio.playback.replaygain.ReplayGainPreAmp
|
||||
import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||
import org.oxycblt.auxio.ui.DisplayMode
|
||||
import org.oxycblt.auxio.ui.Sort
|
||||
import org.oxycblt.auxio.ui.accent.Accent
|
||||
|
@ -109,12 +109,11 @@ class SettingsManager private constructor(context: Context) :
|
|||
var replayGainPreAmp: ReplayGainPreAmp
|
||||
get() =
|
||||
ReplayGainPreAmp(
|
||||
prefs.getFloat(KEY_REPLAY_GAIN_PRE_AMP_WITH, 0f),
|
||||
prefs.getFloat(KEY_REPLAY_GAIN_PRE_AMP_WITHOUT, 0f))
|
||||
prefs.getFloat(KEY_PRE_AMP_WITH, 0f), prefs.getFloat(KEY_PRE_AMP_WITHOUT, 0f))
|
||||
set(value) {
|
||||
prefs.edit {
|
||||
putFloat(KEY_REPLAY_GAIN_PRE_AMP_WITH, value.with)
|
||||
putFloat(KEY_REPLAY_GAIN_PRE_AMP_WITHOUT, value.without)
|
||||
putFloat(KEY_PRE_AMP_WITH, value.with)
|
||||
putFloat(KEY_PRE_AMP_WITHOUT, value.without)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
@ -258,7 +257,7 @@ class SettingsManager private constructor(context: Context) :
|
|||
KEY_USE_ALT_NOTIFICATION_ACTION -> callbacks.forEach { it.onNotifSettingsChanged() }
|
||||
KEY_SHOW_COVERS, KEY_QUALITY_COVERS -> callbacks.forEach { it.onCoverSettingsChanged() }
|
||||
KEY_LIB_TABS -> callbacks.forEach { it.onLibraryChanged() }
|
||||
KEY_REPLAY_GAIN, KEY_REPLAY_GAIN_PRE_AMP_WITH, KEY_REPLAY_GAIN_PRE_AMP_WITHOUT ->
|
||||
KEY_REPLAY_GAIN, KEY_PRE_AMP_WITH, KEY_PRE_AMP_WITHOUT ->
|
||||
callbacks.forEach { it.onReplayGainSettingsChanged() }
|
||||
}
|
||||
}
|
||||
|
@ -290,9 +289,9 @@ class SettingsManager private constructor(context: Context) :
|
|||
|
||||
const val KEY_HEADSET_AUTOPLAY = "auxio_headset_autoplay"
|
||||
const val KEY_REPLAY_GAIN = "auxio_replay_gain"
|
||||
const val KEY_REPLAY_GAIN_PRE_AMP = "auxio_pre_amp"
|
||||
const val KEY_REPLAY_GAIN_PRE_AMP_WITH = "auxio_pre_amp_with"
|
||||
const val KEY_REPLAY_GAIN_PRE_AMP_WITHOUT = "auxio_pre_amp_without"
|
||||
const val KEY_PRE_AMP = "auxio_pre_amp"
|
||||
const val KEY_PRE_AMP_WITH = "auxio_pre_amp_with"
|
||||
const val KEY_PRE_AMP_WITHOUT = "auxio_pre_amp_without"
|
||||
|
||||
const val KEY_SONG_PLAYBACK_MODE = "KEY_SONG_PLAY_MODE2"
|
||||
const val KEY_KEEP_SHUFFLE = "KEY_KEEP_SHUFFLE"
|
||||
|
|
|
@ -40,20 +40,20 @@ class AccentCustomizeDialog :
|
|||
override fun onCreateBinding(inflater: LayoutInflater) = DialogAccentBinding.inflate(inflater)
|
||||
|
||||
override fun onConfigDialog(builder: AlertDialog.Builder) {
|
||||
builder.setTitle(R.string.set_accent)
|
||||
builder
|
||||
.setTitle(R.string.set_accent)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
if (accentAdapter.selectedAccent != settingsManager.accent) {
|
||||
logD("Applying new accent")
|
||||
settingsManager.accent = unlikelyToBeNull(accentAdapter.selectedAccent)
|
||||
requireActivity().recreate()
|
||||
}
|
||||
|
||||
builder.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
if (accentAdapter.selectedAccent != settingsManager.accent) {
|
||||
logD("Applying new accent")
|
||||
settingsManager.accent = unlikelyToBeNull(accentAdapter.selectedAccent)
|
||||
requireActivity().recreate()
|
||||
dismiss()
|
||||
}
|
||||
|
||||
dismiss()
|
||||
}
|
||||
|
||||
// Negative button just dismisses, no need for a listener.
|
||||
builder.setNegativeButton(android.R.string.cancel, null)
|
||||
// Negative button just dismisses, no need for a listener.
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
}
|
||||
|
||||
override fun onBindingCreated(binding: DialogAccentBinding, savedInstanceState: Bundle?) {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="@dimen/spacing_small">
|
||||
android:paddingTop="@dimen/spacing_medium">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/excluded_recycler"
|
||||
|
@ -22,7 +22,10 @@
|
|||
android:id="@+id/excluded_empty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/spacing_medium"
|
||||
android:paddingTop="@dimen/spacing_medium"
|
||||
android:paddingStart="@dimen/spacing_medium"
|
||||
android:paddingEnd="@dimen/spacing_medium"
|
||||
android:paddingBottom="@dimen/spacing_medium"
|
||||
android:text="@string/lbl_no_dirs"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.Auxio.TitleMidLarge"
|
||||
|
|
103
app/src/main/res/layout/dialog_pre_amp.xml
Normal file
103
app/src/main/res/layout/dialog_pre_amp.xml
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/with_tags_header"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_medium"
|
||||
android:layout_marginStart="@dimen/spacing_mid_large"
|
||||
android:text="@string/set_pre_amp_with"
|
||||
android:textAppearance="@style/TextAppearance.Auxio.TitleMedium"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/with_tags_slider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:stepSize="0.1"
|
||||
android:valueFrom="-15.0"
|
||||
android:valueTo="15.0"
|
||||
app:labelBehavior="gone"
|
||||
app:thumbRadius="@dimen/slider_thumb_radius"
|
||||
app:haloRadius="@dimen/slider_halo_radius"
|
||||
app:tickVisible="false"
|
||||
android:layout_marginStart="@dimen/spacing_small"
|
||||
app:layout_constraintEnd_toStartOf="@+id/with_tags_ticker"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/with_tags_header"
|
||||
tools:value="0.0" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/with_tags_ticker"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="@dimen/size_btn_large"
|
||||
android:layout_marginEnd="@dimen/spacing_mid_large"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.Auxio.BodyMedium"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/with_tags_slider"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/with_tags_slider"
|
||||
tools:text="+1.6 dB" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/without_tags_header"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_medium"
|
||||
android:layout_marginStart="@dimen/spacing_mid_large"
|
||||
android:text="@string/set_pre_amp_without"
|
||||
android:textAppearance="@style/TextAppearance.Auxio.TitleMedium"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/with_tags_slider" />
|
||||
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/without_tags_slider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:stepSize="0.1"
|
||||
android:valueFrom="-15.0"
|
||||
android:valueTo="15.0"
|
||||
app:thumbRadius="@dimen/slider_thumb_radius"
|
||||
app:haloRadius="@dimen/slider_halo_radius"
|
||||
android:layout_marginStart="@dimen/spacing_small"
|
||||
app:tickVisible="false"
|
||||
app:labelBehavior="gone"
|
||||
app:layout_constraintEnd_toStartOf="@+id/without_tags_ticker"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/without_tags_header"
|
||||
tools:value="0.0" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/without_tags_ticker"
|
||||
android:layout_width="0dp"
|
||||
android:gravity="center"
|
||||
android:minWidth="@dimen/size_btn_large"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.Auxio.BodyMedium"
|
||||
android:layout_marginEnd="@dimen/spacing_mid_large"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/without_tags_slider"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/without_tags_slider"
|
||||
tools:text="+1.6 dB" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pre_amp_notice"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_medium"
|
||||
android:text="@string/set_pre_amp_warning"
|
||||
android:layout_marginStart="@dimen/spacing_mid_large"
|
||||
android:layout_marginEnd="@dimen/spacing_mid_large"
|
||||
android:textAppearance="@style/TextAppearance.Auxio.BodySmall"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/without_tags_slider" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -6,7 +6,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:overScrollMode="never"
|
||||
android:paddingTop="@dimen/spacing_small"
|
||||
android:paddingTop="@dimen/spacing_medium"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintBottom_toTopOf="@+id/accent_cancel"
|
||||
app:layout_constraintTop_toBottomOf="@+id/accent_header"
|
||||
|
|
|
@ -3,11 +3,8 @@
|
|||
<androidx.constraintlayout.widget.ConstraintLayout 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"
|
||||
style="@style/Widget.Auxio.ItemLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:padding="0dp">
|
||||
|
||||
<TextView
|
||||
|
@ -15,7 +12,10 @@
|
|||
style="@style/Widget.Auxio.TextView.Item.Primary"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/spacing_medium"
|
||||
android:layout_marginStart="@dimen/spacing_mid_large"
|
||||
android:layout_marginEnd="@dimen/spacing_medium"
|
||||
android:paddingTop="@dimen/spacing_small"
|
||||
android:paddingBottom="@dimen/spacing_small"
|
||||
android:gravity="center"
|
||||
android:maxLines="@null"
|
||||
android:textAppearance="@style/TextAppearance.Auxio.BodyLarge"
|
||||
|
@ -29,11 +29,15 @@
|
|||
android:id="@+id/excluded_clear"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/spacing_small"
|
||||
android:background="@drawable/ui_unbounded_ripple"
|
||||
android:contentDescription="@string/desc_blacklist_delete"
|
||||
android:minWidth="@dimen/size_btn_small"
|
||||
android:minHeight="@dimen/size_btn_small"
|
||||
android:paddingTop="@dimen/spacing_small"
|
||||
android:paddingBottom="@dimen/spacing_small"
|
||||
android:paddingEnd="@dimen/spacing_medium"
|
||||
android:paddingStart="@dimen/spacing_medium"
|
||||
android:layout_marginEnd="@dimen/spacing_small"
|
||||
android:src="@drawable/ic_clear"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
|
|
@ -14,9 +14,9 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/spacing_medium"
|
||||
android:clickable="false"
|
||||
android:layout_marginTop="@dimen/spacing_small"
|
||||
android:layout_marginBottom="@dimen/spacing_small"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:paddingStart="@dimen/spacing_medium"
|
||||
android:textAppearance="@style/TextAppearance.Auxio.BodyLarge"
|
||||
|
@ -39,6 +39,7 @@
|
|||
android:minHeight="@dimen/size_btn_small"
|
||||
android:paddingStart="@dimen/spacing_medium"
|
||||
android:paddingEnd="@dimen/spacing_medium"
|
||||
android:layout_marginEnd="@dimen/spacing_small"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_handle"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/tab_icon"
|
||||
|
|
|
@ -81,7 +81,7 @@
|
|||
<string name="set_alt_shuffle">تفضيل نشاط الخلط</string>
|
||||
|
||||
<string name="set_audio">صوتيات</string>
|
||||
<string name="set_replay_gain">صخب الصوت (تجريبي)</string>
|
||||
<string name="set_replay_gain">صخب الصوت</string>
|
||||
<string name="set_off">اطفاء</string>
|
||||
<string name="set_replay_gain_track">تفضيل المقطع</string>
|
||||
<string name="set_replay_gain_album">تفضيل الالبوم</string>
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
<string name="set_audio">Audio</string>
|
||||
<string name="set_headset_autoplay">Kopfhörer automatische Wiedergabe</string>
|
||||
<string name="set_headset_autoplay_desc">Beginne die Wiedergabe immer, wenn Kopfhörer verbunden sind (funktioniert nicht auf allen Geräten)</string>
|
||||
<string name="set_replay_gain">ReplayGain (Experimentell)</string>
|
||||
<string name="set_replay_gain">ReplayGain</string>
|
||||
<string name="set_off">Aus</string>
|
||||
<string name="set_replay_gain_track">Titel bevorzugen</string>
|
||||
<string name="set_replay_gain_album">Album bevorzugen</string>
|
||||
|
|
|
@ -81,7 +81,7 @@
|
|||
<string name="set_alt_shuffle">Preferir acción de mezcla</string>
|
||||
|
||||
<string name="set_audio">Sonido</string>
|
||||
<string name="set_replay_gain">ReplayGain (Experimental)</string>
|
||||
<string name="set_replay_gain">ReplayGain</string>
|
||||
<string name="set_off">Desactivado</string>
|
||||
<string name="set_replay_gain_track">Por pista</string>
|
||||
<string name="set_replay_gain_album">Por álbum</string>
|
||||
|
|
|
@ -83,7 +83,7 @@
|
|||
<string name="set_audio">Audio</string>
|
||||
<string name="set_headset_autoplay">Autoplay cuffie</string>
|
||||
<string name="set_headset_autoplay_desc">Comincia la riproduzione ogni volta che le cuffie sono inserite (potrebbe non funzionare su tutti i dispositivi)</string>
|
||||
<string name="set_replay_gain">Replay Gain (Sperimentale)</string>
|
||||
<string name="set_replay_gain">Replay Gain</string>
|
||||
<string name="set_replay_gain_track">Preferisci traccia</string>
|
||||
<string name="set_replay_gain_album">Preferisci disco</string>
|
||||
<string name="set_replay_gain_dynamic">Dinamico</string>
|
||||
|
|
|
@ -83,7 +83,7 @@
|
|||
<string name="set_audio">Звук</string>
|
||||
<string name="set_headset_autoplay">Воспроизводить при подключении</string>
|
||||
<string name="set_headset_autoplay_desc">Всегда начинать воспроизведение при подключении наушников (может работать не на всех устройствах)</string>
|
||||
<string name="set_replay_gain">ReplayGain (экспериментально)</string>
|
||||
<string name="set_replay_gain">ReplayGain</string>
|
||||
<string name="set_off">Выкл.</string>
|
||||
<string name="set_replay_gain_track">По треку</string>
|
||||
<string name="set_replay_gain_album">По альбому</string>
|
||||
|
|
|
@ -82,7 +82,7 @@
|
|||
<string name="set_audio">音频</string>
|
||||
<string name="set_headset_autoplay">自动播放</string>
|
||||
<string name="set_headset_autoplay_desc">连接至耳机时总是自动播放(并非在所有设备上都有用)</string>
|
||||
<string name="set_replay_gain">回放增益(实验性)</string>
|
||||
<string name="set_replay_gain">回放增益</string>
|
||||
<string name="set_replay_gain_track">偏好曲目</string>
|
||||
<string name="set_replay_gain_album">偏好专辑</string>
|
||||
<string name="set_replay_gain_dynamic">动态</string>
|
||||
|
|
|
@ -42,8 +42,6 @@
|
|||
|
||||
<dimen name="slider_thumb_radius">6dp</dimen>
|
||||
<dimen name="slider_halo_radius">16dp</dimen>
|
||||
<dimen name="slider_thumb_radius_collapsed">6dp</dimen>
|
||||
<dimen name="slider_thumb_radius_expanded">10dp</dimen>
|
||||
|
||||
<dimen name="recycler_fab_space_normal">88dp</dimen>
|
||||
<dimen name="recycler_fab_space_large">128dp</dimen>
|
||||
|
|
|
@ -87,10 +87,15 @@
|
|||
<string name="set_audio">Audio</string>
|
||||
<string name="set_headset_autoplay">Headset autoplay</string>
|
||||
<string name="set_headset_autoplay_desc">Always start playing when a headset is connected (may not work on all devices)</string>
|
||||
<string name="set_replay_gain">ReplayGain (Experimental)</string>
|
||||
<string name="set_replay_gain">ReplayGain</string>
|
||||
<string name="set_replay_gain_track">Prefer track</string>
|
||||
<string name="set_replay_gain_album">Prefer album</string>
|
||||
<string name="set_replay_gain_dynamic">Dynamic</string>
|
||||
<string name="set_pre_amp">ReplayGain pre-amp</string>
|
||||
<string name="set_pre_amp_desc">The pre-amp is applied to the existing adjustment during playback</string>
|
||||
<string name="set_pre_amp_with">Adjustment with tags</string>
|
||||
<string name="set_pre_amp_without">Adjustment without tags</string>
|
||||
<string name="set_pre_amp_warning">Warning: Changing the pre-amp to a high positive value may result in peaking on some audio tracks.</string>
|
||||
|
||||
<string name="set_behavior">Behavior</string>
|
||||
<string name="set_song_mode">When a song is selected</string>
|
||||
|
@ -109,6 +114,7 @@
|
|||
<string name="set_excluded">Excluded folders</string>
|
||||
<string name="set_excluded_desc">The content of excluded folders is hidden from your library</string>
|
||||
|
||||
<!-- TODO: Phase out android strings for in-house strings -->
|
||||
<string name="set_off">Off</string>
|
||||
|
||||
<!-- Error Namespace | Error Labels -->
|
||||
|
@ -173,6 +179,10 @@
|
|||
|
||||
<!-- Format Namespace | Value formatting/plurals -->
|
||||
<string name="fmt_disc_no">Disc %d</string>
|
||||
|
||||
<string name="fmt_db_pos">+%.1f dB</string>
|
||||
<string name="fmt_db_neg">-%.1f dB</string>
|
||||
|
||||
<string name="fmt_songs_loaded">Songs loaded: %d</string>
|
||||
<string name="fmt_albums_loaded">Albums loaded: %d</string>
|
||||
<string name="fmt_artists_loaded">Artists loaded: %d</string>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
A dialog theme that doesn't suck. This is the only non-Material3 usage in the entire
|
||||
project since the Material3 dialogs [and especially the button panel] are unusable.
|
||||
-->
|
||||
<style name="Theme.Auxio.Dialog" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog">
|
||||
<style name="Theme.Auxio.Dialog" parent="ThemeOverlay.Material3.MaterialAlertDialog">
|
||||
<item name="android:checkedTextViewStyle">@style/Widget.Auxio.Dialog.CheckedTextView</item>
|
||||
<item name="materialAlertDialogTitleTextStyle">@style/Widget.Auxio.Dialog.TextView</item>
|
||||
<item name="buttonBarPositiveButtonStyle">@style/Widget.Material3.Button.TextButton.Dialog
|
||||
|
|
|
@ -94,6 +94,14 @@
|
|||
app:key="auxio_replay_gain"
|
||||
app:title="@string/set_replay_gain" />
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="auxio_pre_amp"
|
||||
app:allowDividerBelow="false"
|
||||
app:title="@string/set_pre_amp"
|
||||
app:dependency="auxio_replay_gain"
|
||||
app:summary="@string/set_pre_amp_desc"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
|
|
Loading…
Reference in a new issue