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

View file

@ -28,30 +28,44 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
* User configuration specific to the home UI. * User configuration specific to the home UI.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface HomeSettings : Settings { interface HomeSettings : Settings<HomeSettings.Listener> {
/** The tabs to show in the home UI. */ /** The tabs to show in the home UI. */
var homeTabs: Array<Tab> var homeTabs: Array<Tab>
/** Whether to hide artists considered "collaborators" from the home UI. */ /** Whether to hide artists considered "collaborators" from the home UI. */
val shouldHideCollaborators: Boolean 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> override var homeTabs: Array<Tab>
get() = get() =
Tab.fromIntCode( Tab.fromIntCode(
sharedPreferences.getInt( 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)) ?: unlikelyToBeNull(Tab.fromIntCode(Tab.SEQUENCE_DEFAULT))
set(value) { set(value) {
sharedPreferences.edit { 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() apply()
} }
} }
override val shouldHideCollaborators: Boolean override val shouldHideCollaborators: Boolean
get() = get() =
sharedPreferences.getBoolean( sharedPreferences.getBoolean(getString(R.string.set_key_hide_collaborators), false)
context.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 { companion object {

View file

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

View file

@ -27,16 +27,20 @@ import org.oxycblt.auxio.util.logD
* User configuration specific to image loading. * User configuration specific to image loading.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface ImageSettings : Settings { interface ImageSettings : Settings<ImageSettings.Listener> {
/** The strategy to use when loading album covers. */ /** The strategy to use when loading album covers. */
val coverMode: CoverMode 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 override val coverMode: CoverMode
get() = get() =
CoverMode.fromIntCode( CoverMode.fromIntCode(
sharedPreferences.getInt( sharedPreferences.getInt(getString(R.string.set_key_cover_mode), Int.MIN_VALUE))
context.getString(R.string.set_key_cover_mode), Int.MIN_VALUE))
?: CoverMode.MEDIA_STORE ?: CoverMode.MEDIA_STORE
override fun migrate() { override fun migrate() {
@ -54,13 +58,19 @@ interface ImageSettings : Settings {
} }
sharedPreferences.edit { 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_SHOW_COVERS)
remove(OLD_KEY_QUALITY_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 { private companion object {
const val OLD_KEY_SHOW_COVERS = "KEY_SHOW_COVERS" const val OLD_KEY_SHOW_COVERS = "KEY_SHOW_COVERS"
const val OLD_KEY_QUALITY_COVERS = "KEY_QUALITY_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. * User configuration specific to music system.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface MusicSettings : Settings { interface MusicSettings : Settings<MusicSettings.Listener> {
/** The configuration on how to handle particular directories in the music library. */ /** The configuration on how to handle particular directories in the music library. */
var musicDirs: MusicDirectories var musicDirs: MusicDirectories
/** Whether to exclude non-music audio files from the music library. */ /** 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. */ /** The [Sort] mode used in an [Genre]'s [Song] list. */
var genreSongSort: Sort 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) private val storageManager = context.getSystemServiceCompat(StorageManager::class)
override var musicDirs: MusicDirectories override var musicDirs: MusicDirectories
get() { get() {
val dirs = val dirs =
(sharedPreferences.getStringSet( (sharedPreferences.getStringSet(getString(R.string.set_key_music_dirs), null)
context.getString(R.string.set_key_music_dirs), null)
?: emptySet()) ?: emptySet())
.mapNotNull { Directory.fromDocumentTreeUri(storageManager, it) } .mapNotNull { Directory.fromDocumentTreeUri(storageManager, it) }
return MusicDirectories( return MusicDirectories(
dirs, dirs,
sharedPreferences.getBoolean( sharedPreferences.getBoolean(
context.getString(R.string.set_key_music_dirs_include), false)) getString(R.string.set_key_music_dirs_include), false))
} }
set(value) { set(value) {
sharedPreferences.edit { sharedPreferences.edit {
putStringSet( putStringSet(
context.getString(R.string.set_key_music_dirs), getString(R.string.set_key_music_dirs),
value.dirs.map(Directory::toDocumentTreeUri).toSet()) value.dirs.map(Directory::toDocumentTreeUri).toSet())
putBoolean( putBoolean(getString(R.string.set_key_music_dirs_include), value.shouldInclude)
context.getString(R.string.set_key_music_dirs_include), value.shouldInclude)
apply() apply()
} }
} }
override val excludeNonMusic: Boolean override val excludeNonMusic: Boolean
get() = get() =
sharedPreferences.getBoolean( sharedPreferences.getBoolean(getString(R.string.set_key_exclude_non_music), true)
context.getString(R.string.set_key_exclude_non_music), true)
override val shouldBeObserving: Boolean override val shouldBeObserving: Boolean
get() = get() = sharedPreferences.getBoolean(getString(R.string.set_key_observing), false)
sharedPreferences.getBoolean(context.getString(R.string.set_key_observing), false)
override var multiValueSeparators: String override var multiValueSeparators: String
// Differ from convention and store a string of separator characters instead of an int // Differ from convention and store a string of separator characters instead of an int
// code. This makes it easier to use and more extendable. // code. This makes it easier to use and more extendable.
get() = get() = sharedPreferences.getString(getString(R.string.set_key_separators), "") ?: ""
sharedPreferences.getString(context.getString(R.string.set_key_separators), "")
?: ""
set(value) { set(value) {
sharedPreferences.edit { sharedPreferences.edit {
putString(context.getString(R.string.set_key_separators), value) putString(getString(R.string.set_key_separators), value)
apply() apply()
} }
} }
@ -105,12 +106,11 @@ interface MusicSettings : Settings {
override var songSort: Sort override var songSort: Sort
get() = get() =
Sort.fromIntCode( Sort.fromIntCode(
sharedPreferences.getInt( sharedPreferences.getInt(getString(R.string.set_key_songs_sort), Int.MIN_VALUE))
context.getString(R.string.set_key_lib_songs_sort), Int.MIN_VALUE))
?: Sort(Sort.Mode.ByName, true) ?: Sort(Sort.Mode.ByName, true)
set(value) { set(value) {
sharedPreferences.edit { 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() apply()
} }
} }
@ -119,11 +119,11 @@ interface MusicSettings : Settings {
get() = get() =
Sort.fromIntCode( Sort.fromIntCode(
sharedPreferences.getInt( 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) ?: Sort(Sort.Mode.ByName, true)
set(value) { set(value) {
sharedPreferences.edit { 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() apply()
} }
} }
@ -132,11 +132,11 @@ interface MusicSettings : Settings {
get() = get() =
Sort.fromIntCode( Sort.fromIntCode(
sharedPreferences.getInt( 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) ?: Sort(Sort.Mode.ByName, true)
set(value) { set(value) {
sharedPreferences.edit { 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() apply()
} }
} }
@ -145,11 +145,11 @@ interface MusicSettings : Settings {
get() = get() =
Sort.fromIntCode( Sort.fromIntCode(
sharedPreferences.getInt( 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) ?: Sort(Sort.Mode.ByName, true)
set(value) { set(value) {
sharedPreferences.edit { 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() apply()
} }
} }
@ -159,7 +159,7 @@ interface MusicSettings : Settings {
var sort = var sort =
Sort.fromIntCode( Sort.fromIntCode(
sharedPreferences.getInt( 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) ?: Sort(Sort.Mode.ByDisc, true)
// Correct legacy album sort modes to Disc // Correct legacy album sort modes to Disc
@ -171,7 +171,7 @@ interface MusicSettings : Settings {
} }
set(value) { set(value) {
sharedPreferences.edit { 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() apply()
} }
} }
@ -180,11 +180,11 @@ interface MusicSettings : Settings {
get() = get() =
Sort.fromIntCode( Sort.fromIntCode(
sharedPreferences.getInt( 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) ?: Sort(Sort.Mode.ByDate, false)
set(value) { set(value) {
sharedPreferences.edit { 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() apply()
} }
} }
@ -193,14 +193,24 @@ interface MusicSettings : Settings {
get() = get() =
Sort.fromIntCode( Sort.fromIntCode(
sharedPreferences.getInt( 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) ?: Sort(Sort.Mode.ByName, true)
set(value) { set(value) {
sharedPreferences.edit { 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() 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 { companion object {

View file

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

View file

@ -65,7 +65,7 @@ class PlaybackBarFragment : ViewBindingFragment<FragmentPlaybackBarBinding>() {
// Set up actions // Set up actions
binding.playbackPlayPause.setOnClickListener { playbackModel.toggleIsPlaying() } 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 // Load the track color in manually as it's unclear whether the track actually supports
// using a ColorStateList in the resources. // 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. * User configuration specific to the playback system.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface PlaybackSettings : Settings { interface PlaybackSettings : Settings<PlaybackSettings.Listener> {
/** The action to display on the playback bar. */ /** The action to display on the playback bar. */
val playbackBarAction: ActionMode val barAction: ActionMode
/** The action to display in the playback notification. */ /** The action to display in the playback notification. */
val playbackNotificationAction: ActionMode val notificationAction: ActionMode
/** Whether to start playback when a headset is plugged in. */ /** Whether to start playback when a headset is plugged in. */
val headsetAutoplay: Boolean val headsetAutoplay: Boolean
/** The current ReplayGain configuration. */ /** The current ReplayGain configuration. */
@ -59,75 +59,72 @@ interface PlaybackSettings : Settings {
/** Whether a song should pause after every repeat. */ /** Whether a song should pause after every repeat. */
val pauseOnRepeat: Boolean 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 override val inListPlaybackMode: MusicMode
get() = get() =
MusicMode.fromIntCode( MusicMode.fromIntCode(
sharedPreferences.getInt( sharedPreferences.getInt(
context.getString(R.string.set_key_library_song_playback_mode), getString(R.string.set_key_in_list_playback_mode), Int.MIN_VALUE))
Int.MIN_VALUE))
?: MusicMode.SONGS ?: MusicMode.SONGS
override val inParentPlaybackMode: MusicMode? override val inParentPlaybackMode: MusicMode?
get() = get() =
MusicMode.fromIntCode( MusicMode.fromIntCode(
sharedPreferences.getInt( sharedPreferences.getInt(
context.getString(R.string.set_key_detail_song_playback_mode), getString(R.string.set_key_in_parent_playback_mode), Int.MIN_VALUE))
Int.MIN_VALUE))
override val playbackBarAction: ActionMode override val barAction: ActionMode
get() = get() =
ActionMode.fromIntCode( ActionMode.fromIntCode(
sharedPreferences.getInt( sharedPreferences.getInt(getString(R.string.set_key_bar_action), Int.MIN_VALUE))
context.getString(R.string.set_key_bar_action), Int.MIN_VALUE))
?: ActionMode.NEXT ?: ActionMode.NEXT
override val playbackNotificationAction: ActionMode override val notificationAction: ActionMode
get() = get() =
ActionMode.fromIntCode( ActionMode.fromIntCode(
sharedPreferences.getInt( 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 ?: ActionMode.REPEAT
override val headsetAutoplay: Boolean override val headsetAutoplay: Boolean
get() = get() =
sharedPreferences.getBoolean( sharedPreferences.getBoolean(getString(R.string.set_key_headset_autoplay), false)
context.getString(R.string.set_key_headset_autoplay), false)
override val replayGainMode: ReplayGainMode override val replayGainMode: ReplayGainMode
get() = get() =
ReplayGainMode.fromIntCode( ReplayGainMode.fromIntCode(
sharedPreferences.getInt( 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 ?: ReplayGainMode.DYNAMIC
override var replayGainPreAmp: ReplayGainPreAmp override var replayGainPreAmp: ReplayGainPreAmp
get() = get() =
ReplayGainPreAmp( ReplayGainPreAmp(
sharedPreferences.getFloat( sharedPreferences.getFloat(getString(R.string.set_key_pre_amp_with), 0f),
context.getString(R.string.set_key_pre_amp_with), 0f), sharedPreferences.getFloat(getString(R.string.set_key_pre_amp_without), 0f))
sharedPreferences.getFloat(
context.getString(R.string.set_key_pre_amp_without), 0f))
set(value) { set(value) {
sharedPreferences.edit { sharedPreferences.edit {
putFloat(context.getString(R.string.set_key_pre_amp_with), value.with) putFloat(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_without), value.without)
apply() apply()
} }
} }
override val keepShuffle: Boolean override val keepShuffle: Boolean
get() = get() = sharedPreferences.getBoolean(getString(R.string.set_key_keep_shuffle), true)
sharedPreferences.getBoolean(context.getString(R.string.set_key_keep_shuffle), true)
override val rewindWithPrev: Boolean override val rewindWithPrev: Boolean
get() = get() = sharedPreferences.getBoolean(getString(R.string.set_key_rewind_prev), true)
sharedPreferences.getBoolean(context.getString(R.string.set_key_rewind_prev), true)
override val pauseOnRepeat: Boolean override val pauseOnRepeat: Boolean
get() = get() = sharedPreferences.getBoolean(getString(R.string.set_key_repeat_pause), false)
sharedPreferences.getBoolean(
context.getString(R.string.set_key_repeat_pause), false)
override fun migrate() { override fun migrate() {
// "Use alternate notification action" was converted to an ActionMode setting in 3.0.0. // "Use alternate notification action" was converted to an ActionMode setting in 3.0.0.
@ -142,7 +139,7 @@ interface PlaybackSettings : Settings {
} }
sharedPreferences.edit { 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) remove(OLD_KEY_ALT_NOTIF_ACTION)
apply() apply()
} }
@ -170,9 +167,7 @@ interface PlaybackSettings : Settings {
?: MusicMode.SONGS ?: MusicMode.SONGS
sharedPreferences.edit { sharedPreferences.edit {
putInt( putInt(getString(R.string.set_key_in_list_playback_mode), mode.intCode)
context.getString(R.string.set_key_library_song_playback_mode),
mode.intCode)
remove(OLD_KEY_LIB_PLAYBACK_MODE) remove(OLD_KEY_LIB_PLAYBACK_MODE)
apply() apply()
} }
@ -188,7 +183,7 @@ interface PlaybackSettings : Settings {
sharedPreferences.edit { sharedPreferences.edit {
putInt( 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) mode?.intCode ?: Int.MIN_VALUE)
remove(OLD_KEY_DETAIL_PLAYBACK_MODE) remove(OLD_KEY_DETAIL_PLAYBACK_MODE)
apply() 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 { companion object {
const val OLD_KEY_ALT_NOTIF_ACTION = "KEY_ALT_NOTIF_ACTION" const val OLD_KEY_ALT_NOTIF_ACTION = "KEY_ALT_NOTIF_ACTION"
const val OLD_KEY_LIB_PLAYBACK_MODE = "KEY_SONG_PLAY_MODE2" const val OLD_KEY_LIB_PLAYBACK_MODE = "KEY_SONG_PLAY_MODE2"

View file

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

View file

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

View file

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

View file

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

View file

@ -19,49 +19,75 @@ package org.oxycblt.auxio.settings
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.annotation.StringRes
import androidx.preference.PreferenceManager 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 * Abstract user configuration information. This interface has no functionality whatsoever. Concrete
* implementations should be preferred instead. * implementations should be preferred instead.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
interface Settings { interface Settings<L> {
/** Migrate any settings fields from older versions into their new counterparts. */ /**
* Migrate any settings fields from older versions into their new counterparts.
* @throws NotImplementedError If there is nothing to migrate.
*/
fun migrate() { fun migrate() {
throw NotImplementedError() throw NotImplementedError()
} }
/** /**
* Add a [SharedPreferences.OnSharedPreferenceChangeListener] to monitor for settings updates. * Add a listener to monitor for settings updates. Will do nothing if
* @param listener The [SharedPreferences.OnSharedPreferenceChangeListener] to add. * @param listener The listener to add.
*/ */
fun addListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) { fun registerListener(listener: L)
throw NotImplementedError()
}
/** /**
* Unregister a [SharedPreferences.OnSharedPreferenceChangeListener], preventing any further * Unregister a listener, preventing any further settings updates from being sent to it.
* settings updates from being sent to ti.t * @param listener The listener to unregister, must be the same as the current listener.
*/ */
fun removeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) { fun unregisterListener(listener: L)
throw NotImplementedError()
}
/** /**
* A framework-backed [Settings] implementation. * A framework-backed [Settings] implementation.
* @param context [Context] required. * @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 = protected val sharedPreferences: SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(context.applicationContext) PreferenceManager.getDefaultSharedPreferences(context.applicationContext)
override fun addListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) { /** @see [Context.getString] */
sharedPreferences.registerOnSharedPreferenceChangeListener(listener) 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) { override fun unregisterListener(listener: L) {
sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener) 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) { when (preference.key) {
getString(R.string.set_key_accent) -> getString(R.string.set_key_accent) ->
SettingsFragmentDirections.goToAccentDialog() SettingsFragmentDirections.goToAccentDialog()
getString(R.string.set_key_lib_tabs) -> getString(R.string.set_key_home_tabs) ->
SettingsFragmentDirections.goToTabDialog() SettingsFragmentDirections.goToTabDialog()
getString(R.string.set_key_pre_amp) -> getString(R.string.set_key_pre_amp) ->
SettingsFragmentDirections.goToPreAmpDialog() 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.ui.accent.Accent
import org.oxycblt.auxio.util.logD 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. */ /** The current theme. Represented by the AppCompatDelegate constants. */
val theme: Int val theme: Int
/** Whether to use a black background when a dark theme is currently used. */ /** 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. */ /** Whether to round additional UI elements that require album covers to be rounded. */
val roundMode: Boolean 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 override val theme: Int
get() = get() =
sharedPreferences.getInt( sharedPreferences.getInt(
context.getString(R.string.set_key_theme), getString(R.string.set_key_theme), AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
override val useBlackTheme: Boolean override val useBlackTheme: Boolean
get() = get() = sharedPreferences.getBoolean(getString(R.string.set_key_black_theme), false)
sharedPreferences.getBoolean(context.getString(R.string.set_key_black_theme), false)
override var accent: Accent override var accent: Accent
get() = get() =
Accent.from( Accent.from(
sharedPreferences.getInt( sharedPreferences.getInt(getString(R.string.set_key_accent), Accent.DEFAULT))
context.getString(R.string.set_key_accent), Accent.DEFAULT))
set(value) { set(value) {
sharedPreferences.edit { sharedPreferences.edit {
putInt(context.getString(R.string.set_key_accent), value.index) putInt(getString(R.string.set_key_accent), value.index)
apply() apply()
} }
} }
override val roundMode: Boolean override val roundMode: Boolean
get() = get() = sharedPreferences.getBoolean(getString(R.string.set_key_round_mode), false)
sharedPreferences.getBoolean(context.getString(R.string.set_key_round_mode), false)
override fun migrate() { override fun migrate() {
if (sharedPreferences.contains(OLD_KEY_ACCENT3)) { if (sharedPreferences.contains(OLD_KEY_ACCENT3)) {
@ -78,13 +83,19 @@ interface UISettings : Settings {
} }
sharedPreferences.edit { sharedPreferences.edit {
putInt(context.getString(R.string.set_key_accent), accent) putInt(getString(R.string.set_key_accent), accent)
remove(OLD_KEY_ACCENT3) remove(OLD_KEY_ACCENT3)
apply() apply()
} }
} }
} }
override fun onSettingChanged(key: String, listener: Listener) {
if (key == getString(R.string.set_key_round_mode)) {
listener.onRoundModeChanged()
}
}
companion object { companion object {
const val OLD_KEY_ACCENT3 = "auxio_accent" const val OLD_KEY_ACCENT3 = "auxio_accent"
} }

View file

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

View file

@ -27,7 +27,7 @@
<PreferenceCategory app:title="@string/set_display"> <PreferenceCategory app:title="@string/set_display">
<org.oxycblt.auxio.settings.prefs.WrappedDialogPreference <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:summary="@string/set_lib_tabs_desc"
app:title="@string/set_lib_tabs" /> app:title="@string/set_lib_tabs" />
@ -86,7 +86,7 @@
app:defaultValue="@integer/music_mode_songs" app:defaultValue="@integer/music_mode_songs"
app:entries="@array/entries_library_song_playback_mode" app:entries="@array/entries_library_song_playback_mode"
app:entryValues="@array/values_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:title="@string/set_library_song_playback_mode"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />
@ -94,7 +94,7 @@
app:defaultValue="@integer/music_mode_none" app:defaultValue="@integer/music_mode_none"
app:entries="@array/entries_detail_song_playback_mode" app:entries="@array/entries_detail_song_playback_mode"
app:entryValues="@array/values_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:title="@string/set_detail_song_playback_mode"
app:useSimpleSummaryProvider="true" /> app:useSimpleSummaryProvider="true" />

View file

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