settings: do not use sharedpreference listener

Switch back to using settings-specific listeners rather than the
SharedPreference listener.

Again, this is due to the need to decouple android code from settings.
It also allows us to fully obscure the details of what settings we are
actually working with.
This commit is contained in:
Alexander Capehart 2023-01-06 19:20:56 -07:00
parent 1b19b698a1
commit ac9f50c0a0
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
19 changed files with 270 additions and 212 deletions

View file

@ -4,11 +4,13 @@
#### What's Improved
- Added ability to edit previously played or currently playing items in the queue
- Added support for date values formatted as "YYYYMMDD"
#### What's Fixed
- Fixed unreliable ReplayGain adjustment application in certain situations
- Fixed crash that would occur in music folders dialog when user does not have a working
file manager
- Fixed notification not updating due to settings changes
#### What's Changed
- Implemented new queue system

View file

@ -28,30 +28,44 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
* User configuration specific to the home UI.
* @author Alexander Capehart (OxygenCobalt)
*/
interface HomeSettings : Settings {
interface HomeSettings : Settings<HomeSettings.Listener> {
/** The tabs to show in the home UI. */
var homeTabs: Array<Tab>
/** Whether to hide artists considered "collaborators" from the home UI. */
val shouldHideCollaborators: Boolean
private class Real(context: Context) : Settings.Real(context), HomeSettings {
interface Listener {
/** Called when the [homeTabs] configuration changes. */
fun onTabsChanged()
/** Called when the [shouldHideCollaborators] configuration changes. */
fun onHideCollaboratorsChanged()
}
private class Real(context: Context) : Settings.Real<Listener>(context), HomeSettings {
override var homeTabs: Array<Tab>
get() =
Tab.fromIntCode(
sharedPreferences.getInt(
context.getString(R.string.set_key_lib_tabs), Tab.SEQUENCE_DEFAULT))
getString(R.string.set_key_home_tabs), Tab.SEQUENCE_DEFAULT))
?: unlikelyToBeNull(Tab.fromIntCode(Tab.SEQUENCE_DEFAULT))
set(value) {
sharedPreferences.edit {
putInt(context.getString(R.string.set_key_lib_tabs), Tab.toIntCode(value))
putInt(getString(R.string.set_key_home_tabs), Tab.toIntCode(value))
apply()
}
}
override val shouldHideCollaborators: Boolean
get() =
sharedPreferences.getBoolean(
context.getString(R.string.set_key_hide_collaborators), false)
sharedPreferences.getBoolean(getString(R.string.set_key_hide_collaborators), false)
override fun onSettingChanged(key: String, listener: Listener) {
when (key) {
getString(R.string.set_key_home_tabs) -> listener.onTabsChanged()
getString(R.string.set_key_hide_collaborators) ->
listener.onHideCollaboratorsChanged()
}
}
}
companion object {

View file

@ -18,17 +18,14 @@
package org.oxycblt.auxio.home
import android.app.Application
import android.content.SharedPreferences
import androidx.lifecycle.AndroidViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.oxycblt.auxio.R
import org.oxycblt.auxio.home.tabs.Tab
import org.oxycblt.auxio.music.*
import org.oxycblt.auxio.music.Library
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Sort
import org.oxycblt.auxio.util.context
import org.oxycblt.auxio.util.logD
/**
@ -36,9 +33,7 @@ import org.oxycblt.auxio.util.logD
* @author Alexander Capehart (OxygenCobalt)
*/
class HomeViewModel(application: Application) :
AndroidViewModel(application),
MusicStore.Listener,
SharedPreferences.OnSharedPreferenceChangeListener {
AndroidViewModel(application), MusicStore.Listener, HomeSettings.Listener {
private val musicStore = MusicStore.getInstance()
private val homeSettings = HomeSettings.from(application)
private val musicSettings = MusicSettings.from(application)
@ -92,13 +87,13 @@ class HomeViewModel(application: Application) :
init {
musicStore.addListener(this)
homeSettings.addListener(this)
homeSettings.registerListener(this)
}
override fun onCleared() {
super.onCleared()
musicStore.removeListener(this)
homeSettings.removeListener(this)
homeSettings.unregisterListener(this)
}
override fun onLibraryChanged(library: Library?) {
@ -120,19 +115,16 @@ class HomeViewModel(application: Application) :
}
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
when (key) {
context.getString(R.string.set_key_lib_tabs) -> {
// Tabs changed, update the current tabs and set up a re-create event.
currentTabModes = makeTabModes()
_shouldRecreate.value = true
}
context.getString(R.string.set_key_hide_collaborators) -> {
// Changes in the hide collaborator setting will change the artist contents
// of the library, consider it a library update.
onLibraryChanged(musicStore.library)
}
}
override fun onTabsChanged() {
// Tabs changed, update the current tabs and set up a re-create event.
currentTabModes = makeTabModes()
_shouldRecreate.value = true
}
override fun onHideCollaboratorsChanged() {
// Changes in the hide collaborator setting will change the artist contents
// of the library, consider it a library update.
onLibraryChanged(musicStore.library)
}
/**

View file

@ -27,16 +27,20 @@ import org.oxycblt.auxio.util.logD
* User configuration specific to image loading.
* @author Alexander Capehart (OxygenCobalt)
*/
interface ImageSettings : Settings {
interface ImageSettings : Settings<ImageSettings.Listener> {
/** The strategy to use when loading album covers. */
val coverMode: CoverMode
private class Real(context: Context) : Settings.Real(context), ImageSettings {
interface Listener {
/** Called when [coverMode] changes. */
fun onCoverModeChanged() {}
}
private class Real(context: Context) : Settings.Real<Listener>(context), ImageSettings {
override val coverMode: CoverMode
get() =
CoverMode.fromIntCode(
sharedPreferences.getInt(
context.getString(R.string.set_key_cover_mode), Int.MIN_VALUE))
sharedPreferences.getInt(getString(R.string.set_key_cover_mode), Int.MIN_VALUE))
?: CoverMode.MEDIA_STORE
override fun migrate() {
@ -54,13 +58,19 @@ interface ImageSettings : Settings {
}
sharedPreferences.edit {
putInt(context.getString(R.string.set_key_cover_mode), mode.intCode)
putInt(getString(R.string.set_key_cover_mode), mode.intCode)
remove(OLD_KEY_SHOW_COVERS)
remove(OLD_KEY_QUALITY_COVERS)
}
}
}
override fun onSettingChanged(key: String, listener: Listener) {
if (key == getString(R.string.set_key_cover_mode)) {
listOf(key, listener)
}
}
private companion object {
const val OLD_KEY_SHOW_COVERS = "KEY_SHOW_COVERS"
const val OLD_KEY_QUALITY_COVERS = "KEY_QUALITY_COVERS"

View file

@ -30,7 +30,7 @@ import org.oxycblt.auxio.util.getSystemServiceCompat
* User configuration specific to music system.
* @author Alexander Capehart (OxygenCobalt)
*/
interface MusicSettings : Settings {
interface MusicSettings : Settings<MusicSettings.Listener> {
/** The configuration on how to handle particular directories in the music library. */
var musicDirs: MusicDirectories
/** Whether to exclude non-music audio files from the music library. */
@ -54,50 +54,51 @@ interface MusicSettings : Settings {
/** The [Sort] mode used in an [Genre]'s [Song] list. */
var genreSongSort: Sort
private class Real(context: Context) : Settings.Real(context), MusicSettings {
interface Listener {
/** Called when a setting controlling how music is loaded has changed. */
fun onIndexingSettingChanged() {}
/** Called when the [shouldBeObserving] configuration has changed. */
fun onObservingChanged() {}
}
private class Real(context: Context) : Settings.Real<Listener>(context), MusicSettings {
private val storageManager = context.getSystemServiceCompat(StorageManager::class)
override var musicDirs: MusicDirectories
get() {
val dirs =
(sharedPreferences.getStringSet(
context.getString(R.string.set_key_music_dirs), null)
(sharedPreferences.getStringSet(getString(R.string.set_key_music_dirs), null)
?: emptySet())
.mapNotNull { Directory.fromDocumentTreeUri(storageManager, it) }
return MusicDirectories(
dirs,
sharedPreferences.getBoolean(
context.getString(R.string.set_key_music_dirs_include), false))
getString(R.string.set_key_music_dirs_include), false))
}
set(value) {
sharedPreferences.edit {
putStringSet(
context.getString(R.string.set_key_music_dirs),
getString(R.string.set_key_music_dirs),
value.dirs.map(Directory::toDocumentTreeUri).toSet())
putBoolean(
context.getString(R.string.set_key_music_dirs_include), value.shouldInclude)
putBoolean(getString(R.string.set_key_music_dirs_include), value.shouldInclude)
apply()
}
}
override val excludeNonMusic: Boolean
get() =
sharedPreferences.getBoolean(
context.getString(R.string.set_key_exclude_non_music), true)
sharedPreferences.getBoolean(getString(R.string.set_key_exclude_non_music), true)
override val shouldBeObserving: Boolean
get() =
sharedPreferences.getBoolean(context.getString(R.string.set_key_observing), false)
get() = sharedPreferences.getBoolean(getString(R.string.set_key_observing), false)
override var multiValueSeparators: String
// Differ from convention and store a string of separator characters instead of an int
// code. This makes it easier to use and more extendable.
get() =
sharedPreferences.getString(context.getString(R.string.set_key_separators), "")
?: ""
get() = sharedPreferences.getString(getString(R.string.set_key_separators), "") ?: ""
set(value) {
sharedPreferences.edit {
putString(context.getString(R.string.set_key_separators), value)
putString(getString(R.string.set_key_separators), value)
apply()
}
}
@ -105,12 +106,11 @@ interface MusicSettings : Settings {
override var songSort: Sort
get() =
Sort.fromIntCode(
sharedPreferences.getInt(
context.getString(R.string.set_key_lib_songs_sort), Int.MIN_VALUE))
sharedPreferences.getInt(getString(R.string.set_key_songs_sort), Int.MIN_VALUE))
?: Sort(Sort.Mode.ByName, true)
set(value) {
sharedPreferences.edit {
putInt(context.getString(R.string.set_key_lib_songs_sort), value.intCode)
putInt(getString(R.string.set_key_songs_sort), value.intCode)
apply()
}
}
@ -119,11 +119,11 @@ interface MusicSettings : Settings {
get() =
Sort.fromIntCode(
sharedPreferences.getInt(
context.getString(R.string.set_key_lib_albums_sort), Int.MIN_VALUE))
getString(R.string.set_key_albums_sort), Int.MIN_VALUE))
?: Sort(Sort.Mode.ByName, true)
set(value) {
sharedPreferences.edit {
putInt(context.getString(R.string.set_key_lib_albums_sort), value.intCode)
putInt(getString(R.string.set_key_albums_sort), value.intCode)
apply()
}
}
@ -132,11 +132,11 @@ interface MusicSettings : Settings {
get() =
Sort.fromIntCode(
sharedPreferences.getInt(
context.getString(R.string.set_key_lib_artists_sort), Int.MIN_VALUE))
getString(R.string.set_key_artists_sort), Int.MIN_VALUE))
?: Sort(Sort.Mode.ByName, true)
set(value) {
sharedPreferences.edit {
putInt(context.getString(R.string.set_key_lib_artists_sort), value.intCode)
putInt(getString(R.string.set_key_artists_sort), value.intCode)
apply()
}
}
@ -145,11 +145,11 @@ interface MusicSettings : Settings {
get() =
Sort.fromIntCode(
sharedPreferences.getInt(
context.getString(R.string.set_key_lib_genres_sort), Int.MIN_VALUE))
getString(R.string.set_key_genres_sort), Int.MIN_VALUE))
?: Sort(Sort.Mode.ByName, true)
set(value) {
sharedPreferences.edit {
putInt(context.getString(R.string.set_key_lib_genres_sort), value.intCode)
putInt(getString(R.string.set_key_genres_sort), value.intCode)
apply()
}
}
@ -159,7 +159,7 @@ interface MusicSettings : Settings {
var sort =
Sort.fromIntCode(
sharedPreferences.getInt(
context.getString(R.string.set_key_detail_album_sort), Int.MIN_VALUE))
getString(R.string.set_key_album_songs_sort), Int.MIN_VALUE))
?: Sort(Sort.Mode.ByDisc, true)
// Correct legacy album sort modes to Disc
@ -171,7 +171,7 @@ interface MusicSettings : Settings {
}
set(value) {
sharedPreferences.edit {
putInt(context.getString(R.string.set_key_detail_album_sort), value.intCode)
putInt(getString(R.string.set_key_album_songs_sort), value.intCode)
apply()
}
}
@ -180,11 +180,11 @@ interface MusicSettings : Settings {
get() =
Sort.fromIntCode(
sharedPreferences.getInt(
context.getString(R.string.set_key_detail_artist_sort), Int.MIN_VALUE))
getString(R.string.set_key_artist_songs_sort), Int.MIN_VALUE))
?: Sort(Sort.Mode.ByDate, false)
set(value) {
sharedPreferences.edit {
putInt(context.getString(R.string.set_key_detail_artist_sort), value.intCode)
putInt(getString(R.string.set_key_artist_songs_sort), value.intCode)
apply()
}
}
@ -193,14 +193,24 @@ interface MusicSettings : Settings {
get() =
Sort.fromIntCode(
sharedPreferences.getInt(
context.getString(R.string.set_key_detail_genre_sort), Int.MIN_VALUE))
getString(R.string.set_key_genre_songs_sort), Int.MIN_VALUE))
?: Sort(Sort.Mode.ByName, true)
set(value) {
sharedPreferences.edit {
putInt(context.getString(R.string.set_key_detail_genre_sort), value.intCode)
putInt(getString(R.string.set_key_genre_songs_sort), value.intCode)
apply()
}
}
override fun onSettingChanged(key: String, listener: Listener) {
when (key) {
getString(R.string.set_key_exclude_non_music),
getString(R.string.set_key_music_dirs),
getString(R.string.set_key_music_dirs_include),
getString(R.string.set_key_separators) -> listener.onIndexingSettingChanged()
getString(R.string.set_key_observing) -> listener.onObservingChanged()
}
}
}
companion object {

View file

@ -19,7 +19,6 @@ package org.oxycblt.auxio.music.system
import android.app.Service
import android.content.Intent
import android.content.SharedPreferences
import android.database.ContentObserver
import android.os.Handler
import android.os.IBinder
@ -32,7 +31,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.MusicSettings
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.storage.contentResolverSafe
@ -55,8 +53,7 @@ import org.oxycblt.auxio.util.logD
*
* @author Alexander Capehart (OxygenCobalt)
*/
class IndexerService :
Service(), Indexer.Controller, SharedPreferences.OnSharedPreferenceChangeListener {
class IndexerService : Service(), Indexer.Controller, MusicSettings.Listener {
private val indexer = Indexer.getInstance()
private val musicStore = MusicStore.getInstance()
private val playbackManager = PlaybackStateManager.getInstance()
@ -84,7 +81,7 @@ class IndexerService :
// condition to cause us to load music before we were fully initialize.
indexerContentObserver = SystemContentObserver()
settings = MusicSettings.from(this)
settings.addListener(this)
settings.registerListener(this)
indexer.registerController(this)
// An indeterminate indexer and a missing library implies we are extremely early
// in app initialization so start loading music.
@ -108,7 +105,7 @@ class IndexerService :
// Then cancel the listener-dependent components to ensure that stray reloading
// events will not occur.
indexerContentObserver.release()
settings.removeListener(this)
settings.unregisterListener(this)
indexer.unregisterController(this)
// Then cancel any remaining music loading jobs.
serviceJob.cancel()
@ -230,22 +227,18 @@ class IndexerService :
// --- SETTING CALLBACKS ---
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
when (key) {
// Hook changes in music settings to a new music loading event.
getString(R.string.set_key_exclude_non_music),
getString(R.string.set_key_music_dirs),
getString(R.string.set_key_music_dirs_include),
getString(R.string.set_key_separators) -> onStartIndexing(true)
getString(R.string.set_key_observing) -> {
// Make sure we don't override the service state with the observing
// notification if we were actively loading when the automatic rescanning
// setting changed. In such a case, the state will still be updated when
// the music loading process ends.
if (!indexer.isIndexing) {
updateIdleSession()
}
}
override fun onIndexingSettingChanged() {
// Music loading configuration changed, need to reload music.
onStartIndexing(true)
}
override fun onObservingChanged() {
// Make sure we don't override the service state with the observing
// notification if we were actively loading when the automatic rescanning
// setting changed. In such a case, the state will still be updated when
// the music loading process ends.
if (!indexer.isIndexing) {
updateIdleSession()
}
}

View file

@ -65,7 +65,7 @@ class PlaybackBarFragment : ViewBindingFragment<FragmentPlaybackBarBinding>() {
// Set up actions
binding.playbackPlayPause.setOnClickListener { playbackModel.toggleIsPlaying() }
setupSecondaryActions(binding, PlaybackSettings.from(context).playbackBarAction)
setupSecondaryActions(binding, PlaybackSettings.from(context).barAction)
// Load the track color in manually as it's unclear whether the track actually supports
// using a ColorStateList in the resources.

View file

@ -31,11 +31,11 @@ import org.oxycblt.auxio.util.logD
* User configuration specific to the playback system.
* @author Alexander Capehart (OxygenCobalt)
*/
interface PlaybackSettings : Settings {
interface PlaybackSettings : Settings<PlaybackSettings.Listener> {
/** The action to display on the playback bar. */
val playbackBarAction: ActionMode
val barAction: ActionMode
/** The action to display in the playback notification. */
val playbackNotificationAction: ActionMode
val notificationAction: ActionMode
/** Whether to start playback when a headset is plugged in. */
val headsetAutoplay: Boolean
/** The current ReplayGain configuration. */
@ -59,75 +59,72 @@ interface PlaybackSettings : Settings {
/** Whether a song should pause after every repeat. */
val pauseOnRepeat: Boolean
private class Real(context: Context) : Settings.Real(context), PlaybackSettings {
interface Listener {
/** Called when one of the ReplayGain configurations have changed. */
fun onReplayGainSettingsChanged() {}
/** Called when [notificationAction] has changed. */
fun onNotificationActionChanged() {}
}
private class Real(context: Context) : Settings.Real<Listener>(context), PlaybackSettings {
override val inListPlaybackMode: MusicMode
get() =
MusicMode.fromIntCode(
sharedPreferences.getInt(
context.getString(R.string.set_key_library_song_playback_mode),
Int.MIN_VALUE))
getString(R.string.set_key_in_list_playback_mode), Int.MIN_VALUE))
?: MusicMode.SONGS
override val inParentPlaybackMode: MusicMode?
get() =
MusicMode.fromIntCode(
sharedPreferences.getInt(
context.getString(R.string.set_key_detail_song_playback_mode),
Int.MIN_VALUE))
getString(R.string.set_key_in_parent_playback_mode), Int.MIN_VALUE))
override val playbackBarAction: ActionMode
override val barAction: ActionMode
get() =
ActionMode.fromIntCode(
sharedPreferences.getInt(
context.getString(R.string.set_key_bar_action), Int.MIN_VALUE))
sharedPreferences.getInt(getString(R.string.set_key_bar_action), Int.MIN_VALUE))
?: ActionMode.NEXT
override val playbackNotificationAction: ActionMode
override val notificationAction: ActionMode
get() =
ActionMode.fromIntCode(
sharedPreferences.getInt(
context.getString(R.string.set_key_notif_action), Int.MIN_VALUE))
getString(R.string.set_key_notif_action), Int.MIN_VALUE))
?: ActionMode.REPEAT
override val headsetAutoplay: Boolean
get() =
sharedPreferences.getBoolean(
context.getString(R.string.set_key_headset_autoplay), false)
sharedPreferences.getBoolean(getString(R.string.set_key_headset_autoplay), false)
override val replayGainMode: ReplayGainMode
get() =
ReplayGainMode.fromIntCode(
sharedPreferences.getInt(
context.getString(R.string.set_key_replay_gain), Int.MIN_VALUE))
getString(R.string.set_key_replay_gain), Int.MIN_VALUE))
?: ReplayGainMode.DYNAMIC
override var replayGainPreAmp: ReplayGainPreAmp
get() =
ReplayGainPreAmp(
sharedPreferences.getFloat(
context.getString(R.string.set_key_pre_amp_with), 0f),
sharedPreferences.getFloat(
context.getString(R.string.set_key_pre_amp_without), 0f))
sharedPreferences.getFloat(getString(R.string.set_key_pre_amp_with), 0f),
sharedPreferences.getFloat(getString(R.string.set_key_pre_amp_without), 0f))
set(value) {
sharedPreferences.edit {
putFloat(context.getString(R.string.set_key_pre_amp_with), value.with)
putFloat(context.getString(R.string.set_key_pre_amp_without), value.without)
putFloat(getString(R.string.set_key_pre_amp_with), value.with)
putFloat(getString(R.string.set_key_pre_amp_without), value.without)
apply()
}
}
override val keepShuffle: Boolean
get() =
sharedPreferences.getBoolean(context.getString(R.string.set_key_keep_shuffle), true)
get() = sharedPreferences.getBoolean(getString(R.string.set_key_keep_shuffle), true)
override val rewindWithPrev: Boolean
get() =
sharedPreferences.getBoolean(context.getString(R.string.set_key_rewind_prev), true)
get() = sharedPreferences.getBoolean(getString(R.string.set_key_rewind_prev), true)
override val pauseOnRepeat: Boolean
get() =
sharedPreferences.getBoolean(
context.getString(R.string.set_key_repeat_pause), false)
get() = sharedPreferences.getBoolean(getString(R.string.set_key_repeat_pause), false)
override fun migrate() {
// "Use alternate notification action" was converted to an ActionMode setting in 3.0.0.
@ -142,7 +139,7 @@ interface PlaybackSettings : Settings {
}
sharedPreferences.edit {
putInt(context.getString(R.string.set_key_notif_action), mode.intCode)
putInt(getString(R.string.set_key_notif_action), mode.intCode)
remove(OLD_KEY_ALT_NOTIF_ACTION)
apply()
}
@ -170,9 +167,7 @@ interface PlaybackSettings : Settings {
?: MusicMode.SONGS
sharedPreferences.edit {
putInt(
context.getString(R.string.set_key_library_song_playback_mode),
mode.intCode)
putInt(getString(R.string.set_key_in_list_playback_mode), mode.intCode)
remove(OLD_KEY_LIB_PLAYBACK_MODE)
apply()
}
@ -188,7 +183,7 @@ interface PlaybackSettings : Settings {
sharedPreferences.edit {
putInt(
context.getString(R.string.set_key_detail_song_playback_mode),
getString(R.string.set_key_in_parent_playback_mode),
mode?.intCode ?: Int.MIN_VALUE)
remove(OLD_KEY_DETAIL_PLAYBACK_MODE)
apply()
@ -196,6 +191,14 @@ interface PlaybackSettings : Settings {
}
}
override fun onSettingChanged(key: String, listener: Listener) {
if (key == getString(R.string.set_key_replay_gain) ||
key == getString(R.string.set_key_pre_amp_with) ||
key == getString(R.string.set_key_pre_amp_without)) {
listener.onReplayGainSettingsChanged()
}
}
companion object {
const val OLD_KEY_ALT_NOTIF_ACTION = "KEY_ALT_NOTIF_ACTION"
const val OLD_KEY_LIB_PLAYBACK_MODE = "KEY_SONG_PLAY_MODE2"

View file

@ -18,7 +18,6 @@
package org.oxycblt.auxio.playback.replaygain
import android.content.Context
import android.content.SharedPreferences
import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.Format
import com.google.android.exoplayer2.Player
@ -28,7 +27,6 @@ import com.google.android.exoplayer2.audio.BaseAudioProcessor
import com.google.android.exoplayer2.util.MimeTypes
import java.nio.ByteBuffer
import kotlin.math.pow
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.extractor.TextTags
import org.oxycblt.auxio.playback.PlaybackSettings
@ -45,10 +43,10 @@ import org.oxycblt.auxio.util.logD
*
* @author Alexander Capehart (OxygenCobalt)
*/
class ReplayGainAudioProcessor(private val context: Context) :
BaseAudioProcessor(), Player.Listener, SharedPreferences.OnSharedPreferenceChangeListener {
class ReplayGainAudioProcessor(context: Context) :
BaseAudioProcessor(), Player.Listener, PlaybackSettings.Listener {
private val playbackManager = PlaybackStateManager.getInstance()
private val settings = PlaybackSettings.from(context)
private val playbackSettings = PlaybackSettings.from(context)
private var lastFormat: Format? = null
private var volume = 1f
@ -65,7 +63,7 @@ class ReplayGainAudioProcessor(private val context: Context) :
*/
fun addToListeners(player: Player) {
player.addListener(this)
settings.addListener(this)
playbackSettings.registerListener(this)
}
/**
@ -75,7 +73,7 @@ class ReplayGainAudioProcessor(private val context: Context) :
*/
fun releaseFromListeners(player: Player) {
player.removeListener(this)
settings.removeListener(this)
playbackSettings.unregisterListener(this)
}
// --- OVERRIDES ---
@ -98,13 +96,9 @@ class ReplayGainAudioProcessor(private val context: Context) :
applyReplayGain(null)
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (key == context.getString(R.string.set_key_replay_gain) ||
key == context.getString(R.string.set_key_pre_amp_with) ||
key == context.getString(R.string.set_key_pre_amp_without)) {
// ReplayGain changed, we need to set it up again.
applyReplayGain(lastFormat)
}
override fun onReplayGainSettingsChanged() {
// ReplayGain config changed, we need to set it up again.
applyReplayGain(lastFormat)
}
// --- REPLAYGAIN PARSING ---
@ -116,14 +110,14 @@ class ReplayGainAudioProcessor(private val context: Context) :
private fun applyReplayGain(format: Format?) {
lastFormat = format
val gain = parseReplayGain(format ?: return)
val preAmp = settings.replayGainPreAmp
val preAmp = playbackSettings.replayGainPreAmp
val adjust =
if (gain != null) {
logD("Found ReplayGain adjustment $gain")
// ReplayGain is configurable, so determine what to do based off of the mode.
val useAlbumGain =
when (settings.replayGainMode) {
when (playbackSettings.replayGainMode) {
// User wants track gain to be preferred. Default to album gain only if
// there is no track gain.
ReplayGainMode.TRACK -> gain.track == 0f

View file

@ -116,7 +116,7 @@ class PlaybackStateManager private constructor() {
*/
@Synchronized
fun registerInternalPlayer(internalPlayer: InternalPlayer) {
if (BuildConfig.DEBUG && this.internalPlayer != null) {
if (this.internalPlayer != null) {
logW("Internal player is already registered")
return
}
@ -141,7 +141,7 @@ class PlaybackStateManager private constructor() {
*/
@Synchronized
fun unregisterInternalPlayer(internalPlayer: InternalPlayer) {
if (BuildConfig.DEBUG && this.internalPlayer !== internalPlayer) {
if (this.internalPlayer !== internalPlayer) {
logW("Given internal player did not match current internal player")
return
}

View file

@ -19,7 +19,6 @@ package org.oxycblt.auxio.playback.system
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.Bitmap
import android.net.Uri
import android.os.Bundle
@ -31,6 +30,7 @@ import androidx.media.session.MediaButtonReceiver
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R
import org.oxycblt.auxio.image.BitmapProvider
import org.oxycblt.auxio.image.ImageSettings
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.ActionMode
@ -51,7 +51,8 @@ import org.oxycblt.auxio.util.logD
class MediaSessionComponent(private val context: Context, private val listener: Listener) :
MediaSessionCompat.Callback(),
PlaybackStateManager.Listener,
SharedPreferences.OnSharedPreferenceChangeListener {
ImageSettings.Listener,
PlaybackSettings.Listener {
private val mediaSession =
MediaSessionCompat(context, context.packageName).apply {
isActive = true
@ -59,13 +60,14 @@ class MediaSessionComponent(private val context: Context, private val listener:
}
private val playbackManager = PlaybackStateManager.getInstance()
private val settings = PlaybackSettings.from(context)
private val playbackSettings = PlaybackSettings.from(context)
private val notification = NotificationComponent(context, mediaSession.sessionToken)
private val provider = BitmapProvider(context)
init {
playbackManager.addListener(this)
playbackSettings.registerListener(this)
mediaSession.setCallback(this)
}
@ -83,7 +85,7 @@ class MediaSessionComponent(private val context: Context, private val listener:
*/
fun release() {
provider.release()
settings.removeListener(this)
playbackSettings.unregisterListener(this)
playbackManager.removeListener(this)
mediaSession.apply {
isActive = false
@ -150,12 +152,14 @@ class MediaSessionComponent(private val context: Context, private val listener:
// --- SETTINGS OVERRIDES ---
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
when (key) {
context.getString(R.string.set_key_cover_mode) ->
updateMediaMetadata(playbackManager.queue.currentSong, playbackManager.parent)
context.getString(R.string.set_key_notif_action) -> invalidateSecondaryAction()
}
override fun onCoverModeChanged() {
// Need to reload the metadata cover.
updateMediaMetadata(playbackManager.queue.currentSong, playbackManager.parent)
}
override fun onNotificationActionChanged() {
// Need to re-load the action shown in the notification.
invalidateSecondaryAction()
}
// --- MEDIASESSION OVERRIDES ---
@ -359,7 +363,7 @@ class MediaSessionComponent(private val context: Context, private val listener:
// Add the secondary action (either repeat/shuffle depending on the configuration)
val secondaryAction =
when (settings.playbackNotificationAction) {
when (playbackSettings.notificationAction) {
ActionMode.SHUFFLE ->
PlaybackStateCompat.CustomAction.Builder(
PlaybackService.ACTION_INVERT_SHUFFLE,
@ -393,7 +397,7 @@ class MediaSessionComponent(private val context: Context, private val listener:
private fun invalidateSecondaryAction() {
invalidateSessionState()
when (settings.playbackNotificationAction) {
when (playbackSettings.notificationAction) {
ActionMode.SHUFFLE -> notification.updateShuffled(playbackManager.queue.isShuffled)
else -> notification.updateRepeatMode(playbackManager.repeatMode)
}

View file

@ -27,21 +27,20 @@ import org.oxycblt.auxio.settings.Settings
* User configuration specific to the search UI.
* @author Alexander Capehart (OxygenCobalt)
*/
interface SearchSettings : Settings {
interface SearchSettings : Settings<Nothing> {
/** The type of Music the search view is currently filtering to. */
var searchFilterMode: MusicMode?
private class Real(context: Context) : Settings.Real(context), SearchSettings {
private class Real(context: Context) : Settings.Real<Nothing>(context), SearchSettings {
override var searchFilterMode: MusicMode?
get() =
MusicMode.fromIntCode(
sharedPreferences.getInt(
context.getString(R.string.set_key_search_filter), Int.MIN_VALUE))
getString(R.string.set_key_search_filter), Int.MIN_VALUE))
set(value) {
sharedPreferences.edit {
putInt(
context.getString(R.string.set_key_search_filter),
value?.intCode ?: Int.MIN_VALUE)
getString(R.string.set_key_search_filter), value?.intCode ?: Int.MIN_VALUE)
apply()
}
}

View file

@ -19,49 +19,75 @@ package org.oxycblt.auxio.settings
import android.content.Context
import android.content.SharedPreferences
import androidx.annotation.StringRes
import androidx.preference.PreferenceManager
import org.oxycblt.auxio.util.logW
import org.oxycblt.auxio.util.unlikelyToBeNull
/**
* Abstract user configuration information. This interface has no functionality whatsoever. Concrete
* implementations should be preferred instead.
* @author Alexander Capehart (OxygenCobalt)
*/
interface Settings {
/** Migrate any settings fields from older versions into their new counterparts. */
interface Settings<L> {
/**
* Migrate any settings fields from older versions into their new counterparts.
* @throws NotImplementedError If there is nothing to migrate.
*/
fun migrate() {
throw NotImplementedError()
}
/**
* Add a [SharedPreferences.OnSharedPreferenceChangeListener] to monitor for settings updates.
* @param listener The [SharedPreferences.OnSharedPreferenceChangeListener] to add.
* Add a listener to monitor for settings updates. Will do nothing if
* @param listener The listener to add.
*/
fun addListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
throw NotImplementedError()
}
fun registerListener(listener: L)
/**
* Unregister a [SharedPreferences.OnSharedPreferenceChangeListener], preventing any further
* settings updates from being sent to ti.t
* Unregister a listener, preventing any further settings updates from being sent to it.
* @param listener The listener to unregister, must be the same as the current listener.
*/
fun removeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
throw NotImplementedError()
}
fun unregisterListener(listener: L)
/**
* A framework-backed [Settings] implementation.
* @param context [Context] required.
*/
abstract class Real(protected val context: Context) : Settings {
abstract class Real<L>(private val context: Context) :
Settings<L>, SharedPreferences.OnSharedPreferenceChangeListener {
protected val sharedPreferences: SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(context.applicationContext)
override fun addListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
/** @see [Context.getString] */
protected fun getString(@StringRes stringRes: Int) = context.getString(stringRes)
private var listener: L? = null
override fun registerListener(listener: L) {
if (this.listener == null) {
// Registering a listener when it was null prior, attach the callback.
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
}
this.listener = listener
}
override fun removeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener)
override fun unregisterListener(listener: L) {
if (this.listener !== listener) {
logW("Given listener was not the current listener.")
}
this.listener = null
// No longer have a listener, detach from the preferences instance.
sharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
}
final override fun onSharedPreferenceChanged(
sharedPreferences: SharedPreferences,
key: String
) {
onSettingChanged(key, unlikelyToBeNull(listener))
}
open fun onSettingChanged(key: String, listener: L) {}
}
}

View file

@ -87,7 +87,7 @@ class PreferenceFragment : PreferenceFragmentCompat() {
when (preference.key) {
getString(R.string.set_key_accent) ->
SettingsFragmentDirections.goToAccentDialog()
getString(R.string.set_key_lib_tabs) ->
getString(R.string.set_key_home_tabs) ->
SettingsFragmentDirections.goToTabDialog()
getString(R.string.set_key_pre_amp) ->
SettingsFragmentDirections.goToPreAmpDialog()

View file

@ -26,7 +26,11 @@ import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.accent.Accent
import org.oxycblt.auxio.util.logD
interface UISettings : Settings {
/**
* User configuration for the general app UI.
* @author Alexander Capehart (OxygenCobalt)
*/
interface UISettings : Settings<UISettings.Listener> {
/** The current theme. Represented by the AppCompatDelegate constants. */
val theme: Int
/** Whether to use a black background when a dark theme is currently used. */
@ -36,32 +40,33 @@ interface UISettings : Settings {
/** Whether to round additional UI elements that require album covers to be rounded. */
val roundMode: Boolean
private class Real(context: Context) : Settings.Real(context), UISettings {
interface Listener {
/** Called when [roundMode] changes. */
fun onRoundModeChanged()
}
private class Real(context: Context) : Settings.Real<Listener>(context), UISettings {
override val theme: Int
get() =
sharedPreferences.getInt(
context.getString(R.string.set_key_theme),
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
getString(R.string.set_key_theme), AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
override val useBlackTheme: Boolean
get() =
sharedPreferences.getBoolean(context.getString(R.string.set_key_black_theme), false)
get() = sharedPreferences.getBoolean(getString(R.string.set_key_black_theme), false)
override var accent: Accent
get() =
Accent.from(
sharedPreferences.getInt(
context.getString(R.string.set_key_accent), Accent.DEFAULT))
sharedPreferences.getInt(getString(R.string.set_key_accent), Accent.DEFAULT))
set(value) {
sharedPreferences.edit {
putInt(context.getString(R.string.set_key_accent), value.index)
putInt(getString(R.string.set_key_accent), value.index)
apply()
}
}
override val roundMode: Boolean
get() =
sharedPreferences.getBoolean(context.getString(R.string.set_key_round_mode), false)
get() = sharedPreferences.getBoolean(getString(R.string.set_key_round_mode), false)
override fun migrate() {
if (sharedPreferences.contains(OLD_KEY_ACCENT3)) {
@ -78,13 +83,19 @@ interface UISettings : Settings {
}
sharedPreferences.edit {
putInt(context.getString(R.string.set_key_accent), accent)
putInt(getString(R.string.set_key_accent), accent)
remove(OLD_KEY_ACCENT3)
apply()
}
}
}
override fun onSettingChanged(key: String, listener: Listener) {
if (key == getString(R.string.set_key_round_mode)) {
listener.onRoundModeChanged()
}
}
companion object {
const val OLD_KEY_ACCENT3 = "auxio_accent"
}

View file

@ -18,13 +18,13 @@
package org.oxycblt.auxio.widgets
import android.content.Context
import android.content.SharedPreferences
import android.graphics.Bitmap
import android.os.Build
import coil.request.ImageRequest
import coil.transform.RoundedCornersTransformation
import org.oxycblt.auxio.R
import org.oxycblt.auxio.image.BitmapProvider
import org.oxycblt.auxio.image.ImageSettings
import org.oxycblt.auxio.image.extractor.SquareFrameTransform
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
@ -43,15 +43,17 @@ import org.oxycblt.auxio.util.logD
* @author Alexander Capehart (OxygenCobalt)
*/
class WidgetComponent(private val context: Context) :
PlaybackStateManager.Listener, SharedPreferences.OnSharedPreferenceChangeListener {
PlaybackStateManager.Listener, UISettings.Listener, ImageSettings.Listener {
private val playbackManager = PlaybackStateManager.getInstance()
private val settings = UISettings.from(context)
private val uiSettings = UISettings.from(context)
private val imageSettings = ImageSettings.from(context)
private val widgetProvider = WidgetProvider()
private val provider = BitmapProvider(context)
init {
playbackManager.addListener(this)
settings.addListener(this)
uiSettings.registerListener(this)
imageSettings.registerListener(this)
}
/** Update [WidgetProvider] with the current playback state. */
@ -76,7 +78,7 @@ class WidgetComponent(private val context: Context) :
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// Android 12, always round the cover with the widget's inner radius
context.getDimenPixels(android.R.dimen.system_app_widget_inner_radius)
} else if (settings.roundMode) {
} else if (uiSettings.roundMode) {
// < Android 12, but the user still enabled round mode.
context.getDimenPixels(R.dimen.size_corners_medium)
} else {
@ -107,27 +109,23 @@ class WidgetComponent(private val context: Context) :
/** Release this instance, preventing any further events from updating the widget instances. */
fun release() {
provider.release()
settings.removeListener(this)
uiSettings.unregisterListener(this)
widgetProvider.reset(context)
playbackManager.removeListener(this)
}
// --- CALLBACKS ---
// Hook all the major song-changing updates + the major player state updates
// to updating the "Now Playing" widget.
// Respond to all major song or player changes that will affect the widget
override fun onIndexMoved(queue: Queue) = update()
override fun onQueueReordered(queue: Queue) = update()
override fun onNewPlayback(queue: Queue, parent: MusicParent?) = update()
override fun onStateChanged(state: InternalPlayer.State) = update()
override fun onRepeatChanged(repeatMode: RepeatMode) = update()
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
if (key == context.getString(R.string.set_key_cover_mode) ||
key == context.getString(R.string.set_key_round_mode)) {
update()
}
}
// Respond to settings changes that will affect the widget
override fun onRoundModeChanged() = update()
override fun onCoverModeChanged() = update()
/**
* A condensed form of the playback state that is safe to use in AppWidgets.

View file

@ -20,8 +20,8 @@
<string name="set_key_pre_amp_with" translatable="false">auxio_pre_amp_with</string>
<string name="set_key_pre_amp_without" translatable="false">auxio_pre_amp_without</string>
<string name="set_key_library_song_playback_mode" translatable="false">auxio_library_playback_mode</string>
<string name="set_key_detail_song_playback_mode" translatable="false">auxio_detail_playback_mode</string>
<string name="set_key_in_list_playback_mode" translatable="false">auxio_library_playback_mode</string>
<string name="set_key_in_parent_playback_mode" translatable="false">auxio_detail_playback_mode</string>
<string name="set_key_keep_shuffle" translatable="false">KEY_KEEP_SHUFFLE</string>
<string name="set_key_rewind_prev" translatable="false">KEY_PREV_REWIND</string>
<string name="set_key_repeat_pause" translatable="false">KEY_LOOP_PAUSE</string>
@ -29,7 +29,7 @@
<string name="set_key_wipe_state" translatable="false">auxio_wipe_state</string>
<string name="set_key_restore_state" translatable="false">auxio_restore_state</string>
<string name="set_key_lib_tabs" translatable="false">auxio_lib_tabs</string>
<string name="set_key_home_tabs" translatable="false">auxio_lib_tabs</string>
<string name="set_key_hide_collaborators" translatable="false">auxio_hide_collaborators</string>
<string name="set_key_round_mode" translatable="false">auxio_round_covers</string>
<string name="set_key_bar_action" translatable="false">auxio_bar_action</string>
@ -37,14 +37,14 @@
<string name="set_key_search_filter" translatable="false">KEY_SEARCH_FILTER</string>
<string name="set_key_lib_songs_sort" translatable="false">auxio_songs_sort</string>
<string name="set_key_lib_albums_sort" translatable="false">auxio_albums_sort</string>
<string name="set_key_lib_artists_sort" translatable="false">auxio_artists_sort</string>
<string name="set_key_lib_genres_sort" translatable="false">auxio_genres_sort</string>
<string name="set_key_songs_sort" translatable="false">auxio_songs_sort</string>
<string name="set_key_albums_sort" translatable="false">auxio_albums_sort</string>
<string name="set_key_artists_sort" translatable="false">auxio_artists_sort</string>
<string name="set_key_genres_sort" translatable="false">auxio_genres_sort</string>
<string name="set_key_detail_album_sort" translatable="false">auxio_album_sort</string>
<string name="set_key_detail_artist_sort" translatable="false">auxio_artist_sort</string>
<string name="set_key_detail_genre_sort" translatable="false">auxio_genre_sort</string>
<string name="set_key_album_songs_sort" translatable="false">auxio_album_sort</string>
<string name="set_key_artist_songs_sort" translatable="false">auxio_artist_sort</string>
<string name="set_key_genre_songs_sort" translatable="false">auxio_genre_sort</string>
<string-array name="entries_theme">
<item>@string/set_theme_auto</item>

View file

@ -27,7 +27,7 @@
<PreferenceCategory app:title="@string/set_display">
<org.oxycblt.auxio.settings.prefs.WrappedDialogPreference
app:key="@string/set_key_lib_tabs"
app:key="@string/set_key_home_tabs"
app:summary="@string/set_lib_tabs_desc"
app:title="@string/set_lib_tabs" />
@ -86,7 +86,7 @@
app:defaultValue="@integer/music_mode_songs"
app:entries="@array/entries_library_song_playback_mode"
app:entryValues="@array/values_library_song_playback_mode"
app:key="@string/set_key_library_song_playback_mode"
app:key="@string/set_key_in_list_playback_mode"
app:title="@string/set_library_song_playback_mode"
app:useSimpleSummaryProvider="true" />
@ -94,7 +94,7 @@
app:defaultValue="@integer/music_mode_none"
app:entries="@array/entries_detail_song_playback_mode"
app:entryValues="@array/values_detail_song_playback_mode"
app:key="@string/set_key_detail_song_playback_mode"
app:key="@string/set_key_in_parent_playback_mode"
app:title="@string/set_detail_song_playback_mode"
app:useSimpleSummaryProvider="true" />

View file

@ -20,6 +20,8 @@ package org.oxycblt.auxio.music
import org.oxycblt.auxio.music.storage.MusicDirectories
interface FakeMusicSettings : MusicSettings {
override fun registerListener(listener: MusicSettings.Listener) = throw NotImplementedError()
override fun unregisterListener(listener: MusicSettings.Listener) = throw NotImplementedError()
override var musicDirs: MusicDirectories
get() = throw NotImplementedError()
set(_) = throw NotImplementedError()