settings: revamp settingsmanager into settings

Revamp the shared object SettingsManager into a standalone utility
called Settings.

This makes many things easier in Auxio. It completely unifies the key
format that we use (Android Strings instead of Java consts), eliminates
the pretty dumb initialization method that we use, and eliminates the
dubiousness of holding a Context-related utility in a global field.

The only cost was having to migrate even more ViewModels to Android
ViewModels. Whatever.
This commit is contained in:
OxygenCobalt 2022-06-20 09:05:04 -06:00
parent ba48cdad29
commit 532a30325a
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
39 changed files with 742 additions and 749 deletions

View file

@ -10,6 +10,10 @@
- Fixed seam that would appear on some album covers
- Fixed visual issue with the queue opening animation
#### Dev/Meta
- Migrated preferences from shared object to utility
- Removed 2.0.0 compat code
## v2.4.0
#### What's New

View file

@ -30,15 +30,11 @@ import org.oxycblt.auxio.image.ArtistImageFetcher
import org.oxycblt.auxio.image.CrossfadeTransitionFactory
import org.oxycblt.auxio.image.GenreImageFetcher
import org.oxycblt.auxio.image.MusicKeyer
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.util.logD
class AuxioApp : Application(), ImageLoaderFactory {
override fun onCreate() {
super.onCreate()
logD(BuildConfig.APPLICATION_ID + ".MainActivity")
// Adding static shortcuts in a dynamic manner is better than declaring them
// manually, as it will properly handle the difference between debug and release
// Auxio instances.
@ -54,10 +50,6 @@ class AuxioApp : Application(), ImageLoaderFactory {
action = INTENT_KEY_SHORTCUT_SHUFFLE
})
.build()))
// Init SettingsManager here so that there aren't any race conditions
// [e.g PlaybackService gets SettingsManager before activity can init SettingsManager]
SettingsManager.init(applicationContext)
}
override fun newImageLoader(): ImageLoader {

View file

@ -18,11 +18,9 @@
package org.oxycblt.auxio
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.View
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.view.WindowCompat
@ -31,7 +29,9 @@ import org.oxycblt.auxio.databinding.ActivityMainBinding
import org.oxycblt.auxio.music.IndexerService
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.system.PlaybackService
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.settings.settings
import org.oxycblt.auxio.util.androidViewModels
import org.oxycblt.auxio.util.getSystemBarInsetsCompat
import org.oxycblt.auxio.util.isNight
import org.oxycblt.auxio.util.logD
@ -51,7 +51,7 @@ import org.oxycblt.auxio.util.logD
* @author OxygenCobalt
*/
class MainActivity : AppCompatActivity() {
private val playbackModel: PlaybackViewModel by viewModels()
private val playbackModel: PlaybackViewModel by androidViewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -72,17 +72,13 @@ class MainActivity : AppCompatActivity() {
startService(Intent(this, PlaybackService::class.java))
// If we have a valid intent, use that. Otherwise, restore the playback state.
val action = intentToDelayedAction(intent) ?: PlaybackViewModel.DelayedAction.RestoreState
playbackModel.startDelayedAction(this, action)
playbackModel.startDelayedAction(
intentToDelayedAction(intent) ?: PlaybackViewModel.DelayedAction.RestoreState)
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intentToDelayedAction(intent)?.let { action ->
playbackModel.startDelayedAction(this, action)
}
intentToDelayedAction(intent)?.let(playbackModel::startDelayedAction)
}
private fun intentToDelayedAction(intent: Intent?): PlaybackViewModel.DelayedAction? {
@ -99,45 +95,25 @@ class MainActivity : AppCompatActivity() {
}
}
/**
* Extracts a [Uri] from an intent as long as:
* - The intent is ACTION_VIEW
* - The intent has not already been used.
*/
private fun retrieveViewUri(intent: Intent?): Uri? {
if (intent != null) {
val action = intent.action
val isConsumed = intent.getBooleanExtra(KEY_INTENT_USED, false)
if (action == Intent.ACTION_VIEW && !isConsumed) {
// Mark the intent as used so this does not fire again
intent.putExtra(KEY_INTENT_USED, true)
return intent.data
}
}
return null
}
private fun setupTheme() {
val settingsManager = SettingsManager.getInstance()
val settings = Settings(this)
// Disable theme customization above Android 12, as it's far enough in as a version to
// the point where most phones should have an automatic option for light/dark theming.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
AppCompatDelegate.setDefaultNightMode(settingsManager.theme)
AppCompatDelegate.setDefaultNightMode(settings.theme)
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}
// The black theme has a completely separate set of styles since style attributes cannot
// be modified at runtime.
if (isNight && settingsManager.useBlackTheme) {
logD("Applying black theme [accent ${settingsManager.accent}]")
setTheme(settingsManager.accent.blackTheme)
if (isNight && settings.useBlackTheme) {
logD("Applying black theme [accent ${settings.accent}]")
setTheme(settings.accent.blackTheme)
} else {
logD("Applying normal theme [accent ${settingsManager.accent}]")
setTheme(settingsManager.accent.theme)
logD("Applying normal theme [accent ${settings.accent}]")
setTheme(settings.accent.theme)
}
}

View file

@ -32,6 +32,7 @@ import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.MainNavigationAction
import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.ui.ViewBindingFragment
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.launch
/**
@ -40,7 +41,7 @@ import org.oxycblt.auxio.util.launch
* @author OxygenCobalt
*/
class MainFragment : ViewBindingFragment<FragmentMainBinding>() {
private val playbackModel: PlaybackViewModel by activityViewModels()
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
private val navModel: NavigationViewModel by activityViewModels()
private var callback: DynamicBackPressedCallback? = null

View file

@ -36,7 +36,7 @@ import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MimeType
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.Header
import org.oxycblt.auxio.ui.Item
import org.oxycblt.auxio.ui.Sort
@ -62,7 +62,7 @@ class DetailViewModel(application: Application) :
)
private val musicStore = MusicStore.getInstance()
private val settingsManager = SettingsManager.getInstance()
private val settings = Settings(application)
private val _currentSong = MutableStateFlow<DetailSong?>(null)
val currentSong: StateFlow<DetailSong?>
@ -77,9 +77,9 @@ class DetailViewModel(application: Application) :
get() = _albumData
var albumSort: Sort
get() = settingsManager.detailAlbumSort
get() = settings.detailAlbumSort
set(value) {
settingsManager.detailAlbumSort = value
settings.detailAlbumSort = value
currentAlbum.value?.let(::refreshAlbumData)
}
@ -91,9 +91,9 @@ class DetailViewModel(application: Application) :
val artistData: StateFlow<List<Item>> = _artistData
var artistSort: Sort
get() = settingsManager.detailArtistSort
get() = settings.detailArtistSort
set(value) {
settingsManager.detailArtistSort = value
settings.detailArtistSort = value
currentArtist.value?.let(::refreshArtistData)
}
@ -105,9 +105,9 @@ class DetailViewModel(application: Application) :
val genreData: StateFlow<List<Item>> = _genreData
var genreSort: Sort
get() = settingsManager.detailGenreSort
get() = settings.detailGenreSort
set(value) {
settingsManager.detailGenreSort = value
settings.detailGenreSort = value
currentGenre.value?.let(::refreshGenreData)
}

View file

@ -55,6 +55,7 @@ import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.MainNavigationAction
import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.ui.ViewBindingFragment
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.getColorStateListSafe
import org.oxycblt.auxio.util.getSystemBarInsetsCompat
import org.oxycblt.auxio.util.launch
@ -71,9 +72,9 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
* @author OxygenCobalt
*/
class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuItemClickListener {
private val playbackModel: PlaybackViewModel by activityViewModels()
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
private val navModel: NavigationViewModel by activityViewModels()
private val homeModel: HomeViewModel by activityViewModels()
private val homeModel: HomeViewModel by androidActivityViewModels()
private val indexerModel: IndexerViewModel by activityViewModels()
private var storagePermissionLauncher: ActivityResultLauncher<String>? = null

View file

@ -17,7 +17,8 @@
package org.oxycblt.auxio.home
import androidx.lifecycle.ViewModel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.oxycblt.auxio.home.tabs.Tab
@ -26,18 +27,20 @@ import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.util.application
import org.oxycblt.auxio.util.logD
/**
* The ViewModel for managing [HomeFragment]'s data, sorting modes, and tab state.
* @author OxygenCobalt
*/
class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.Callback {
class HomeViewModel(application: Application) :
AndroidViewModel(application), Settings.Callback, MusicStore.Callback {
private val musicStore = MusicStore.getInstance()
private val settingsManager = SettingsManager.getInstance()
private val settings = Settings(application, this)
private val _songs = MutableStateFlow(listOf<Song>())
val songs: StateFlow<List<Song>>
@ -60,7 +63,7 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.Callback
/** Internal getter for getting the visible library tabs */
private val visibleTabs: List<DisplayMode>
get() = settingsManager.libTabs.filterIsInstance<Tab.Visible>().map { it.mode }
get() = settings.libTabs.filterIsInstance<Tab.Visible>().map { it.mode }
private val _currentTab = MutableStateFlow(tabs[0])
val currentTab: StateFlow<DisplayMode> = _currentTab
@ -77,7 +80,6 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.Callback
init {
musicStore.addCallback(this)
settingsManager.addCallback(this)
}
/** Update the current tab based off of the new ViewPager position. */
@ -93,10 +95,10 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.Callback
/** Get the specific sort for the given [DisplayMode]. */
fun getSortForDisplay(displayMode: DisplayMode): Sort {
return when (displayMode) {
DisplayMode.SHOW_SONGS -> settingsManager.libSongSort
DisplayMode.SHOW_ALBUMS -> settingsManager.libAlbumSort
DisplayMode.SHOW_ARTISTS -> settingsManager.libArtistSort
DisplayMode.SHOW_GENRES -> settingsManager.libGenreSort
DisplayMode.SHOW_SONGS -> settings.libSongSort
DisplayMode.SHOW_ALBUMS -> settings.libAlbumSort
DisplayMode.SHOW_ARTISTS -> settings.libArtistSort
DisplayMode.SHOW_GENRES -> settings.libGenreSort
}
}
@ -105,19 +107,19 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.Callback
logD("Updating ${_currentTab.value} sort to $sort")
when (_currentTab.value) {
DisplayMode.SHOW_SONGS -> {
settingsManager.libSongSort = sort
settings.libSongSort = sort
_songs.value = sort.songs(_songs.value)
}
DisplayMode.SHOW_ALBUMS -> {
settingsManager.libAlbumSort = sort
settings.libAlbumSort = sort
_albums.value = sort.albums(_albums.value)
}
DisplayMode.SHOW_ARTISTS -> {
settingsManager.libArtistSort = sort
settings.libArtistSort = sort
_artists.value = sort.artists(_artists.value)
}
DisplayMode.SHOW_GENRES -> {
settingsManager.libGenreSort = sort
settings.libGenreSort = sort
_genres.value = sort.genres(_genres.value)
}
}
@ -136,10 +138,10 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.Callback
override fun onLibraryChanged(library: MusicStore.Library?) {
if (library != null) {
_songs.value = settingsManager.libSongSort.songs(library.songs)
_albums.value = settingsManager.libAlbumSort.albums(library.albums)
_artists.value = settingsManager.libArtistSort.artists(library.artists)
_genres.value = settingsManager.libGenreSort.genres(library.genres)
_songs.value = settings.libSongSort.songs(library.songs)
_albums.value = settings.libAlbumSort.albums(library.albums)
_artists.value = settings.libArtistSort.artists(library.artists)
_genres.value = settings.libGenreSort.genres(library.genres)
}
}
@ -151,6 +153,6 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.Callback
override fun onCleared() {
super.onCleared()
musicStore.removeCallback(this)
settingsManager.removeCallback(this)
settings.release()
}
}

View file

@ -20,13 +20,13 @@ package org.oxycblt.auxio.home.list
import android.os.Bundle
import android.view.LayoutInflater
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView
import org.oxycblt.auxio.ui.Item
import org.oxycblt.auxio.ui.MenuFragment
import org.oxycblt.auxio.ui.MenuItemListener
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.applySpans
/**
@ -38,7 +38,7 @@ abstract class HomeListFragment<T : Item> :
MenuItemListener,
FastScrollRecyclerView.PopupProvider,
FastScrollRecyclerView.OnFastScrollListener {
protected val homeModel: HomeViewModel by activityViewModels()
protected val homeModel: HomeViewModel by androidActivityViewModels()
override fun onCreateBinding(inflater: LayoutInflater) =
FragmentHomeListBinding.inflate(inflater)

View file

@ -22,6 +22,8 @@ import android.view.View
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.settings.settings
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Item
import org.oxycblt.auxio.ui.MenuItemListener
@ -39,6 +41,7 @@ import org.oxycblt.auxio.util.logEOrThrow
*/
class SongListFragment : HomeListFragment<Song>() {
private val homeAdapter = SongsAdapter(this)
private val settings: Settings by settings()
override fun onBindingCreated(binding: FragmentHomeListBinding, savedInstanceState: Bundle?) {
super.onBindingCreated(binding, savedInstanceState)
@ -80,7 +83,7 @@ class SongListFragment : HomeListFragment<Song>() {
override fun onItemClick(item: Item) {
check(item is Song)
playbackModel.play(item)
playbackModel.play(item, settings.songPlaybackMode)
}
override fun onOpenMenu(item: Item, anchor: View) {

View file

@ -25,7 +25,8 @@ import androidx.recyclerview.widget.RecyclerView
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogTabsBinding
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.settings.settings
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.ViewBindingDialogFragment
import org.oxycblt.auxio.util.logD
@ -36,8 +37,8 @@ import org.oxycblt.auxio.util.requireAttached
* @author OxygenCobalt
*/
class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAdapter.Listener {
private val settingsManager = SettingsManager.getInstance()
private val tabAdapter = TabAdapter(this)
private val settings: Settings by settings()
private var touchHelper: ItemTouchHelper? = null
private var callback: TabDragCallback? = null
@ -48,7 +49,7 @@ class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAd
.setTitle(R.string.set_lib_tabs)
.setPositiveButton(R.string.lbl_ok) { _, _ ->
logD("Committing tab changes")
settingsManager.libTabs = tabAdapter.data.tabs
settings.libTabs = tabAdapter.data.tabs
}
.setNegativeButton(R.string.lbl_cancel, null)
}
@ -59,7 +60,7 @@ class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAd
logD("Found saved tab state")
tabAdapter.data.submitTabs(savedTabs)
} else {
tabAdapter.data.submitTabs(settingsManager.libTabs)
tabAdapter.data.submitTabs(settings.libTabs)
}
binding.tabRecycler.apply {

View file

@ -45,7 +45,7 @@ import kotlinx.coroutines.withContext
import okio.buffer
import okio.source
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW
@ -56,19 +56,19 @@ import org.oxycblt.auxio.util.logW
* TODO: Artist images
*/
abstract class BaseFetcher : Fetcher {
private val settingsManager = SettingsManager.getInstance()
/**
* Fetch the artwork of an [album]. This call respects user configuration and has proper
* redundancy in the case that metadata fails to load.
*/
protected suspend fun fetchArt(context: Context, album: Album): InputStream? {
if (!settingsManager.showCovers) {
val settings = Settings(context)
if (!settings.showCovers) {
return null
}
return try {
if (settingsManager.useQualityCovers) {
if (settings.useQualityCovers) {
fetchQualityCovers(context, album)
} else {
fetchMediaStoreCovers(context, album)

View file

@ -34,7 +34,7 @@ import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.getColorStateListSafe
import org.oxycblt.auxio.util.getDrawableSafe
@ -51,11 +51,13 @@ class StyledImageView
@JvmOverloads
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
AppCompatImageView(context, attrs, defStyleAttr) {
private val settings = Settings(context)
var cornerRadius = 0f
set(value) {
field = value
(background as? MaterialShapeDrawable)?.let { bg ->
if (!isInEditMode && SettingsManager.getInstance().roundCovers) {
if (settings.roundCovers) {
bg.setCornerSize(value)
} else {
bg.setCornerSize(0f)

View file

@ -34,9 +34,11 @@ class MusicStore private constructor() {
var library: Library? = null
set(value) {
field = value
for (callback in callbacks) {
callback.onLibraryChanged(library)
synchronized(this) {
field = value
for (callback in callbacks) {
callback.onLibraryChanged(library)
}
}
}

View file

@ -41,7 +41,7 @@ import org.oxycblt.auxio.music.no
import org.oxycblt.auxio.music.queryCursor
import org.oxycblt.auxio.music.storageVolumesCompat
import org.oxycblt.auxio.music.useQuery
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.contentResolverSafe
import org.oxycblt.auxio.util.getSystemServiceSafe
import org.oxycblt.auxio.util.logD
@ -125,10 +125,10 @@ abstract class MediaStoreBackend : Indexer.Backend {
protected val volumes = mutableListOf<StorageVolume>()
override fun query(context: Context): Cursor {
val settingsManager = SettingsManager.getInstance()
val settings = Settings(context)
val storageManager = context.getSystemServiceSafe(StorageManager::class)
volumes.addAll(storageManager.storageVolumesCompat)
val dirs = settingsManager.getMusicDirs(context, storageManager)
val dirs = settings.getMusicDirs(storageManager)
val args = mutableListOf<String>()
var selector = BASE_SELECTOR
@ -144,8 +144,7 @@ abstract class MediaStoreBackend : Indexer.Backend {
" AND NOT ("
}
// Since selector arguments are contained within a single parentheses, we need to
// do a bunch of stuff.
// Each impl adds the directories that they want selected.
for (i in dirs.dirs.indices) {
if (addDirToSelectorArgs(dirs.dirs[i], args)) {
selector +=
@ -510,8 +509,9 @@ open class VolumeAwareMediaStoreBackend : MediaStoreBackend() {
val relativePath = cursor.getStringOrNull(relativePathIndex)
// We now have access to the volume name, so we try to leverage it instead.
// I have no idea how well this works in practice, so we still leverage
// the API 21 path grokking in the case that these fields are not sane.
// I have no idea how well this works in practice, but I assume that the fields
// probably exist.
// TODO: Remove redundant null checks for fields you are pretty sure are not null.
if (volumeName != null && relativePath != null) {
// Iterating through the volume list is cheaper than creating a map,
// interestingly enough.

View file

@ -25,14 +25,15 @@ import android.view.LayoutInflater
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogMusicDirsBinding
import org.oxycblt.auxio.music.Directory
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.settings.settings
import org.oxycblt.auxio.ui.ViewBindingDialogFragment
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.getSystemServiceSafe
import org.oxycblt.auxio.util.hardRestart
import org.oxycblt.auxio.util.logD
@ -44,11 +45,10 @@ import org.oxycblt.auxio.util.showToast
*/
class MusicDirsDialog :
ViewBindingDialogFragment<DialogMusicDirsBinding>(), MusicDirAdapter.Listener {
private val settingsManager = SettingsManager.getInstance()
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
private val playbackModel: PlaybackViewModel by activityViewModels()
private val dirAdapter = MusicDirAdapter(this)
private val settings: Settings by settings()
private var storageManager: StorageManager? = null
override fun onCreateBinding(inflater: LayoutInflater) =
@ -80,7 +80,7 @@ class MusicDirsDialog :
}
dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.setOnClickListener {
val dirs = settingsManager.getMusicDirs(requireContext(), requireStorageManager())
val dirs = settings.getMusicDirs(requireStorageManager())
if (dirs.dirs != dirAdapter.data.currentList ||
dirs.shouldInclude != isInclude(requireBinding())) {
@ -99,7 +99,7 @@ class MusicDirsDialog :
}
val storageManager = requireStorageManager()
var dirs = settingsManager.getMusicDirs(requireContext(), storageManager)
var dirs = settings.getMusicDirs(storageManager)
if (savedInstanceState != null) {
val pendingDirs = savedInstanceState.getStringArrayList(KEY_PENDING_DIRS)
@ -187,8 +187,7 @@ class MusicDirsDialog :
binding.folderModeGroup.checkedButtonId == R.id.dirs_mode_include
private fun saveAndRestart() {
settingsManager.setMusicDirs(
MusicDirs(dirAdapter.data.currentList, isInclude(requireBinding())))
settings.setMusicDirs(MusicDirs(dirAdapter.data.currentList, isInclude(requireBinding())))
playbackModel.savePlaybackState(requireContext()) { requireContext().hardRestart() }
}

View file

@ -28,6 +28,7 @@ import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.ui.MainNavigationAction
import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.ui.ViewBindingFragment
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.getColorStateListSafe
import org.oxycblt.auxio.util.getSystemBarInsetsCompat
import org.oxycblt.auxio.util.getSystemGestureInsetsCompat
@ -40,7 +41,7 @@ import org.oxycblt.auxio.util.textSafe
* @author OxygenCobalt
*/
class PlaybackBarFragment : ViewBindingFragment<FragmentPlaybackBarBinding>() {
private val playbackModel: PlaybackViewModel by activityViewModels()
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
private val navModel: NavigationViewModel by activityViewModels()
override fun onCreateBinding(inflater: LayoutInflater) =

View file

@ -33,6 +33,7 @@ import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.ui.MainNavigationAction
import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.ui.ViewBindingFragment
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.getDrawableSafe
import org.oxycblt.auxio.util.getSystemBarInsetsCompat
import org.oxycblt.auxio.util.getSystemGestureInsetsCompat
@ -53,7 +54,7 @@ class PlaybackPanelFragment :
ViewBindingFragment<FragmentPlaybackPanelBinding>(),
StyledSeekBar.Callback,
Toolbar.OnMenuItemClickListener {
private val playbackModel: PlaybackViewModel by activityViewModels()
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
private val navModel: NavigationViewModel by activityViewModels()
private var queueItem: MenuItem? = null

View file

@ -17,9 +17,10 @@
package org.oxycblt.auxio.playback
import android.app.Application
import android.content.Context
import android.net.Uri
import androidx.lifecycle.ViewModel
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -31,9 +32,12 @@ import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.playback.state.PlaybackStateDatabase
import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.settings.settings
import org.oxycblt.auxio.util.application
import org.oxycblt.auxio.util.logE
/**
@ -44,12 +48,13 @@ import org.oxycblt.auxio.util.logE
*
* @author OxygenCobalt
*/
class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore.Callback {
class PlaybackViewModel(application: Application) :
AndroidViewModel(application), PlaybackStateManager.Callback, MusicStore.Callback {
private val musicStore = MusicStore.getInstance()
private val settingsManager = SettingsManager.getInstance()
private val settings = Settings(application)
private val playbackManager = PlaybackStateManager.getInstance()
private var pendingDelayedAction: DelayedActionImpl? = null
private var pendingDelayedAction: DelayedAction? = null
private val _song = MutableStateFlow<Song?>(null)
/** The current song. */
@ -85,12 +90,10 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore
// --- PLAYING FUNCTIONS ---
/**
* Play a [song] with the [mode] specified. [mode] will default to the preferred song playback
* mode of the user if not specified.
*/
fun play(song: Song, mode: PlaybackMode = settingsManager.songPlaybackMode) {
playbackManager.play(song, mode)
/** Play a [song] with the [mode] specified, */
fun play(song: Song, mode: PlaybackMode) {
playbackManager.play(
song, settings.keepShuffle && playbackManager.isPlaying, mode, settings)
}
/**
@ -103,7 +106,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore
return
}
playbackManager.play(album, shuffled)
playbackManager.play(album, shuffled, settings)
}
/**
@ -116,7 +119,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore
return
}
playbackManager.play(artist, shuffled)
playbackManager.play(artist, shuffled, settings)
}
/**
@ -129,12 +132,12 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore
return
}
playbackManager.play(genre, shuffled)
playbackManager.play(genre, shuffled, settings)
}
/** Shuffle all songs */
fun shuffleAll() {
playbackManager.shuffleAll()
playbackManager.shuffleAll(settings)
}
/**
@ -149,26 +152,29 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore
* We would normally want to put this kind of functionality into PlaybackService, but it's
* lifecycle makes that more or less impossible.
*/
fun startDelayedAction(context: Context, action: DelayedAction) {
fun startDelayedAction(action: DelayedAction) {
val library = musicStore.library
val actionImpl = DelayedActionImpl(context.applicationContext, action)
if (library != null) {
performActionImpl(actionImpl, library)
performActionImpl(action, library)
} else {
pendingDelayedAction = actionImpl
pendingDelayedAction = action
}
}
private fun performActionImpl(action: DelayedActionImpl, library: MusicStore.Library) {
when (action.inner) {
private fun performActionImpl(action: DelayedAction, library: MusicStore.Library) {
when (action) {
is DelayedAction.RestoreState -> {
if (!playbackManager.isInitialized) {
viewModelScope.launch { playbackManager.restoreState(action.context) }
viewModelScope.launch {
playbackManager.restoreState(PlaybackStateDatabase.getInstance(application))
}
}
}
is DelayedAction.ShuffleAll -> shuffleAll()
is DelayedAction.Open -> {
library.findSongForUri(action.context, action.inner.uri)?.let(::play)
library.findSongForUri(application, action.uri)?.let { song ->
play(song, settings.songPlaybackMode)
}
}
}
}
@ -219,17 +225,17 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore
/** Add an [Album] to the top of the queue. */
fun playNext(album: Album) {
playbackManager.playNext(settingsManager.detailAlbumSort.songs(album.songs))
playbackManager.playNext(settings.detailAlbumSort.songs(album.songs))
}
/** Add an [Artist] to the top of the queue. */
fun playNext(artist: Artist) {
playbackManager.playNext(settingsManager.detailArtistSort.songs(artist.songs))
playbackManager.playNext(settings.detailArtistSort.songs(artist.songs))
}
/** Add a [Genre] to the top of the queue. */
fun playNext(genre: Genre) {
playbackManager.playNext(settingsManager.detailGenreSort.songs(genre.songs))
playbackManager.playNext(settings.detailGenreSort.songs(genre.songs))
}
/** Add a [Song] to the end of the queue. */
@ -239,17 +245,17 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore
/** Add an [Album] to the end of the queue. */
fun addToQueue(album: Album) {
playbackManager.addToQueue(settingsManager.detailAlbumSort.songs(album.songs))
playbackManager.addToQueue(settings.detailAlbumSort.songs(album.songs))
}
/** Add an [Artist] to the end of the queue. */
fun addToQueue(artist: Artist) {
playbackManager.addToQueue(settingsManager.detailArtistSort.songs(artist.songs))
playbackManager.addToQueue(settings.detailArtistSort.songs(artist.songs))
}
/** Add a [Genre] to the end of the queue. */
fun addToQueue(genre: Genre) {
playbackManager.addToQueue(settingsManager.detailGenreSort.songs(genre.songs))
playbackManager.addToQueue(settings.detailGenreSort.songs(genre.songs))
}
// --- STATUS FUNCTIONS ---
@ -261,7 +267,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore
/** Flip the shuffle status, e.g from on to off. Will keep song by default. */
fun invertShuffled() {
playbackManager.reshuffle(!playbackManager.isShuffled)
playbackManager.reshuffle(!playbackManager.isShuffled, settings)
}
/** Increment the repeat mode, e.g from [RepeatMode.NONE] to [RepeatMode.ALL] */
@ -277,7 +283,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore
*/
fun savePlaybackState(context: Context, onDone: () -> Unit) {
viewModelScope.launch {
playbackManager.saveState(context)
playbackManager.saveState(PlaybackStateDatabase.getInstance(context))
onDone()
}
}
@ -289,8 +295,6 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore
data class Open(val uri: Uri) : DelayedAction()
}
private data class DelayedActionImpl(val context: Context, val inner: DelayedAction)
// --- OVERRIDES ---
override fun onCleared() {

View file

@ -20,7 +20,6 @@ package org.oxycblt.auxio.playback.queue
import android.os.Bundle
import android.view.LayoutInflater
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
@ -28,6 +27,7 @@ import org.oxycblt.auxio.databinding.FragmentQueueBinding
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.ViewBindingFragment
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.launch
import org.oxycblt.auxio.util.requireAttached
@ -36,7 +36,7 @@ import org.oxycblt.auxio.util.requireAttached
* @author OxygenCobalt
*/
class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemListener {
private val playbackModel: PlaybackViewModel by activityViewModels()
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
private var queueAdapter = QueueAdapter(this)
private var touchHelper: ItemTouchHelper? = null
private var callback: QueueDragCallback? = null

View file

@ -25,7 +25,8 @@ import kotlin.math.abs
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogPreAmpBinding
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.settings.settings
import org.oxycblt.auxio.ui.ViewBindingDialogFragment
import org.oxycblt.auxio.util.textSafe
@ -34,7 +35,7 @@ import org.oxycblt.auxio.util.textSafe
* @author OxygenCobalt
*/
class PreAmpCustomizeDialog : ViewBindingDialogFragment<DialogPreAmpBinding>() {
private val settingsManager = SettingsManager.getInstance()
private val settings: Settings by settings()
override fun onCreateBinding(inflater: LayoutInflater) = DialogPreAmpBinding.inflate(inflater)
@ -43,7 +44,7 @@ class PreAmpCustomizeDialog : ViewBindingDialogFragment<DialogPreAmpBinding>() {
.setTitle(R.string.set_pre_amp)
.setPositiveButton(R.string.lbl_ok) { _, _ ->
val binding = requireBinding()
settingsManager.replayGainPreAmp =
settings.replayGainPreAmp =
ReplayGainPreAmp(binding.withTagsSlider.value, binding.withoutTagsSlider.value)
}
.setNegativeButton(R.string.lbl_cancel, null)
@ -54,7 +55,7 @@ class PreAmpCustomizeDialog : ViewBindingDialogFragment<DialogPreAmpBinding>() {
// First initialization, we need to supply the sliders with the values from
// settings. After this, the sliders save their own state, so we do not need to
// do any restore behavior.
val preAmp = settingsManager.replayGainPreAmp
val preAmp = settings.replayGainPreAmp
binding.withTagsSlider.value = preAmp.with
binding.withoutTagsSlider.value = preAmp.without
}

View file

@ -17,6 +17,7 @@
package org.oxycblt.auxio.playback.replaygain
import android.content.Context
import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.audio.AudioProcessor
import com.google.android.exoplayer2.audio.BaseAudioProcessor
@ -27,7 +28,7 @@ import java.nio.ByteBuffer
import kotlin.math.pow
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.clamp
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.unlikelyToBeNull
@ -42,12 +43,12 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
*
* @author OxygenCobalt
*/
class ReplayGainAudioProcessor : BaseAudioProcessor() {
class ReplayGainAudioProcessor(context: Context) : BaseAudioProcessor() {
private data class Gain(val track: Float, val album: Float)
private data class GainTag(val key: String, val value: Float)
private val playbackManager = PlaybackStateManager.getInstance()
private val settingsManager = SettingsManager.getInstance()
private val settings = Settings(context)
private var volume = 1f
set(value) {
@ -63,20 +64,20 @@ class ReplayGainAudioProcessor : BaseAudioProcessor() {
* based off Vanilla Music's implementation, but has diverged to a significant extent.
*/
fun applyReplayGain(metadata: Metadata?) {
if (settingsManager.replayGainMode == ReplayGainMode.OFF) {
if (settings.replayGainMode == ReplayGainMode.OFF) {
logD("ReplayGain not enabled")
volume = 1f
return
}
val gain = metadata?.let(::parseReplayGain)
val preAmp = settingsManager.replayGainPreAmp
val preAmp = settings.replayGainPreAmp
val adjust =
if (gain != null) {
// ReplayGain is configurable, so determine what to do based off of the mode.
val useAlbumGain =
when (settingsManager.replayGainMode) {
when (settings.replayGainMode) {
ReplayGainMode.OFF -> throw IllegalStateException()
// User wants track gain to be preferred. Default to album gain only if

View file

@ -17,7 +17,6 @@
package org.oxycblt.auxio.playback.state
import android.content.Context
import kotlin.math.max
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -28,7 +27,7 @@ import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW
@ -51,7 +50,6 @@ import org.oxycblt.auxio.util.logW
*/
class PlaybackStateManager private constructor() {
private val musicStore = MusicStore.getInstance()
private val settingsManager = SettingsManager.getInstance()
/** The currently playing song. Null if there isn't one */
val song
@ -150,7 +148,7 @@ class PlaybackStateManager private constructor() {
* Play a [song].
* @param playbackMode The [PlaybackMode] to construct the queue off of.
*/
fun play(song: Song, playbackMode: PlaybackMode) {
fun play(song: Song, shuffle: Boolean, playbackMode: PlaybackMode, settings: Settings) {
val library = musicStore.library ?: return
synchronized(this) {
@ -162,7 +160,7 @@ class PlaybackStateManager private constructor() {
PlaybackMode.IN_GENRE -> song.genre
}
applyNewQueue(library, settingsManager.keepShuffle && isShuffled, song)
applyNewQueue(library, settings, shuffle, song)
seekTo(0)
notifyNewPlayback()
notifyShuffledChanged()
@ -175,12 +173,12 @@ class PlaybackStateManager private constructor() {
* Play a [parent], such as an artist or album.
* @param shuffled Whether the queue is shuffled or not
*/
fun play(parent: MusicParent, shuffled: Boolean) {
fun play(parent: MusicParent, shuffled: Boolean, settings: Settings) {
val library = musicStore.library ?: return
synchronized(this) {
this.parent = parent
applyNewQueue(library, shuffled, null)
applyNewQueue(library, settings, shuffled, null)
seekTo(0)
notifyNewPlayback()
notifyShuffledChanged()
@ -190,11 +188,11 @@ class PlaybackStateManager private constructor() {
}
/** Shuffle all songs. */
fun shuffleAll() {
fun shuffleAll(settings: Settings) {
val library = musicStore.library ?: return
synchronized(this) {
parent = null
applyNewQueue(library, true, null)
applyNewQueue(library, settings, true, null)
seekTo(0)
notifyNewPlayback()
notifyShuffledChanged()
@ -291,17 +289,22 @@ class PlaybackStateManager private constructor() {
}
/** Set whether this instance is [shuffled]. Updates the queue accordingly. */
fun reshuffle(shuffled: Boolean) {
fun reshuffle(shuffled: Boolean, settings: Settings) {
val library = musicStore.library ?: return
synchronized(this) {
val song = song ?: return
applyNewQueue(library, shuffled, song)
applyNewQueue(library, settings, shuffled, song)
notifyQueueChanged()
notifyShuffledChanged()
}
}
private fun applyNewQueue(library: MusicStore.Library, shuffled: Boolean, keep: Song?) {
private fun applyNewQueue(
library: MusicStore.Library,
settings: Settings,
shuffled: Boolean,
keep: Song?
) {
val newQueue = (parent?.songs ?: library.songs).toMutableList()
val newIndex: Int
@ -317,10 +320,10 @@ class PlaybackStateManager private constructor() {
val sort =
parent.let { parent ->
when (parent) {
null -> settingsManager.libSongSort
is Album -> settingsManager.detailAlbumSort
is Artist -> settingsManager.detailArtistSort
is Genre -> settingsManager.detailGenreSort
null -> settings.libSongSort
is Album -> settings.detailAlbumSort
is Artist -> settings.detailArtistSort
is Genre -> settings.detailGenreSort
}
}
@ -372,26 +375,12 @@ class PlaybackStateManager private constructor() {
/** Rewind to the beginning of a song. */
fun rewind() = seekTo(0)
/** Repeat the current song (in line with the user configuration). */
fun repeat() {
rewind()
if (settingsManager.pauseOnRepeat) {
synchronized(this) {
isPlaying = false
}
}
}
// --- PERSISTENCE FUNCTIONS ---
/**
* Restore the state from the database
* @param context [Context] required.
*/
suspend fun restoreState(context: Context) {
/** Restore the state from the [database] */
suspend fun restoreState(database: PlaybackStateDatabase) {
val library = musicStore.library ?: return
val start: Long
val database = PlaybackStateDatabase.getInstance(context)
val state: PlaybackStateDatabase.SavedState?
logD("Getting state from DB")
@ -421,17 +410,13 @@ class PlaybackStateManager private constructor() {
}
}
/**
* Save the current state to the database.
* @param context [Context] required
*/
suspend fun saveState(context: Context) {
/** Save the current state to the [database]. */
suspend fun saveState(database: PlaybackStateDatabase) {
logD("Saving state to DB")
// Pack the entire state and save it to the database.
withContext(Dispatchers.IO) {
val start = System.currentTimeMillis()
val database = PlaybackStateDatabase.getInstance(context)
database.write(
synchronized(this) {

View file

@ -31,7 +31,7 @@ import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.logD
/**
@ -46,9 +46,9 @@ class MediaSessionComponent(private val context: Context, private val player: Pl
Player.Listener,
MediaSessionCompat.Callback(),
PlaybackStateManager.Callback,
SettingsManager.Callback {
Settings.Callback {
private val playbackManager = PlaybackStateManager.getInstance()
private val settingsManager = SettingsManager.getInstance()
private val settings = Settings(context, this)
private val mediaSession =
MediaSessionCompat(context, context.packageName).apply { isActive = true }
private val provider = BitmapProvider(context)
@ -59,7 +59,6 @@ class MediaSessionComponent(private val context: Context, private val player: Pl
init {
player.addListener(this)
playbackManager.addCallback(this)
settingsManager.addCallback(this)
mediaSession.setCallback(this)
}
@ -69,9 +68,9 @@ class MediaSessionComponent(private val context: Context, private val player: Pl
fun release() {
provider.release()
settings.release()
player.removeListener(this)
playbackManager.removeCallback(this)
settingsManager.removeCallback(this)
mediaSession.apply {
isActive = false
@ -218,7 +217,8 @@ class MediaSessionComponent(private val context: Context, private val player: Pl
override fun onSetShuffleMode(shuffleMode: Int) {
playbackManager.reshuffle(
shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_ALL ||
shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_GROUP)
shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_GROUP,
settings)
}
override fun onStop() {

View file

@ -48,9 +48,10 @@ import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.replaygain.ReplayGainAudioProcessor
import org.oxycblt.auxio.playback.state.PlaybackStateDatabase
import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.widgets.WidgetComponent
import org.oxycblt.auxio.widgets.WidgetProvider
@ -74,10 +75,10 @@ class PlaybackService :
Player.Listener,
NotificationComponent.Callback,
PlaybackStateManager.Controller,
SettingsManager.Callback {
Settings.Callback {
// Player components
private lateinit var player: ExoPlayer
private val replayGainProcessor = ReplayGainAudioProcessor()
private lateinit var replayGainProcessor: ReplayGainAudioProcessor
// System backend components
private lateinit var notificationComponent: NotificationComponent
@ -87,7 +88,7 @@ class PlaybackService :
// Managers
private val playbackManager = PlaybackStateManager.getInstance()
private val settingsManager = SettingsManager.getInstance()
private lateinit var settings: Settings
// State
private var isForeground = false
@ -105,6 +106,8 @@ class PlaybackService :
// --- PLAYER SETUP ---
replayGainProcessor = ReplayGainAudioProcessor(this)
player = newPlayer()
player.addListener(this)
@ -138,8 +141,9 @@ class PlaybackService :
// --- PLAYBACKSTATEMANAGER SETUP ---
settings = Settings(this, this)
playbackManager.registerController(this)
settingsManager.addCallback(this)
logD("Service created")
}
@ -166,7 +170,7 @@ class PlaybackService :
playbackManager.isPlaying = false
playbackManager.unregisterController(this)
settingsManager.removeCallback(this)
settings.release()
unregisterReceiver(systemReceiver)
serviceJob.cancel()
@ -196,7 +200,10 @@ class PlaybackService :
when (state) {
Player.STATE_ENDED -> {
if (playbackManager.repeatMode == RepeatMode.TRACK) {
playbackManager.repeat()
playbackManager.rewind()
if (settings.pauseOnRepeat) {
playbackManager.isPlaying = false
}
} else {
playbackManager.next()
}
@ -261,7 +268,7 @@ class PlaybackService :
}
override fun shouldPrevRewind() =
settingsManager.rewindWithPrev && player.currentPosition > REWIND_THRESHOLD
settings.rewindWithPrev && player.currentPosition > REWIND_THRESHOLD
override fun onPlayingChanged(isPlaying: Boolean) {
player.playWhenReady = isPlaying
@ -269,13 +276,13 @@ class PlaybackService :
}
override fun onRepeatChanged(repeatMode: RepeatMode) {
if (!settingsManager.useAltNotifAction) {
if (!settings.useAltNotifAction) {
notificationComponent.updateRepeatMode(repeatMode)
}
}
override fun onShuffledChanged(isShuffled: Boolean) {
if (settingsManager.useAltNotifAction) {
if (settings.useAltNotifAction) {
notificationComponent.updateShuffled(isShuffled)
}
}
@ -293,7 +300,7 @@ class PlaybackService :
}
override fun onNotifSettingsChanged() {
if (settingsManager.useAltNotifAction) {
if (settings.useAltNotifAction) {
onShuffledChanged(playbackManager.isShuffled)
} else {
onRepeatChanged(playbackManager.repeatMode)
@ -353,7 +360,9 @@ class PlaybackService :
private fun stopAndSave() {
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
isForeground = false
saveScope.launch { playbackManager.saveState(this@PlaybackService) }
saveScope.launch {
playbackManager.saveState(PlaybackStateDatabase.getInstance(this@PlaybackService))
}
}
/** A [BroadcastReceiver] for receiving general playback events from the system. */
@ -386,7 +395,8 @@ class PlaybackService :
ACTION_PLAY_PAUSE -> playbackManager.isPlaying = !playbackManager.isPlaying
ACTION_INC_REPEAT_MODE ->
playbackManager.repeatMode = playbackManager.repeatMode.increment()
ACTION_INVERT_SHUFFLE -> playbackManager.reshuffle(!playbackManager.isShuffled)
ACTION_INVERT_SHUFFLE ->
playbackManager.reshuffle(!playbackManager.isShuffled, settings)
ACTION_SKIP_PREV -> playbackManager.prev()
ACTION_SKIP_NEXT -> playbackManager.next()
ACTION_EXIT -> {
@ -408,7 +418,7 @@ class PlaybackService :
*/
private fun maybeResumeFromPlug() {
if (playbackManager.song != null &&
settingsManager.headsetAutoplay &&
settings.headsetAutoplay &&
initialHeadsetPlugEventHandled) {
logD("Device connected, resuming")
playbackManager.isPlaying = true

View file

@ -36,6 +36,8 @@ import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.settings.settings
import org.oxycblt.auxio.ui.Header
import org.oxycblt.auxio.ui.Item
import org.oxycblt.auxio.ui.MenuFragment
@ -57,6 +59,7 @@ class SearchFragment :
private val searchModel: SearchViewModel by androidViewModels()
private val searchAdapter = SearchAdapter(this)
private val settings: Settings by settings()
private var imm: InputMethodManager? = null
private var launchedKeyboard = false
@ -124,7 +127,7 @@ class SearchFragment :
override fun onItemClick(item: Item) {
when (item) {
is Song -> playbackModel.play(item)
is Song -> playbackModel.play(item, settings.songPlaybackMode)
is MusicParent -> navModel.exploreNavigateTo(item)
}
}

View file

@ -30,7 +30,7 @@ import kotlinx.coroutines.launch
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Header
import org.oxycblt.auxio.ui.Item
@ -45,23 +45,18 @@ import org.oxycblt.auxio.util.logD
class SearchViewModel(application: Application) :
AndroidViewModel(application), MusicStore.Callback {
private val musicStore = MusicStore.getInstance()
private val settingsManager = SettingsManager.getInstance()
private val settings = Settings(application)
private val _searchResults = MutableStateFlow(listOf<Item>())
/** Current search results from the last [search] call. */
val searchResults: StateFlow<List<Item>>
get() = _searchResults
private var _filterMode: DisplayMode? = null
val filterMode: DisplayMode?
get() = _filterMode
get() = settings.searchFilterMode
private var lastQuery: String? = null
init {
_filterMode = settingsManager.searchFilterMode
}
/**
* Use [query] to perform a search of the music library. Will push results to [searchResults].
*/
@ -84,28 +79,28 @@ class SearchViewModel(application: Application) :
// Note: a filter mode of null means to not filter at all.
if (_filterMode == null || _filterMode == DisplayMode.SHOW_ARTISTS) {
if (filterMode == null || filterMode == DisplayMode.SHOW_ARTISTS) {
library.artists.filterByOrNull(query)?.let { artists ->
results.add(Header(-1, R.string.lbl_artists))
results.addAll(sort.artists(artists))
}
}
if (_filterMode == null || _filterMode == DisplayMode.SHOW_ALBUMS) {
if (filterMode == null || filterMode == DisplayMode.SHOW_ALBUMS) {
library.albums.filterByOrNull(query)?.let { albums ->
results.add(Header(-2, R.string.lbl_albums))
results.addAll(sort.albums(albums))
}
}
if (_filterMode == null || _filterMode == DisplayMode.SHOW_GENRES) {
if (filterMode == null || filterMode == DisplayMode.SHOW_GENRES) {
library.genres.filterByOrNull(query)?.let { genres ->
results.add(Header(-3, R.string.lbl_genres))
results.addAll(sort.genres(genres))
}
}
if (_filterMode == null || _filterMode == DisplayMode.SHOW_SONGS) {
if (filterMode == null || filterMode == DisplayMode.SHOW_SONGS) {
library.songs.filterByOrNull(query)?.let { songs ->
results.add(Header(-4, R.string.lbl_songs))
results.addAll(sort.songs(songs))
@ -120,7 +115,7 @@ class SearchViewModel(application: Application) :
* Update the current filter mode with a menu [id]. New value will be pushed to [filterMode].
*/
fun updateFilterModeWithId(@IdRes id: Int) {
_filterMode =
val newFilterMode =
when (id) {
R.id.option_filter_songs -> DisplayMode.SHOW_SONGS
R.id.option_filter_albums -> DisplayMode.SHOW_ALBUMS
@ -129,9 +124,9 @@ class SearchViewModel(application: Application) :
else -> null
}
logD("Updating filter mode to $_filterMode")
logD("Updating filter mode to $newFilterMode")
settingsManager.searchFilterMode = _filterMode
settings.searchFilterMode = filterMode
search(lastQuery)
}

View file

@ -25,7 +25,6 @@ import android.os.Bundle
import android.view.LayoutInflater
import androidx.core.net.toUri
import androidx.core.view.updatePadding
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.oxycblt.auxio.BuildConfig
@ -37,6 +36,7 @@ import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.ui.ViewBindingFragment
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.formatDuration
import org.oxycblt.auxio.util.getSystemBarInsetsCompat
import org.oxycblt.auxio.util.launch
@ -49,7 +49,7 @@ import org.oxycblt.auxio.util.textSafe
* @author OxygenCobalt
*/
class AboutFragment : ViewBindingFragment<FragmentAboutBinding>() {
private val homeModel: HomeViewModel by activityViewModels()
private val homeModel: HomeViewModel by androidActivityViewModels()
override fun onCreateBinding(inflater: LayoutInflater) = FragmentAboutBinding.inflate(inflater)

View file

@ -0,0 +1,369 @@
/*
* Copyright (c) 2022 Auxio Project
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.settings
import android.content.Context
import android.content.SharedPreferences
import android.os.storage.StorageManager
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.edit
import androidx.fragment.app.Fragment
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.preference.PreferenceManager
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
import org.oxycblt.auxio.R
import org.oxycblt.auxio.home.tabs.Tab
import org.oxycblt.auxio.music.Directory
import org.oxycblt.auxio.music.dirs.MusicDirs
import org.oxycblt.auxio.playback.replaygain.ReplayGainMode
import org.oxycblt.auxio.playback.replaygain.ReplayGainPreAmp
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.accent.Accent
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.requireAttached
import org.oxycblt.auxio.util.unlikelyToBeNull
fun Fragment.settings(): ReadOnlyProperty<Fragment, Settings> =
object : ReadOnlyProperty<Fragment, Settings>, DefaultLifecycleObserver {
private var settings: Settings? = null
init {
lifecycle.addObserver(
object : DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
viewLifecycleOwnerLiveData.observe(this@settings) { viewLifecycleOwner ->
viewLifecycleOwner.lifecycle.addObserver(this)
}
}
})
}
override fun getValue(thisRef: Fragment, property: KProperty<*>): Settings {
requireAttached()
val currentSettings = settings
if (currentSettings != null) {
return currentSettings
}
val newSettings = Settings(requireContext())
settings = newSettings
return newSettings
}
override fun onDestroy(owner: LifecycleOwner) {
settings = null
}
}
class Settings(private val context: Context, private val callback: Callback? = null) :
SharedPreferences.OnSharedPreferenceChangeListener {
private val inner = PreferenceManager.getDefaultSharedPreferences(context.applicationContext)
init {
if (callback != null) {
inner.registerOnSharedPreferenceChangeListener(this)
}
}
fun release() {
inner.unregisterOnSharedPreferenceChangeListener(this)
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
val callback = unlikelyToBeNull(callback)
when (key) {
context.getString(R.string.set_key_alt_notif_action) ->
callback.onNotifSettingsChanged()
context.getString(R.string.set_key_show_covers),
context.getString(R.string.set_key_quality_covers) -> callback.onCoverSettingsChanged()
context.getString(R.string.set_key_lib_tabs) -> callback.onLibrarySettingsChanged()
context.getString(R.string.set_key_replay_gain),
context.getString(R.string.set_key_pre_amp_with),
context.getString(R.string.set_key_pre_amp_without) ->
callback.onReplayGainSettingsChanged()
}
}
// --- VALUES ---
/** The current theme */
val theme: Int
get() =
inner.getInt(
context.getString(R.string.set_key_theme),
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
/** Whether the dark theme should be black or not */
val useBlackTheme: Boolean
get() = inner.getBoolean(context.getString(R.string.set_key_black_theme), false)
/** The current accent. */
var accent: Accent
get() = handleAccentCompat(context, inner)
set(value) {
inner.edit {
putInt(context.getString(R.string.set_key_accent), value.index)
apply()
}
}
/**
* Whether to display the RepeatMode or the shuffle status on the notification. False if repeat,
* true if shuffle.
*/
val useAltNotifAction: Boolean
get() = inner.getBoolean(context.getString(R.string.set_key_alt_notif_action), false)
/** The current library tabs preferred by the user. */
var libTabs: Array<Tab>
get() =
Tab.fromSequence(
inner.getInt(context.getString(R.string.set_key_lib_tabs), Tab.SEQUENCE_DEFAULT))
?: unlikelyToBeNull(Tab.fromSequence(Tab.SEQUENCE_DEFAULT))
set(value) {
inner.edit {
putInt(context.getString(R.string.set_key_lib_tabs), Tab.toSequence(value))
apply()
}
}
/** Whether to load embedded covers */
val showCovers: Boolean
get() = inner.getBoolean(context.getString(R.string.set_key_show_covers), true)
/** Whether to ignore MediaStore covers */
val useQualityCovers: Boolean
get() = inner.getBoolean(context.getString(R.string.set_key_quality_covers), false)
/** Whether to round album covers */
val roundCovers: Boolean
get() = inner.getBoolean(context.getString(R.string.set_key_round_covers), false)
/** Whether to resume playback when a headset is connected (may not work well in all cases) */
val headsetAutoplay: Boolean
get() = inner.getBoolean(context.getString(R.string.set_key_headset_autoplay), false)
/** The current ReplayGain configuration */
val replayGainMode: ReplayGainMode
get() =
ReplayGainMode.fromIntCode(
inner.getInt(context.getString(R.string.set_key_replay_gain), Int.MIN_VALUE))
?: ReplayGainMode.OFF
/** The current ReplayGain pre-amp configuration */
var replayGainPreAmp: ReplayGainPreAmp
get() =
ReplayGainPreAmp(
inner.getFloat(context.getString(R.string.set_key_pre_amp_with), 0f),
inner.getFloat(context.getString(R.string.set_key_pre_amp_without), 0f))
set(value) {
inner.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)
apply()
}
}
/** What queue to create when a song is selected (ex. From All Songs or Search) */
val songPlaybackMode: PlaybackMode
get() =
PlaybackMode.fromInt(
inner.getInt(context.getString(R.string.set_key_song_play_mode), Int.MIN_VALUE))
?: PlaybackMode.ALL_SONGS
/** Whether shuffle should stay on when a new song is selected. */
val keepShuffle: Boolean
get() = inner.getBoolean(context.getString(R.string.set_key_keep_shuffle), true)
/** Whether to rewind when the back button is pressed. */
val rewindWithPrev: Boolean
get() = inner.getBoolean(context.getString(R.string.set_key_rewind_prev), true)
/**
* Whether [org.oxycblt.auxio.playback.state.RepeatMode.TRACK] should pause when the track
* repeats
*/
val pauseOnRepeat: Boolean
get() = inner.getBoolean(context.getString(R.string.set_key_repeat_pause), false)
/** Get the list of directories that music should be hidden/loaded from. */
fun getMusicDirs(storageManager: StorageManager): MusicDirs {
val key = context.getString(R.string.set_key_music_dirs)
if (!inner.contains(key)) {
logD("Attempting to migrate excluded directories")
// We need to migrate this setting now while we have a context. Note that while
// this does do IO work, the old excluded directory database is so small as to make
// it negligible.
setMusicDirs(MusicDirs(handleExcludedCompat(context, storageManager), false))
}
val dirs =
(inner.getStringSet(key, null) ?: emptySet()).mapNotNull {
Directory.fromDocumentUri(storageManager, it)
}
return MusicDirs(
dirs, inner.getBoolean(context.getString(R.string.set_key_music_dirs_include), false))
}
/** Set the list of directories that music should be hidden/loaded from. */
fun setMusicDirs(musicDirs: MusicDirs) {
inner.edit {
putStringSet(
context.getString(R.string.set_key_music_dirs),
musicDirs.dirs.map(Directory::toDocumentUri).toSet())
putBoolean(
context.getString(R.string.set_key_music_dirs_include), musicDirs.shouldInclude)
// TODO: This is a stopgap measure before automatic rescanning, remove
commit()
}
}
/** The current filter mode of the search tab */
var searchFilterMode: DisplayMode?
get() =
DisplayMode.fromInt(
inner.getInt(context.getString(R.string.set_key_search_filter), Int.MIN_VALUE))
set(value) {
inner.edit {
putInt(
context.getString(R.string.set_key_search_filter),
value?.intCode ?: Int.MIN_VALUE)
apply()
}
}
/** The song sort mode on HomeFragment */
var libSongSort: Sort
get() =
Sort.fromIntCode(
inner.getInt(context.getString(R.string.set_key_lib_songs_sort), Int.MIN_VALUE))
?: Sort.ByName(true)
set(value) {
inner.edit {
putInt(context.getString(R.string.set_key_lib_songs_sort), value.intCode)
apply()
}
}
/** The album sort mode on HomeFragment */
var libAlbumSort: Sort
get() =
Sort.fromIntCode(
inner.getInt(context.getString(R.string.set_key_lib_albums_sort), Int.MIN_VALUE))
?: Sort.ByName(true)
set(value) {
inner.edit {
putInt(context.getString(R.string.set_key_lib_albums_sort), value.intCode)
apply()
}
}
/** The artist sort mode on HomeFragment */
var libArtistSort: Sort
get() =
Sort.fromIntCode(
inner.getInt(context.getString(R.string.set_key_lib_artists_sort), Int.MIN_VALUE))
?: Sort.ByName(true)
set(value) {
inner.edit {
putInt(context.getString(R.string.set_key_lib_artists_sort), value.intCode)
apply()
}
}
/** The genre sort mode on HomeFragment */
var libGenreSort: Sort
get() =
Sort.fromIntCode(
inner.getInt(context.getString(R.string.set_key_lib_genres_sort), Int.MIN_VALUE))
?: Sort.ByName(true)
set(value) {
inner.edit {
putInt(context.getString(R.string.set_key_lib_genres_sort), value.intCode)
apply()
}
}
/** The detail album sort mode */
var detailAlbumSort: Sort
get() {
var sort =
Sort.fromIntCode(
inner.getInt(context.getString(R.string.set_key_lib_album_sort), Int.MIN_VALUE))
?: Sort.ByDisc(true)
// Correct legacy album sort modes to Disc
if (sort is Sort.ByName) {
sort = Sort.ByDisc(sort.isAscending)
}
return sort
}
set(value) {
inner.edit {
putInt(context.getString(R.string.set_key_lib_album_sort), value.intCode)
apply()
}
}
/** The detail artist sort mode */
var detailArtistSort: Sort
get() =
Sort.fromIntCode(
inner.getInt(context.getString(R.string.set_key_lib_artist_sort), Int.MIN_VALUE))
?: Sort.ByYear(false)
set(value) {
inner.edit {
putInt(context.getString(R.string.set_key_lib_artists_sort), value.intCode)
apply()
}
}
/** The detail genre sort mode */
var detailGenreSort: Sort
get() =
Sort.fromIntCode(
inner.getInt(context.getString(R.string.set_key_lib_genre_sort), Int.MIN_VALUE))
?: Sort.ByName(true)
set(value) {
inner.edit {
putInt(context.getString(R.string.set_key_lib_genre_sort), value.intCode)
apply()
}
}
/**
* An interface for receiving some preference updates. Use/Extend this instead of
* [SharedPreferences.OnSharedPreferenceChangeListener] if possible, as it doesn't require a
* context.
*/
interface Callback {
fun onLibrarySettingsChanged() {}
fun onNotifSettingsChanged() {}
fun onCoverSettingsChanged() {}
fun onReplayGainSettingsChanged() {}
}
}

View file

@ -26,6 +26,7 @@ import android.os.storage.StorageManager
import android.util.Log
import androidx.core.content.edit
import java.io.File
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.Directory
import org.oxycblt.auxio.music.directoryCompat
import org.oxycblt.auxio.music.isInternalCompat
@ -34,35 +35,11 @@ import org.oxycblt.auxio.ui.accent.Accent
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.queryAll
// A couple of utils for migrating from old settings values to the new formats
// A couple of utils for migrating from old settings values to the new formats.
// Usually, these will last for 6 months before being removed.
fun handleAccentCompat(prefs: SharedPreferences): Accent {
if (prefs.contains(OldKeys.KEY_ACCENT2)) {
Log.d("Auxio.SettingsCompat", "Migrating ${OldKeys.KEY_ACCENT2}")
var accent = prefs.getInt(OldKeys.KEY_ACCENT2, 5)
// Blue grey was merged with Light Blue in 2.0.0
if (accent >= 17) {
accent = 6
}
// Deep Orange was merged with red in 2.0.0
if (accent == 14) {
accent = 0
}
// Correct accents beyond deep orange (Brown/Grey)
if (accent > 14) {
accent--
}
prefs.edit {
putInt(OldKeys.KEY_ACCENT3, accent)
remove(OldKeys.KEY_ACCENT2)
apply()
}
}
fun handleAccentCompat(context: Context, prefs: SharedPreferences): Accent {
val currentKey = context.getString(R.string.set_key_accent)
if (prefs.contains(OldKeys.KEY_ACCENT3)) {
Log.d("Auxio.SettingsCompat", "Migrating ${OldKeys.KEY_ACCENT3}")
@ -76,13 +53,13 @@ fun handleAccentCompat(prefs: SharedPreferences): Accent {
}
prefs.edit {
putInt(SettingsManager.KEY_ACCENT, accent)
putInt(currentKey, accent)
remove(OldKeys.KEY_ACCENT3)
apply()
}
}
return Accent.from(prefs.getInt(SettingsManager.KEY_ACCENT, Accent.DEFAULT))
return Accent.from(prefs.getInt(currentKey, Accent.DEFAULT))
}
/**
@ -154,6 +131,5 @@ class LegacyExcludedDatabase(context: Context) :
/** Cache of the old keys used in Auxio. */
private object OldKeys {
const val KEY_ACCENT2 = "KEY_ACCENT2"
const val KEY_ACCENT3 = "auxio_accent"
}

View file

@ -22,7 +22,6 @@ import android.view.View
import androidx.annotation.DrawableRes
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.view.updatePadding
import androidx.fragment.app.activityViewModels
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
@ -38,6 +37,7 @@ import org.oxycblt.auxio.playback.replaygain.ReplayGainMode
import org.oxycblt.auxio.settings.ui.IntListPreference
import org.oxycblt.auxio.settings.ui.IntListPreferenceDialog
import org.oxycblt.auxio.ui.accent.AccentDialog
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.getSystemBarInsetsCompat
import org.oxycblt.auxio.util.hardRestart
import org.oxycblt.auxio.util.isNight
@ -54,8 +54,8 @@ import org.oxycblt.auxio.util.showToast
*/
@Suppress("UNUSED")
class SettingsListFragment : PreferenceFragmentCompat() {
private val playbackModel: PlaybackViewModel by activityViewModels()
val settingsManager = SettingsManager.getInstance()
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
private val settings: Settings by settings()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -112,7 +112,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
preference.apply {
when (key) {
SettingsManager.KEY_THEME -> {
getString(R.string.set_key_theme) -> {
setIcon(AppCompatDelegate.getDefaultNightMode().toThemeIcon())
onPreferenceChangeListener =
@ -122,16 +122,17 @@ class SettingsListFragment : PreferenceFragmentCompat() {
true
}
}
SettingsManager.KEY_ACCENT -> {
getString(R.string.set_key_accent) -> {
onPreferenceClickListener =
Preference.OnPreferenceClickListener {
AccentDialog().show(childFragmentManager, AccentDialog.TAG)
true
}
summary = context.getString(settingsManager.accent.name)
// TODO: Replace with preference impl
summary = context.getString(settings.accent.name)
}
SettingsManager.KEY_BLACK_THEME -> {
getString(R.string.set_key_black_theme) -> {
onPreferenceClickListener =
Preference.OnPreferenceClickListener {
if (requireContext().isNight) {
@ -141,23 +142,23 @@ class SettingsListFragment : PreferenceFragmentCompat() {
true
}
}
SettingsManager.KEY_LIB_TABS -> {
getString(R.string.set_key_lib_tabs) -> {
onPreferenceClickListener =
Preference.OnPreferenceClickListener {
TabCustomizeDialog().show(childFragmentManager, TabCustomizeDialog.TAG)
true
}
}
SettingsManager.KEY_SHOW_COVERS,
SettingsManager.KEY_QUALITY_COVERS -> {
getString(R.string.set_key_show_covers),
getString(R.string.set_key_quality_covers) -> {
onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _, _ ->
Coil.imageLoader(requireContext()).apply { this.memoryCache?.clear() }
true
}
}
SettingsManager.KEY_REPLAY_GAIN -> {
notifyDependencyChange(settingsManager.replayGainMode == ReplayGainMode.OFF)
getString(R.string.set_key_replay_gain) -> {
notifyDependencyChange(settings.replayGainMode == ReplayGainMode.OFF)
onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _, value ->
notifyDependencyChange(
@ -165,7 +166,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
true
}
}
SettingsManager.KEY_PRE_AMP -> {
getString(R.string.set_key_pre_amp) -> {
onPreferenceClickListener =
Preference.OnPreferenceClickListener {
PreAmpCustomizeDialog()
@ -173,9 +174,10 @@ class SettingsListFragment : PreferenceFragmentCompat() {
true
}
}
SettingsManager.KEY_SAVE_STATE -> {
getString(R.string.set_key_save_state) -> {
onPreferenceClickListener =
Preference.OnPreferenceClickListener {
// FIXME: Callback can still occur on non-attached fragment
playbackModel.savePlaybackState(requireContext()) {
requireContext().showToast(R.string.lbl_state_saved)
}
@ -183,7 +185,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
true
}
}
SettingsManager.KEY_REINDEX -> {
getString(R.string.set_key_reindex) -> {
onPreferenceClickListener =
Preference.OnPreferenceClickListener {
playbackModel.savePlaybackState(requireContext()) {
@ -193,7 +195,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
true
}
}
SettingsManager.KEY_MUSIC_DIRS -> {
getString(R.string.set_key_music_dirs) -> {
onPreferenceClickListener =
Preference.OnPreferenceClickListener {
MusicDirsDialog().show(childFragmentManager, MusicDirsDialog.TAG)

View file

@ -1,377 +0,0 @@
/*
* Copyright (c) 2021 Auxio Project
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.settings
import android.content.Context
import android.content.SharedPreferences
import android.os.storage.StorageManager
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import org.oxycblt.auxio.home.tabs.Tab
import org.oxycblt.auxio.music.Directory
import org.oxycblt.auxio.music.dirs.MusicDirs
import org.oxycblt.auxio.playback.replaygain.ReplayGainMode
import org.oxycblt.auxio.playback.replaygain.ReplayGainPreAmp
import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.ui.DisplayMode
import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.ui.accent.Accent
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.unlikelyToBeNull
/**
* Wrapper around the [SharedPreferences] class that writes & reads values without a context.
* @author OxygenCobalt
*/
class SettingsManager private constructor(context: Context) :
SharedPreferences.OnSharedPreferenceChangeListener {
private val inner = PreferenceManager.getDefaultSharedPreferences(context)
// --- VALUES ---
/** The current theme */
val theme: Int
get() = inner.getInt(KEY_THEME, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
/** Whether the dark theme should be black or not */
val useBlackTheme: Boolean
get() = inner.getBoolean(KEY_BLACK_THEME, false)
/** The current accent. */
var accent: Accent
get() = handleAccentCompat(inner)
set(value) {
inner.edit {
putInt(KEY_ACCENT, value.index)
apply()
}
}
/**
* Whether to display the RepeatMode or the shuffle status on the notification. False if repeat,
* true if shuffle.
*/
val useAltNotifAction: Boolean
get() = inner.getBoolean(KEY_USE_ALT_NOTIFICATION_ACTION, false)
/** The current library tabs preferred by the user. */
var libTabs: Array<Tab>
get() =
Tab.fromSequence(inner.getInt(KEY_LIB_TABS, Tab.SEQUENCE_DEFAULT))
?: unlikelyToBeNull(Tab.fromSequence(Tab.SEQUENCE_DEFAULT))
set(value) {
inner.edit {
putInt(KEY_LIB_TABS, Tab.toSequence(value))
apply()
}
}
/** Whether to load embedded covers */
val showCovers: Boolean
get() = inner.getBoolean(KEY_SHOW_COVERS, true)
/** Whether to ignore MediaStore covers */
val useQualityCovers: Boolean
get() = inner.getBoolean(KEY_QUALITY_COVERS, false)
/** Whether to round album covers */
val roundCovers: Boolean
get() = inner.getBoolean(KEY_ROUND_COVERS, false)
/** Whether to resume playback when a headset is connected (may not work well in all cases) */
val headsetAutoplay: Boolean
get() = inner.getBoolean(KEY_HEADSET_AUTOPLAY, false)
/** The current ReplayGain configuration */
val replayGainMode: ReplayGainMode
get() =
ReplayGainMode.fromIntCode(inner.getInt(KEY_REPLAY_GAIN, Int.MIN_VALUE))
?: ReplayGainMode.OFF
/** The current ReplayGain pre-amp configuration */
var replayGainPreAmp: ReplayGainPreAmp
get() =
ReplayGainPreAmp(
inner.getFloat(KEY_PRE_AMP_WITH, 0f), inner.getFloat(KEY_PRE_AMP_WITHOUT, 0f))
set(value) {
inner.edit {
putFloat(KEY_PRE_AMP_WITH, value.with)
putFloat(KEY_PRE_AMP_WITHOUT, value.without)
apply()
}
}
/** What queue to create when a song is selected (ex. From All Songs or Search) */
val songPlaybackMode: PlaybackMode
get() =
PlaybackMode.fromInt(inner.getInt(KEY_SONG_PLAYBACK_MODE, Int.MIN_VALUE))
?: PlaybackMode.ALL_SONGS
/** Whether shuffle should stay on when a new song is selected. */
val keepShuffle: Boolean
get() = inner.getBoolean(KEY_KEEP_SHUFFLE, true)
/** Whether to rewind when the back button is pressed. */
val rewindWithPrev: Boolean
get() = inner.getBoolean(KEY_PREV_REWIND, true)
/**
* Whether [org.oxycblt.auxio.playback.state.RepeatMode.TRACK] should pause when the track
* repeats
*/
val pauseOnRepeat: Boolean
get() = inner.getBoolean(KEY_PAUSE_ON_REPEAT, false)
/** Get the list of directories that music should be hidden/loaded from. */
fun getMusicDirs(context: Context, storageManager: StorageManager): MusicDirs {
if (!inner.contains(KEY_MUSIC_DIRS)) {
logD("Attempting to migrate excluded directories")
// We need to migrate this setting now while we have a context. Note that while
// this does do IO work, the old excluded directory database is so small as to make
// it negligible.
setMusicDirs(MusicDirs(handleExcludedCompat(context, storageManager), false))
}
val dirs =
(inner.getStringSet(KEY_MUSIC_DIRS, null) ?: emptySet()).mapNotNull {
Directory.fromDocumentUri(storageManager, it)
}
return MusicDirs(dirs, inner.getBoolean(KEY_SHOULD_INCLUDE, false))
}
/** Set the list of directories that music should be hidden/loaded from. */
fun setMusicDirs(musicDirs: MusicDirs) {
inner.edit {
putStringSet(KEY_MUSIC_DIRS, musicDirs.dirs.map(Directory::toDocumentUri).toSet())
putBoolean(KEY_SHOULD_INCLUDE, musicDirs.shouldInclude)
// TODO: This is a stopgap measure before automatic rescanning, remove
commit()
}
}
/** The current filter mode of the search tab */
var searchFilterMode: DisplayMode?
get() = DisplayMode.fromInt(inner.getInt(KEY_SEARCH_FILTER_MODE, Int.MIN_VALUE))
set(value) {
inner.edit {
putInt(KEY_SEARCH_FILTER_MODE, value?.intCode ?: Int.MIN_VALUE)
apply()
}
}
/** The song sort mode on HomeFragment */
var libSongSort: Sort
get() =
Sort.fromIntCode(inner.getInt(KEY_LIB_SONGS_SORT, Int.MIN_VALUE)) ?: Sort.ByName(true)
set(value) {
inner.edit {
putInt(KEY_LIB_SONGS_SORT, value.intCode)
apply()
}
}
/** The album sort mode on HomeFragment */
var libAlbumSort: Sort
get() =
Sort.fromIntCode(inner.getInt(KEY_LIB_ALBUMS_SORT, Int.MIN_VALUE)) ?: Sort.ByName(true)
set(value) {
inner.edit {
putInt(KEY_LIB_ALBUMS_SORT, value.intCode)
apply()
}
}
/** The artist sort mode on HomeFragment */
var libArtistSort: Sort
get() =
Sort.fromIntCode(inner.getInt(KEY_LIB_ARTISTS_SORT, Int.MIN_VALUE)) ?: Sort.ByName(true)
set(value) {
inner.edit {
putInt(KEY_LIB_ARTISTS_SORT, value.intCode)
apply()
}
}
/** The genre sort mode on HomeFragment */
var libGenreSort: Sort
get() =
Sort.fromIntCode(inner.getInt(KEY_LIB_GENRES_SORT, Int.MIN_VALUE)) ?: Sort.ByName(true)
set(value) {
inner.edit {
putInt(KEY_LIB_GENRES_SORT, value.intCode)
apply()
}
}
/** The detail album sort mode */
var detailAlbumSort: Sort
get() {
var sort =
Sort.fromIntCode(inner.getInt(KEY_DETAIL_ALBUM_SORT, Int.MIN_VALUE))
?: Sort.ByDisc(true)
// Correct legacy album sort modes to Disc
if (sort is Sort.ByName) {
sort = Sort.ByDisc(sort.isAscending)
}
return sort
}
set(value) {
inner.edit {
putInt(KEY_DETAIL_ALBUM_SORT, value.intCode)
apply()
}
}
/** The detail artist sort mode */
var detailArtistSort: Sort
get() =
Sort.fromIntCode(inner.getInt(KEY_DETAIL_ARTIST_SORT, Int.MIN_VALUE))
?: Sort.ByYear(false)
set(value) {
inner.edit {
putInt(KEY_DETAIL_ARTIST_SORT, value.intCode)
apply()
}
}
/** The detail genre sort mode */
var detailGenreSort: Sort
get() =
Sort.fromIntCode(inner.getInt(KEY_DETAIL_GENRE_SORT, Int.MIN_VALUE))
?: Sort.ByName(true)
set(value) {
inner.edit {
putInt(KEY_DETAIL_GENRE_SORT, value.intCode)
apply()
}
}
init {
inner.registerOnSharedPreferenceChangeListener(this)
}
// --- CALLBACKS ---
private val callbacks = mutableListOf<Callback>()
fun addCallback(callback: Callback) {
callbacks.add(callback)
}
fun removeCallback(callback: Callback) {
callbacks.remove(callback)
}
// --- OVERRIDES ---
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
when (key) {
KEY_USE_ALT_NOTIFICATION_ACTION -> callbacks.forEach { it.onNotifSettingsChanged() }
KEY_SHOW_COVERS,
KEY_QUALITY_COVERS -> callbacks.forEach { it.onCoverSettingsChanged() }
KEY_LIB_TABS -> callbacks.forEach { it.onLibrarySettingsChanged() }
KEY_REPLAY_GAIN,
KEY_PRE_AMP_WITH,
KEY_PRE_AMP_WITHOUT -> callbacks.forEach { it.onReplayGainSettingsChanged() }
}
}
/**
* An interface for receiving some preference updates. Use/Extend this instead of
* [SharedPreferences.OnSharedPreferenceChangeListener] if possible, as it doesn't require a
* context.
*/
interface Callback {
fun onLibrarySettingsChanged() {}
fun onNotifSettingsChanged() {}
fun onCoverSettingsChanged() {}
fun onReplayGainSettingsChanged() {}
}
companion object {
// Note: The old way of naming keys was to prefix them with KEY_. Now it's to prefix them
// with auxio_.
const val KEY_THEME = "KEY_THEME2"
const val KEY_BLACK_THEME = "KEY_BLACK_THEME"
const val KEY_ACCENT = "auxio_accent2"
const val KEY_LIB_TABS = "auxio_lib_tabs"
const val KEY_SHOW_COVERS = "KEY_SHOW_COVERS"
const val KEY_QUALITY_COVERS = "KEY_QUALITY_COVERS"
const val KEY_ROUND_COVERS = "auxio_round_covers"
const val KEY_USE_ALT_NOTIFICATION_ACTION = "KEY_ALT_NOTIF_ACTION"
const val KEY_HEADSET_AUTOPLAY = "auxio_headset_autoplay"
const val KEY_REPLAY_GAIN = "auxio_replay_gain"
const val KEY_PRE_AMP = "auxio_pre_amp"
const val KEY_PRE_AMP_WITH = "auxio_pre_amp_with"
const val KEY_PRE_AMP_WITHOUT = "auxio_pre_amp_without"
const val KEY_SONG_PLAYBACK_MODE = "KEY_SONG_PLAY_MODE2"
const val KEY_KEEP_SHUFFLE = "KEY_KEEP_SHUFFLE"
const val KEY_PREV_REWIND = "KEY_PREV_REWIND"
const val KEY_PAUSE_ON_REPEAT = "KEY_LOOP_PAUSE"
const val KEY_SAVE_STATE = "auxio_save_state"
const val KEY_REINDEX = "auxio_reindex"
const val KEY_MUSIC_DIRS = "auxio_music_dirs"
const val KEY_SHOULD_INCLUDE = "auxio_include_dirs"
const val KEY_SEARCH_FILTER_MODE = "KEY_SEARCH_FILTER"
const val KEY_LIB_SONGS_SORT = "auxio_songs_sort"
const val KEY_LIB_ALBUMS_SORT = "auxio_albums_sort"
const val KEY_LIB_ARTISTS_SORT = "auxio_artists_sort"
const val KEY_LIB_GENRES_SORT = "auxio_genres_sort"
const val KEY_DETAIL_ALBUM_SORT = "auxio_album_sort"
const val KEY_DETAIL_ARTIST_SORT = "auxio_artist_sort"
const val KEY_DETAIL_GENRE_SORT = "auxio_genre_sort"
@Volatile private var INSTANCE: SettingsManager? = null
/**
* Init the single instance of [SettingsManager]. Done so that every object can have access
* to it regardless of if it has a context.
*/
fun init(context: Context): SettingsManager {
if (INSTANCE == null) {
synchronized(this) { INSTANCE = SettingsManager(context) }
}
return getInstance()
}
/** Get the single instance of [SettingsManager]. */
fun getInstance(): SettingsManager {
val instance = INSTANCE
if (instance != null) {
return instance
}
error("SettingsManager must be initialized with init() before getting its instance")
}
}
}

View file

@ -29,6 +29,7 @@ import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logEOrThrow
import org.oxycblt.auxio.util.showToast
@ -41,7 +42,7 @@ import org.oxycblt.auxio.util.showToast
abstract class MenuFragment<T : ViewBinding> : ViewBindingFragment<T>() {
private var currentMenu: PopupMenu? = null
protected val playbackModel: PlaybackViewModel by activityViewModels()
protected val playbackModel: PlaybackViewModel by androidActivityViewModels()
protected val navModel: NavigationViewModel by activityViewModels()
/**

View file

@ -23,7 +23,8 @@ import androidx.appcompat.app.AlertDialog
import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogAccentBinding
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.settings.settings
import org.oxycblt.auxio.ui.ViewBindingDialogFragment
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.unlikelyToBeNull
@ -33,8 +34,8 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
* @author OxygenCobalt
*/
class AccentDialog : ViewBindingDialogFragment<DialogAccentBinding>(), AccentAdapter.Listener {
private val settingsManager = SettingsManager.getInstance()
private var accentAdapter = AccentAdapter(this)
private val settings: Settings by settings()
override fun onCreateBinding(inflater: LayoutInflater) = DialogAccentBinding.inflate(inflater)
@ -42,9 +43,9 @@ class AccentDialog : ViewBindingDialogFragment<DialogAccentBinding>(), AccentAda
builder
.setTitle(R.string.set_accent)
.setPositiveButton(R.string.lbl_ok) { _, _ ->
if (accentAdapter.selectedAccent != settingsManager.accent) {
if (accentAdapter.selectedAccent != settings.accent) {
logD("Applying new accent")
settingsManager.accent = unlikelyToBeNull(accentAdapter.selectedAccent)
settings.accent = unlikelyToBeNull(accentAdapter.selectedAccent)
requireActivity().recreate()
}
@ -59,7 +60,7 @@ class AccentDialog : ViewBindingDialogFragment<DialogAccentBinding>(), AccentAda
if (savedInstanceState != null) {
Accent.from(savedInstanceState.getInt(KEY_PENDING_ACCENT))
} else {
settingsManager.accent
settings.accent
})
}

View file

@ -27,7 +27,9 @@ import android.os.Build
import android.view.View
import android.view.WindowInsets
import android.widget.TextView
import androidx.activity.viewModels
import androidx.annotation.ColorRes
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.Insets
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.view.WindowInsetsCompat
@ -175,6 +177,9 @@ fun Fragment.launch(
inline fun <reified T : AndroidViewModel> Fragment.androidViewModels() =
viewModels<T> { ViewModelProvider.AndroidViewModelFactory(requireActivity().application) }
inline fun <reified T : AndroidViewModel> AppCompatActivity.androidViewModels() =
viewModels<T> { ViewModelProvider.AndroidViewModelFactory(application) }
inline fun <reified T : AndroidViewModel> Fragment.androidActivityViewModels() =
activityViewModels<T> {
ViewModelProvider.AndroidViewModelFactory(requireActivity().application)

View file

@ -29,7 +29,7 @@ import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.getDimenSizeSafe
import org.oxycblt.auxio.util.logD
@ -41,15 +41,14 @@ import org.oxycblt.auxio.util.logD
* @author OxygenCobalt
*/
class WidgetComponent(private val context: Context) :
PlaybackStateManager.Callback, SettingsManager.Callback {
PlaybackStateManager.Callback, Settings.Callback {
private val playbackManager = PlaybackStateManager.getInstance()
private val settingsManager = SettingsManager.getInstance()
private val settings = Settings(context, this)
private val widget = WidgetProvider()
private val provider = BitmapProvider(context)
init {
playbackManager.addCallback(this)
settingsManager.addCallback(this)
if (playbackManager.isInitialized) {
update()
@ -129,9 +128,9 @@ class WidgetComponent(private val context: Context) :
*/
fun release() {
provider.release()
settings.release()
widget.reset(context)
playbackManager.removeCallback(this)
settingsManager.removeCallback(this)
}
// --- CALLBACKS ---

View file

@ -2,4 +2,5 @@
<resources>
<bool name="enable_theme_settings">true</bool>
<integer name="recycler_spans">1</integer>
<integer name="detail_app_bar_title_anim_duration">150</integer>
</resources>

View file

@ -1,62 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="detail_app_bar_title_anim_duration">150</integer>
<!--
FIXME: Unify SettingsManager into this by making it context-dependent again (requires a bunch
of technical reworks) -->
<!-- Preference values -->
<string-array name="entries_theme">
<item>@string/set_theme_auto</item>
<item>@string/set_theme_day</item>
<item>@string/set_theme_night</item>
</string-array>
<integer-array name="values_theme">
<item>@integer/theme_auto</item>
<item>@integer/theme_light</item>
<item>@integer/theme_dark</item>
</integer-array>
<array name="entries_song_playback_mode">
<item>@string/lbl_play_all</item>
<item>@string/lbl_play_artist</item>
<item>@string/lbl_play_album</item>
<item>@string/lbl_play_genre</item>
</array>
<string-array name="values_song_playback_mode">
<item>@integer/play_mode_songs</item>
<item>@integer/play_mode_artist</item>
<item>@integer/play_mode_album</item>
<item>@integer/play_mode_genre</item>
</string-array>
<array name="entries_replay_gain">
<item>@string/lbl_off</item>
<item>@string/set_replay_gain_track</item>
<item>@string/set_replay_gain_album</item>
<item>@string/set_replay_gain_dynamic</item>
</array>
<string-array name="values_replay_gain">
<item>@integer/replay_gain_off</item>
<item>@integer/replay_gain_track</item>
<item>@integer/replay_gain_album</item>
<item>@integer/replay_gain_dynamic</item>
</string-array>
<integer name="theme_auto">-1</integer>
<integer name="theme_light">1</integer>
<integer name="theme_dark">2</integer>
<integer name="play_mode_genre">0xA103</integer>
<integer name="play_mode_artist">0xA104</integer>
<integer name="play_mode_album">0xA105</integer>
<integer name="play_mode_songs">0xA106</integer>
<integer name="replay_gain_off">0xA110</integer>
<integer name="replay_gain_track">0xA111</integer>
<integer name="replay_gain_album">0xA112</integer>
<integer name="replay_gain_dynamic">0xA113</integer>
</resources>

View file

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Note: The old way of naming keys was to prefix them with KEY_. Now it's to prefix them with auxio_. -->
<string name="set_key_theme" translatable="false">KEY_THEME2</string>
<string name="set_key_black_theme" translatable="false">KEY_BLACK_THEME</string>
<string name="set_key_accent" translatable="false">auxio_accent2</string>
<string name="set_key_lib_tabs" translatable="false">auxio_lib_tabs</string>
<string name="set_key_show_covers" translatable="false">KEY_SHOW_COVERS</string>
<string name="set_key_quality_covers" translatable="false">KEY_QUALITY_COVERS</string>
<string name="set_key_round_covers" translatable="false">auxio_round_covers</string>
<string name="set_key_alt_notif_action" translatable="false">KEY_ALT_NOTIF_ACTION</string>
<string name="set_key_headset_autoplay" translatable="false">auxio_headset_autoplay</string>
<string name="set_key_replay_gain" translatable="false">auxio_replay_gain</string>
<string name="set_key_pre_amp" translatable="false">auxio_pre_amp</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_song_play_mode" translatable="false">KEY_SONG_PLAY_MODE2</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>
<string name="set_key_save_state" translatable="false">auxio_save_state</string>
<string name="set_key_reindex" translatable="false">auxio_reindex</string>
<string name="set_key_music_dirs" translatable="false">auxio_music_dirs</string>
<string name="set_key_music_dirs_include" translatable="false">auxio_include_dirs</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_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_lib_album_sort" translatable="false">auxio_album_sort</string>
<string name="set_key_lib_artist_sort" translatable="false">auxio_artist_sort</string>
<string name="set_key_lib_genre_sort" translatable="false">auxio_genre_sort</string>
<string-array name="entries_theme">
<item>@string/set_theme_auto</item>
<item>@string/set_theme_day</item>
<item>@string/set_theme_night</item>
</string-array>
<integer-array name="values_theme">
<item>@integer/theme_auto</item>
<item>@integer/theme_light</item>
<item>@integer/theme_dark</item>
</integer-array>
<array name="entries_song_playback_mode">
<item>@string/lbl_play_all</item>
<item>@string/lbl_play_artist</item>
<item>@string/lbl_play_album</item>
<item>@string/lbl_play_genre</item>
</array>
<string-array name="values_song_playback_mode">
<item>@integer/play_mode_songs</item>
<item>@integer/play_mode_artist</item>
<item>@integer/play_mode_album</item>
<item>@integer/play_mode_genre</item>
</string-array>
<array name="entries_replay_gain">
<item>@string/lbl_off</item>
<item>@string/set_replay_gain_track</item>
<item>@string/set_replay_gain_album</item>
<item>@string/set_replay_gain_dynamic</item>
</array>
<string-array name="values_replay_gain">
<item>@integer/replay_gain_off</item>
<item>@integer/replay_gain_track</item>
<item>@integer/replay_gain_album</item>
<item>@integer/replay_gain_dynamic</item>
</string-array>
<integer name="theme_auto">-1</integer>
<integer name="theme_light">1</integer>
<integer name="theme_dark">2</integer>
<integer name="play_mode_genre">0xA103</integer>
<integer name="play_mode_artist">0xA104</integer>
<integer name="play_mode_album">0xA105</integer>
<integer name="play_mode_songs">0xA106</integer>
<integer name="replay_gain_off">0xA110</integer>
<integer name="replay_gain_track">0xA111</integer>
<integer name="replay_gain_album">0xA112</integer>
<integer name="replay_gain_dynamic">0xA113</integer>
</resources>

View file

@ -11,19 +11,19 @@
app:icon="@drawable/ic_light"
app:iconSpaceReserved="false"
app:isPreferenceVisible="@bool/enable_theme_settings"
app:key="KEY_THEME2"
app:key="@string/set_key_theme"
app:title="@string/set_theme" />
<Preference
app:icon="@drawable/ic_accent"
app:key="auxio_accent2"
app:key="@string/set_key_accent"
app:title="@string/set_accent" />
<org.oxycblt.auxio.settings.ui.M3SwitchPreference
app:allowDividerBelow="false"
app:defaultValue="false"
app:iconSpaceReserved="false"
app:key="KEY_BLACK_THEME"
app:key="@string/set_key_black_theme"
app:summary="@string/set_black_mode_desc"
app:title="@string/set_black_mode" />
@ -35,29 +35,29 @@
<Preference
app:iconSpaceReserved="false"
app:key="auxio_lib_tabs"
app:key="@string/set_key_lib_tabs"
app:summary="@string/set_lib_tabs_desc"
app:title="@string/set_lib_tabs" />
<org.oxycblt.auxio.settings.ui.M3SwitchPreference
app:defaultValue="true"
app:iconSpaceReserved="false"
app:key="KEY_SHOW_COVERS"
app:key="@string/set_key_show_covers"
app:summary="@string/set_show_covers_desc"
app:title="@string/set_show_covers" />
<org.oxycblt.auxio.settings.ui.M3SwitchPreference
app:defaultValue="false"
app:dependency="KEY_SHOW_COVERS"
app:dependency="@string/set_key_show_covers"
app:iconSpaceReserved="false"
app:key="KEY_QUALITY_COVERS"
app:key="@string/set_key_quality_covers"
app:summary="@string/set_quality_covers_desc"
app:title="@string/set_quality_covers" />
<org.oxycblt.auxio.settings.ui.M3SwitchPreference
app:defaultValue="false"
app:iconSpaceReserved="false"
app:key="auxio_round_covers"
app:key="@string/set_key_round_covers"
app:summary="@string/set_round_covers_desc"
app:title="@string/set_round_covers" />
@ -65,7 +65,7 @@
app:allowDividerBelow="false"
app:defaultValue="false"
app:iconSpaceReserved="false"
app:key="KEY_ALT_NOTIF_ACTION"
app:key="@string/set_key_alt_notif_action"
app:summaryOff="@string/set_alt_repeat"
app:summaryOn="@string/set_alt_shuffle"
app:title="@string/set_alt_action" />
@ -79,7 +79,7 @@
<org.oxycblt.auxio.settings.ui.M3SwitchPreference
app:defaultValue="false"
app:iconSpaceReserved="false"
app:key="auxio_headset_autoplay"
app:key="@string/set_key_headset_autoplay"
app:summary="@string/set_headset_autoplay_desc"
app:title="@string/set_headset_autoplay" />
@ -89,14 +89,14 @@
app:entries="@array/entries_replay_gain"
app:entryValues="@array/values_replay_gain"
app:iconSpaceReserved="false"
app:key="auxio_replay_gain"
app:key="@string/set_key_replay_gain"
app:title="@string/set_replay_gain" />
<Preference
app:allowDividerBelow="false"
app:dependency="auxio_replay_gain"
app:dependency="@string/set_key_replay_gain"
app:iconSpaceReserved="false"
app:key="auxio_pre_amp"
app:key="@string/set_key_pre_amp"
app:summary="@string/set_pre_amp_desc"
app:title="@string/set_pre_amp" />
@ -111,14 +111,14 @@
app:entries="@array/entries_song_playback_mode"
app:entryValues="@array/values_song_playback_mode"
app:iconSpaceReserved="false"
app:key="KEY_SONG_PLAY_MODE2"
app:key="@string/set_key_song_play_mode"
app:title="@string/set_song_mode"
app:useSimpleSummaryProvider="true" />
<org.oxycblt.auxio.settings.ui.M3SwitchPreference
app:defaultValue="true"
app:iconSpaceReserved="false"
app:key="KEY_KEEP_SHUFFLE"
app:key="@string/set_key_keep_shuffle"
app:summary="@string/set_keep_shuffle_desc"
app:title="@string/set_keep_shuffle" />
@ -126,7 +126,7 @@
app:allowDividerBelow="false"
app:defaultValue="true"
app:iconSpaceReserved="false"
app:key="KEY_PREV_REWIND"
app:key="@string/set_key_rewind_prev"
app:summary="@string/set_rewind_prev_desc"
app:title="@string/set_rewind_prev" />
@ -134,7 +134,7 @@
app:allowDividerBelow="false"
app:defaultValue="false"
app:iconSpaceReserved="false"
app:key="KEY_LOOP_PAUSE"
app:key="@string/set_key_repeat_pause"
app:summary="@string/set_repeat_pause_desc"
app:title="@string/set_repeat_pause" />
@ -146,19 +146,19 @@
<Preference
app:iconSpaceReserved="false"
app:key="auxio_save_state"
app:key="@string/set_key_save_state"
app:summary="@string/set_save_desc"
app:title="@string/set_save" />
<Preference
app:iconSpaceReserved="false"
app:key="auxio_reindex"
app:key="@string/set_key_reindex"
app:summary="@string/set_reindex_desc"
app:title="@string/set_reindex" />
<Preference
app:iconSpaceReserved="false"
app:key="auxio_music_dirs"
app:key="@string/set_key_music_dirs"
app:summary="@string/set_dirs_desc"
app:title="@string/set_dirs" />