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 seam that would appear on some album covers
- Fixed visual issue with the queue opening animation - 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 ## v2.4.0
#### What's New #### 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.CrossfadeTransitionFactory
import org.oxycblt.auxio.image.GenreImageFetcher import org.oxycblt.auxio.image.GenreImageFetcher
import org.oxycblt.auxio.image.MusicKeyer import org.oxycblt.auxio.image.MusicKeyer
import org.oxycblt.auxio.settings.SettingsManager
import org.oxycblt.auxio.util.logD
class AuxioApp : Application(), ImageLoaderFactory { class AuxioApp : Application(), ImageLoaderFactory {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
logD(BuildConfig.APPLICATION_ID + ".MainActivity")
// Adding static shortcuts in a dynamic manner is better than declaring them // Adding static shortcuts in a dynamic manner is better than declaring them
// manually, as it will properly handle the difference between debug and release // manually, as it will properly handle the difference between debug and release
// Auxio instances. // Auxio instances.
@ -54,10 +50,6 @@ class AuxioApp : Application(), ImageLoaderFactory {
action = INTENT_KEY_SHORTCUT_SHUFFLE action = INTENT_KEY_SHORTCUT_SHUFFLE
}) })
.build())) .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 { override fun newImageLoader(): ImageLoader {

View file

@ -18,11 +18,9 @@
package org.oxycblt.auxio package org.oxycblt.auxio
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.view.WindowCompat 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.music.IndexerService
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.system.PlaybackService 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.getSystemBarInsetsCompat
import org.oxycblt.auxio.util.isNight import org.oxycblt.auxio.util.isNight
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
@ -51,7 +51,7 @@ import org.oxycblt.auxio.util.logD
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private val playbackModel: PlaybackViewModel by viewModels() private val playbackModel: PlaybackViewModel by androidViewModels()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -72,17 +72,13 @@ class MainActivity : AppCompatActivity() {
startService(Intent(this, PlaybackService::class.java)) startService(Intent(this, PlaybackService::class.java))
// If we have a valid intent, use that. Otherwise, restore the playback state. // If we have a valid intent, use that. Otherwise, restore the playback state.
val action = intentToDelayedAction(intent) ?: PlaybackViewModel.DelayedAction.RestoreState playbackModel.startDelayedAction(
intentToDelayedAction(intent) ?: PlaybackViewModel.DelayedAction.RestoreState)
playbackModel.startDelayedAction(this, action)
} }
override fun onNewIntent(intent: Intent?) { override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent) super.onNewIntent(intent)
intentToDelayedAction(intent)?.let(playbackModel::startDelayedAction)
intentToDelayedAction(intent)?.let { action ->
playbackModel.startDelayedAction(this, action)
}
} }
private fun intentToDelayedAction(intent: Intent?): PlaybackViewModel.DelayedAction? { 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() { 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 // 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. // the point where most phones should have an automatic option for light/dark theming.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
AppCompatDelegate.setDefaultNightMode(settingsManager.theme) AppCompatDelegate.setDefaultNightMode(settings.theme)
} else { } else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
} }
// The black theme has a completely separate set of styles since style attributes cannot // The black theme has a completely separate set of styles since style attributes cannot
// be modified at runtime. // be modified at runtime.
if (isNight && settingsManager.useBlackTheme) { if (isNight && settings.useBlackTheme) {
logD("Applying black theme [accent ${settingsManager.accent}]") logD("Applying black theme [accent ${settings.accent}]")
setTheme(settingsManager.accent.blackTheme) setTheme(settings.accent.blackTheme)
} else { } else {
logD("Applying normal theme [accent ${settingsManager.accent}]") logD("Applying normal theme [accent ${settings.accent}]")
setTheme(settingsManager.accent.theme) 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.MainNavigationAction
import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.ui.ViewBindingFragment
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.launch import org.oxycblt.auxio.util.launch
/** /**
@ -40,7 +41,7 @@ import org.oxycblt.auxio.util.launch
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class MainFragment : ViewBindingFragment<FragmentMainBinding>() { class MainFragment : ViewBindingFragment<FragmentMainBinding>() {
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by androidActivityViewModels()
private val navModel: NavigationViewModel by activityViewModels() private val navModel: NavigationViewModel by activityViewModels()
private var callback: DynamicBackPressedCallback? = null 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.MimeType
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song 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.Header
import org.oxycblt.auxio.ui.Item import org.oxycblt.auxio.ui.Item
import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.Sort
@ -62,7 +62,7 @@ class DetailViewModel(application: Application) :
) )
private val musicStore = MusicStore.getInstance() private val musicStore = MusicStore.getInstance()
private val settingsManager = SettingsManager.getInstance() private val settings = Settings(application)
private val _currentSong = MutableStateFlow<DetailSong?>(null) private val _currentSong = MutableStateFlow<DetailSong?>(null)
val currentSong: StateFlow<DetailSong?> val currentSong: StateFlow<DetailSong?>
@ -77,9 +77,9 @@ class DetailViewModel(application: Application) :
get() = _albumData get() = _albumData
var albumSort: Sort var albumSort: Sort
get() = settingsManager.detailAlbumSort get() = settings.detailAlbumSort
set(value) { set(value) {
settingsManager.detailAlbumSort = value settings.detailAlbumSort = value
currentAlbum.value?.let(::refreshAlbumData) currentAlbum.value?.let(::refreshAlbumData)
} }
@ -91,9 +91,9 @@ class DetailViewModel(application: Application) :
val artistData: StateFlow<List<Item>> = _artistData val artistData: StateFlow<List<Item>> = _artistData
var artistSort: Sort var artistSort: Sort
get() = settingsManager.detailArtistSort get() = settings.detailArtistSort
set(value) { set(value) {
settingsManager.detailArtistSort = value settings.detailArtistSort = value
currentArtist.value?.let(::refreshArtistData) currentArtist.value?.let(::refreshArtistData)
} }
@ -105,9 +105,9 @@ class DetailViewModel(application: Application) :
val genreData: StateFlow<List<Item>> = _genreData val genreData: StateFlow<List<Item>> = _genreData
var genreSort: Sort var genreSort: Sort
get() = settingsManager.detailGenreSort get() = settings.detailGenreSort
set(value) { set(value) {
settingsManager.detailGenreSort = value settings.detailGenreSort = value
currentGenre.value?.let(::refreshGenreData) 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.MainNavigationAction
import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.ui.ViewBindingFragment
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.getColorStateListSafe import org.oxycblt.auxio.util.getColorStateListSafe
import org.oxycblt.auxio.util.getSystemBarInsetsCompat import org.oxycblt.auxio.util.getSystemBarInsetsCompat
import org.oxycblt.auxio.util.launch import org.oxycblt.auxio.util.launch
@ -71,9 +72,9 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuItemClickListener { 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 navModel: NavigationViewModel by activityViewModels()
private val homeModel: HomeViewModel by activityViewModels() private val homeModel: HomeViewModel by androidActivityViewModels()
private val indexerModel: IndexerViewModel by activityViewModels() private val indexerModel: IndexerViewModel by activityViewModels()
private var storagePermissionLauncher: ActivityResultLauncher<String>? = null private var storagePermissionLauncher: ActivityResultLauncher<String>? = null

View file

@ -17,7 +17,8 @@
package org.oxycblt.auxio.home 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.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import org.oxycblt.auxio.home.tabs.Tab 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.Genre
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.music.Song 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.DisplayMode
import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.Sort
import org.oxycblt.auxio.util.application
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
/** /**
* The ViewModel for managing [HomeFragment]'s data, sorting modes, and tab state. * The ViewModel for managing [HomeFragment]'s data, sorting modes, and tab state.
* @author OxygenCobalt * @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 musicStore = MusicStore.getInstance()
private val settingsManager = SettingsManager.getInstance() private val settings = Settings(application, this)
private val _songs = MutableStateFlow(listOf<Song>()) private val _songs = MutableStateFlow(listOf<Song>())
val songs: StateFlow<List<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 */ /** Internal getter for getting the visible library tabs */
private val visibleTabs: List<DisplayMode> 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]) private val _currentTab = MutableStateFlow(tabs[0])
val currentTab: StateFlow<DisplayMode> = _currentTab val currentTab: StateFlow<DisplayMode> = _currentTab
@ -77,7 +80,6 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.Callback
init { init {
musicStore.addCallback(this) musicStore.addCallback(this)
settingsManager.addCallback(this)
} }
/** Update the current tab based off of the new ViewPager position. */ /** 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]. */ /** Get the specific sort for the given [DisplayMode]. */
fun getSortForDisplay(displayMode: DisplayMode): Sort { fun getSortForDisplay(displayMode: DisplayMode): Sort {
return when (displayMode) { return when (displayMode) {
DisplayMode.SHOW_SONGS -> settingsManager.libSongSort DisplayMode.SHOW_SONGS -> settings.libSongSort
DisplayMode.SHOW_ALBUMS -> settingsManager.libAlbumSort DisplayMode.SHOW_ALBUMS -> settings.libAlbumSort
DisplayMode.SHOW_ARTISTS -> settingsManager.libArtistSort DisplayMode.SHOW_ARTISTS -> settings.libArtistSort
DisplayMode.SHOW_GENRES -> settingsManager.libGenreSort DisplayMode.SHOW_GENRES -> settings.libGenreSort
} }
} }
@ -105,19 +107,19 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.Callback
logD("Updating ${_currentTab.value} sort to $sort") logD("Updating ${_currentTab.value} sort to $sort")
when (_currentTab.value) { when (_currentTab.value) {
DisplayMode.SHOW_SONGS -> { DisplayMode.SHOW_SONGS -> {
settingsManager.libSongSort = sort settings.libSongSort = sort
_songs.value = sort.songs(_songs.value) _songs.value = sort.songs(_songs.value)
} }
DisplayMode.SHOW_ALBUMS -> { DisplayMode.SHOW_ALBUMS -> {
settingsManager.libAlbumSort = sort settings.libAlbumSort = sort
_albums.value = sort.albums(_albums.value) _albums.value = sort.albums(_albums.value)
} }
DisplayMode.SHOW_ARTISTS -> { DisplayMode.SHOW_ARTISTS -> {
settingsManager.libArtistSort = sort settings.libArtistSort = sort
_artists.value = sort.artists(_artists.value) _artists.value = sort.artists(_artists.value)
} }
DisplayMode.SHOW_GENRES -> { DisplayMode.SHOW_GENRES -> {
settingsManager.libGenreSort = sort settings.libGenreSort = sort
_genres.value = sort.genres(_genres.value) _genres.value = sort.genres(_genres.value)
} }
} }
@ -136,10 +138,10 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.Callback
override fun onLibraryChanged(library: MusicStore.Library?) { override fun onLibraryChanged(library: MusicStore.Library?) {
if (library != null) { if (library != null) {
_songs.value = settingsManager.libSongSort.songs(library.songs) _songs.value = settings.libSongSort.songs(library.songs)
_albums.value = settingsManager.libAlbumSort.albums(library.albums) _albums.value = settings.libAlbumSort.albums(library.albums)
_artists.value = settingsManager.libArtistSort.artists(library.artists) _artists.value = settings.libArtistSort.artists(library.artists)
_genres.value = settingsManager.libGenreSort.genres(library.genres) _genres.value = settings.libGenreSort.genres(library.genres)
} }
} }
@ -151,6 +153,6 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.Callback
override fun onCleared() { override fun onCleared() {
super.onCleared() super.onCleared()
musicStore.removeCallback(this) 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.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.home.HomeViewModel import org.oxycblt.auxio.home.HomeViewModel
import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView
import org.oxycblt.auxio.ui.Item import org.oxycblt.auxio.ui.Item
import org.oxycblt.auxio.ui.MenuFragment import org.oxycblt.auxio.ui.MenuFragment
import org.oxycblt.auxio.ui.MenuItemListener import org.oxycblt.auxio.ui.MenuItemListener
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.applySpans import org.oxycblt.auxio.util.applySpans
/** /**
@ -38,7 +38,7 @@ abstract class HomeListFragment<T : Item> :
MenuItemListener, MenuItemListener,
FastScrollRecyclerView.PopupProvider, FastScrollRecyclerView.PopupProvider,
FastScrollRecyclerView.OnFastScrollListener { FastScrollRecyclerView.OnFastScrollListener {
protected val homeModel: HomeViewModel by activityViewModels() protected val homeModel: HomeViewModel by androidActivityViewModels()
override fun onCreateBinding(inflater: LayoutInflater) = override fun onCreateBinding(inflater: LayoutInflater) =
FragmentHomeListBinding.inflate(inflater) FragmentHomeListBinding.inflate(inflater)

View file

@ -22,6 +22,8 @@ import android.view.View
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.FragmentHomeListBinding import org.oxycblt.auxio.databinding.FragmentHomeListBinding
import org.oxycblt.auxio.music.Song 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.DisplayMode
import org.oxycblt.auxio.ui.Item import org.oxycblt.auxio.ui.Item
import org.oxycblt.auxio.ui.MenuItemListener import org.oxycblt.auxio.ui.MenuItemListener
@ -39,6 +41,7 @@ import org.oxycblt.auxio.util.logEOrThrow
*/ */
class SongListFragment : HomeListFragment<Song>() { class SongListFragment : HomeListFragment<Song>() {
private val homeAdapter = SongsAdapter(this) private val homeAdapter = SongsAdapter(this)
private val settings: Settings by settings()
override fun onBindingCreated(binding: FragmentHomeListBinding, savedInstanceState: Bundle?) { override fun onBindingCreated(binding: FragmentHomeListBinding, savedInstanceState: Bundle?) {
super.onBindingCreated(binding, savedInstanceState) super.onBindingCreated(binding, savedInstanceState)
@ -80,7 +83,7 @@ class SongListFragment : HomeListFragment<Song>() {
override fun onItemClick(item: Item) { override fun onItemClick(item: Item) {
check(item is Song) check(item is Song)
playbackModel.play(item) playbackModel.play(item, settings.songPlaybackMode)
} }
override fun onOpenMenu(item: Item, anchor: View) { 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.BuildConfig
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogTabsBinding 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.DisplayMode
import org.oxycblt.auxio.ui.ViewBindingDialogFragment import org.oxycblt.auxio.ui.ViewBindingDialogFragment
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
@ -36,8 +37,8 @@ import org.oxycblt.auxio.util.requireAttached
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAdapter.Listener { class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAdapter.Listener {
private val settingsManager = SettingsManager.getInstance()
private val tabAdapter = TabAdapter(this) private val tabAdapter = TabAdapter(this)
private val settings: Settings by settings()
private var touchHelper: ItemTouchHelper? = null private var touchHelper: ItemTouchHelper? = null
private var callback: TabDragCallback? = null private var callback: TabDragCallback? = null
@ -48,7 +49,7 @@ class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAd
.setTitle(R.string.set_lib_tabs) .setTitle(R.string.set_lib_tabs)
.setPositiveButton(R.string.lbl_ok) { _, _ -> .setPositiveButton(R.string.lbl_ok) { _, _ ->
logD("Committing tab changes") logD("Committing tab changes")
settingsManager.libTabs = tabAdapter.data.tabs settings.libTabs = tabAdapter.data.tabs
} }
.setNegativeButton(R.string.lbl_cancel, null) .setNegativeButton(R.string.lbl_cancel, null)
} }
@ -59,7 +60,7 @@ class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAd
logD("Found saved tab state") logD("Found saved tab state")
tabAdapter.data.submitTabs(savedTabs) tabAdapter.data.submitTabs(savedTabs)
} else { } else {
tabAdapter.data.submitTabs(settingsManager.libTabs) tabAdapter.data.submitTabs(settings.libTabs)
} }
binding.tabRecycler.apply { binding.tabRecycler.apply {

View file

@ -45,7 +45,7 @@ import kotlinx.coroutines.withContext
import okio.buffer import okio.buffer
import okio.source import okio.source
import org.oxycblt.auxio.music.Album 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.logD
import org.oxycblt.auxio.util.logW import org.oxycblt.auxio.util.logW
@ -56,19 +56,19 @@ import org.oxycblt.auxio.util.logW
* TODO: Artist images * TODO: Artist images
*/ */
abstract class BaseFetcher : Fetcher { abstract class BaseFetcher : Fetcher {
private val settingsManager = SettingsManager.getInstance()
/** /**
* Fetch the artwork of an [album]. This call respects user configuration and has proper * Fetch the artwork of an [album]. This call respects user configuration and has proper
* redundancy in the case that metadata fails to load. * redundancy in the case that metadata fails to load.
*/ */
protected suspend fun fetchArt(context: Context, album: Album): InputStream? { protected suspend fun fetchArt(context: Context, album: Album): InputStream? {
if (!settingsManager.showCovers) { val settings = Settings(context)
if (!settings.showCovers) {
return null return null
} }
return try { return try {
if (settingsManager.useQualityCovers) { if (settings.useQualityCovers) {
fetchQualityCovers(context, album) fetchQualityCovers(context, album)
} else { } else {
fetchMediaStoreCovers(context, album) 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.Genre
import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Music
import org.oxycblt.auxio.music.Song 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.getColorStateListSafe
import org.oxycblt.auxio.util.getDrawableSafe import org.oxycblt.auxio.util.getDrawableSafe
@ -51,11 +51,13 @@ class StyledImageView
@JvmOverloads @JvmOverloads
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) : constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
AppCompatImageView(context, attrs, defStyleAttr) { AppCompatImageView(context, attrs, defStyleAttr) {
private val settings = Settings(context)
var cornerRadius = 0f var cornerRadius = 0f
set(value) { set(value) {
field = value field = value
(background as? MaterialShapeDrawable)?.let { bg -> (background as? MaterialShapeDrawable)?.let { bg ->
if (!isInEditMode && SettingsManager.getInstance().roundCovers) { if (settings.roundCovers) {
bg.setCornerSize(value) bg.setCornerSize(value)
} else { } else {
bg.setCornerSize(0f) bg.setCornerSize(0f)

View file

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

View file

@ -25,14 +25,15 @@ import android.view.LayoutInflater
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogMusicDirsBinding import org.oxycblt.auxio.databinding.DialogMusicDirsBinding
import org.oxycblt.auxio.music.Directory import org.oxycblt.auxio.music.Directory
import org.oxycblt.auxio.playback.PlaybackViewModel 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.ui.ViewBindingDialogFragment
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.getSystemServiceSafe import org.oxycblt.auxio.util.getSystemServiceSafe
import org.oxycblt.auxio.util.hardRestart import org.oxycblt.auxio.util.hardRestart
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
@ -44,11 +45,10 @@ import org.oxycblt.auxio.util.showToast
*/ */
class MusicDirsDialog : class MusicDirsDialog :
ViewBindingDialogFragment<DialogMusicDirsBinding>(), MusicDirAdapter.Listener { 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 dirAdapter = MusicDirAdapter(this)
private val settings: Settings by settings()
private var storageManager: StorageManager? = null private var storageManager: StorageManager? = null
override fun onCreateBinding(inflater: LayoutInflater) = override fun onCreateBinding(inflater: LayoutInflater) =
@ -80,7 +80,7 @@ class MusicDirsDialog :
} }
dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.setOnClickListener { dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.setOnClickListener {
val dirs = settingsManager.getMusicDirs(requireContext(), requireStorageManager()) val dirs = settings.getMusicDirs(requireStorageManager())
if (dirs.dirs != dirAdapter.data.currentList || if (dirs.dirs != dirAdapter.data.currentList ||
dirs.shouldInclude != isInclude(requireBinding())) { dirs.shouldInclude != isInclude(requireBinding())) {
@ -99,7 +99,7 @@ class MusicDirsDialog :
} }
val storageManager = requireStorageManager() val storageManager = requireStorageManager()
var dirs = settingsManager.getMusicDirs(requireContext(), storageManager) var dirs = settings.getMusicDirs(storageManager)
if (savedInstanceState != null) { if (savedInstanceState != null) {
val pendingDirs = savedInstanceState.getStringArrayList(KEY_PENDING_DIRS) val pendingDirs = savedInstanceState.getStringArrayList(KEY_PENDING_DIRS)
@ -187,8 +187,7 @@ class MusicDirsDialog :
binding.folderModeGroup.checkedButtonId == R.id.dirs_mode_include binding.folderModeGroup.checkedButtonId == R.id.dirs_mode_include
private fun saveAndRestart() { private fun saveAndRestart() {
settingsManager.setMusicDirs( settings.setMusicDirs(MusicDirs(dirAdapter.data.currentList, isInclude(requireBinding())))
MusicDirs(dirAdapter.data.currentList, isInclude(requireBinding())))
playbackModel.savePlaybackState(requireContext()) { requireContext().hardRestart() } 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.MainNavigationAction
import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.ui.ViewBindingFragment
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.getColorStateListSafe import org.oxycblt.auxio.util.getColorStateListSafe
import org.oxycblt.auxio.util.getSystemBarInsetsCompat import org.oxycblt.auxio.util.getSystemBarInsetsCompat
import org.oxycblt.auxio.util.getSystemGestureInsetsCompat import org.oxycblt.auxio.util.getSystemGestureInsetsCompat
@ -40,7 +41,7 @@ import org.oxycblt.auxio.util.textSafe
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class PlaybackBarFragment : ViewBindingFragment<FragmentPlaybackBarBinding>() { class PlaybackBarFragment : ViewBindingFragment<FragmentPlaybackBarBinding>() {
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by androidActivityViewModels()
private val navModel: NavigationViewModel by activityViewModels() private val navModel: NavigationViewModel by activityViewModels()
override fun onCreateBinding(inflater: LayoutInflater) = 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.MainNavigationAction
import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.NavigationViewModel
import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.ui.ViewBindingFragment
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.getDrawableSafe import org.oxycblt.auxio.util.getDrawableSafe
import org.oxycblt.auxio.util.getSystemBarInsetsCompat import org.oxycblt.auxio.util.getSystemBarInsetsCompat
import org.oxycblt.auxio.util.getSystemGestureInsetsCompat import org.oxycblt.auxio.util.getSystemGestureInsetsCompat
@ -53,7 +54,7 @@ class PlaybackPanelFragment :
ViewBindingFragment<FragmentPlaybackPanelBinding>(), ViewBindingFragment<FragmentPlaybackPanelBinding>(),
StyledSeekBar.Callback, StyledSeekBar.Callback,
Toolbar.OnMenuItemClickListener { Toolbar.OnMenuItemClickListener {
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by androidActivityViewModels()
private val navModel: NavigationViewModel by activityViewModels() private val navModel: NavigationViewModel by activityViewModels()
private var queueItem: MenuItem? = null private var queueItem: MenuItem? = null

View file

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

View file

@ -20,7 +20,6 @@ package org.oxycblt.auxio.playback.queue
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView 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.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.ui.ViewBindingFragment
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.launch import org.oxycblt.auxio.util.launch
import org.oxycblt.auxio.util.requireAttached import org.oxycblt.auxio.util.requireAttached
@ -36,7 +36,7 @@ import org.oxycblt.auxio.util.requireAttached
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemListener { class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemListener {
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by androidActivityViewModels()
private var queueAdapter = QueueAdapter(this) private var queueAdapter = QueueAdapter(this)
private var touchHelper: ItemTouchHelper? = null private var touchHelper: ItemTouchHelper? = null
private var callback: QueueDragCallback? = 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.BuildConfig
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogPreAmpBinding 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.ui.ViewBindingDialogFragment
import org.oxycblt.auxio.util.textSafe import org.oxycblt.auxio.util.textSafe
@ -34,7 +35,7 @@ import org.oxycblt.auxio.util.textSafe
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class PreAmpCustomizeDialog : ViewBindingDialogFragment<DialogPreAmpBinding>() { class PreAmpCustomizeDialog : ViewBindingDialogFragment<DialogPreAmpBinding>() {
private val settingsManager = SettingsManager.getInstance() private val settings: Settings by settings()
override fun onCreateBinding(inflater: LayoutInflater) = DialogPreAmpBinding.inflate(inflater) override fun onCreateBinding(inflater: LayoutInflater) = DialogPreAmpBinding.inflate(inflater)
@ -43,7 +44,7 @@ class PreAmpCustomizeDialog : ViewBindingDialogFragment<DialogPreAmpBinding>() {
.setTitle(R.string.set_pre_amp) .setTitle(R.string.set_pre_amp)
.setPositiveButton(R.string.lbl_ok) { _, _ -> .setPositiveButton(R.string.lbl_ok) { _, _ ->
val binding = requireBinding() val binding = requireBinding()
settingsManager.replayGainPreAmp = settings.replayGainPreAmp =
ReplayGainPreAmp(binding.withTagsSlider.value, binding.withoutTagsSlider.value) ReplayGainPreAmp(binding.withTagsSlider.value, binding.withoutTagsSlider.value)
} }
.setNegativeButton(R.string.lbl_cancel, null) .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 // 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 // settings. After this, the sliders save their own state, so we do not need to
// do any restore behavior. // do any restore behavior.
val preAmp = settingsManager.replayGainPreAmp val preAmp = settings.replayGainPreAmp
binding.withTagsSlider.value = preAmp.with binding.withTagsSlider.value = preAmp.with
binding.withoutTagsSlider.value = preAmp.without binding.withoutTagsSlider.value = preAmp.without
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -25,7 +25,6 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.oxycblt.auxio.BuildConfig 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.Genre
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.ui.ViewBindingFragment import org.oxycblt.auxio.ui.ViewBindingFragment
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.formatDuration import org.oxycblt.auxio.util.formatDuration
import org.oxycblt.auxio.util.getSystemBarInsetsCompat import org.oxycblt.auxio.util.getSystemBarInsetsCompat
import org.oxycblt.auxio.util.launch import org.oxycblt.auxio.util.launch
@ -49,7 +49,7 @@ import org.oxycblt.auxio.util.textSafe
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class AboutFragment : ViewBindingFragment<FragmentAboutBinding>() { class AboutFragment : ViewBindingFragment<FragmentAboutBinding>() {
private val homeModel: HomeViewModel by activityViewModels() private val homeModel: HomeViewModel by androidActivityViewModels()
override fun onCreateBinding(inflater: LayoutInflater) = FragmentAboutBinding.inflate(inflater) 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 android.util.Log
import androidx.core.content.edit import androidx.core.content.edit
import java.io.File import java.io.File
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.Directory import org.oxycblt.auxio.music.Directory
import org.oxycblt.auxio.music.directoryCompat import org.oxycblt.auxio.music.directoryCompat
import org.oxycblt.auxio.music.isInternalCompat 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.logD
import org.oxycblt.auxio.util.queryAll 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 { fun handleAccentCompat(context: Context, prefs: SharedPreferences): Accent {
if (prefs.contains(OldKeys.KEY_ACCENT2)) { val currentKey = context.getString(R.string.set_key_accent)
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()
}
}
if (prefs.contains(OldKeys.KEY_ACCENT3)) { if (prefs.contains(OldKeys.KEY_ACCENT3)) {
Log.d("Auxio.SettingsCompat", "Migrating ${OldKeys.KEY_ACCENT3}") Log.d("Auxio.SettingsCompat", "Migrating ${OldKeys.KEY_ACCENT3}")
@ -76,13 +53,13 @@ fun handleAccentCompat(prefs: SharedPreferences): Accent {
} }
prefs.edit { prefs.edit {
putInt(SettingsManager.KEY_ACCENT, accent) putInt(currentKey, accent)
remove(OldKeys.KEY_ACCENT3) remove(OldKeys.KEY_ACCENT3)
apply() 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. */ /** Cache of the old keys used in Auxio. */
private object OldKeys { private object OldKeys {
const val KEY_ACCENT2 = "KEY_ACCENT2"
const val KEY_ACCENT3 = "auxio_accent" const val KEY_ACCENT3 = "auxio_accent"
} }

View file

@ -22,7 +22,6 @@ import android.view.View
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.activityViewModels
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceCategory import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat 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.IntListPreference
import org.oxycblt.auxio.settings.ui.IntListPreferenceDialog import org.oxycblt.auxio.settings.ui.IntListPreferenceDialog
import org.oxycblt.auxio.ui.accent.AccentDialog import org.oxycblt.auxio.ui.accent.AccentDialog
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.getSystemBarInsetsCompat import org.oxycblt.auxio.util.getSystemBarInsetsCompat
import org.oxycblt.auxio.util.hardRestart import org.oxycblt.auxio.util.hardRestart
import org.oxycblt.auxio.util.isNight import org.oxycblt.auxio.util.isNight
@ -54,8 +54,8 @@ import org.oxycblt.auxio.util.showToast
*/ */
@Suppress("UNUSED") @Suppress("UNUSED")
class SettingsListFragment : PreferenceFragmentCompat() { class SettingsListFragment : PreferenceFragmentCompat() {
private val playbackModel: PlaybackViewModel by activityViewModels() private val playbackModel: PlaybackViewModel by androidActivityViewModels()
val settingsManager = SettingsManager.getInstance() private val settings: Settings by settings()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -112,7 +112,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
preference.apply { preference.apply {
when (key) { when (key) {
SettingsManager.KEY_THEME -> { getString(R.string.set_key_theme) -> {
setIcon(AppCompatDelegate.getDefaultNightMode().toThemeIcon()) setIcon(AppCompatDelegate.getDefaultNightMode().toThemeIcon())
onPreferenceChangeListener = onPreferenceChangeListener =
@ -122,16 +122,17 @@ class SettingsListFragment : PreferenceFragmentCompat() {
true true
} }
} }
SettingsManager.KEY_ACCENT -> { getString(R.string.set_key_accent) -> {
onPreferenceClickListener = onPreferenceClickListener =
Preference.OnPreferenceClickListener { Preference.OnPreferenceClickListener {
AccentDialog().show(childFragmentManager, AccentDialog.TAG) AccentDialog().show(childFragmentManager, AccentDialog.TAG)
true 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 = onPreferenceClickListener =
Preference.OnPreferenceClickListener { Preference.OnPreferenceClickListener {
if (requireContext().isNight) { if (requireContext().isNight) {
@ -141,23 +142,23 @@ class SettingsListFragment : PreferenceFragmentCompat() {
true true
} }
} }
SettingsManager.KEY_LIB_TABS -> { getString(R.string.set_key_lib_tabs) -> {
onPreferenceClickListener = onPreferenceClickListener =
Preference.OnPreferenceClickListener { Preference.OnPreferenceClickListener {
TabCustomizeDialog().show(childFragmentManager, TabCustomizeDialog.TAG) TabCustomizeDialog().show(childFragmentManager, TabCustomizeDialog.TAG)
true true
} }
} }
SettingsManager.KEY_SHOW_COVERS, getString(R.string.set_key_show_covers),
SettingsManager.KEY_QUALITY_COVERS -> { getString(R.string.set_key_quality_covers) -> {
onPreferenceChangeListener = onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _, _ -> Preference.OnPreferenceChangeListener { _, _ ->
Coil.imageLoader(requireContext()).apply { this.memoryCache?.clear() } Coil.imageLoader(requireContext()).apply { this.memoryCache?.clear() }
true true
} }
} }
SettingsManager.KEY_REPLAY_GAIN -> { getString(R.string.set_key_replay_gain) -> {
notifyDependencyChange(settingsManager.replayGainMode == ReplayGainMode.OFF) notifyDependencyChange(settings.replayGainMode == ReplayGainMode.OFF)
onPreferenceChangeListener = onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _, value -> Preference.OnPreferenceChangeListener { _, value ->
notifyDependencyChange( notifyDependencyChange(
@ -165,7 +166,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
true true
} }
} }
SettingsManager.KEY_PRE_AMP -> { getString(R.string.set_key_pre_amp) -> {
onPreferenceClickListener = onPreferenceClickListener =
Preference.OnPreferenceClickListener { Preference.OnPreferenceClickListener {
PreAmpCustomizeDialog() PreAmpCustomizeDialog()
@ -173,9 +174,10 @@ class SettingsListFragment : PreferenceFragmentCompat() {
true true
} }
} }
SettingsManager.KEY_SAVE_STATE -> { getString(R.string.set_key_save_state) -> {
onPreferenceClickListener = onPreferenceClickListener =
Preference.OnPreferenceClickListener { Preference.OnPreferenceClickListener {
// FIXME: Callback can still occur on non-attached fragment
playbackModel.savePlaybackState(requireContext()) { playbackModel.savePlaybackState(requireContext()) {
requireContext().showToast(R.string.lbl_state_saved) requireContext().showToast(R.string.lbl_state_saved)
} }
@ -183,7 +185,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
true true
} }
} }
SettingsManager.KEY_REINDEX -> { getString(R.string.set_key_reindex) -> {
onPreferenceClickListener = onPreferenceClickListener =
Preference.OnPreferenceClickListener { Preference.OnPreferenceClickListener {
playbackModel.savePlaybackState(requireContext()) { playbackModel.savePlaybackState(requireContext()) {
@ -193,7 +195,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
true true
} }
} }
SettingsManager.KEY_MUSIC_DIRS -> { getString(R.string.set_key_music_dirs) -> {
onPreferenceClickListener = onPreferenceClickListener =
Preference.OnPreferenceClickListener { Preference.OnPreferenceClickListener {
MusicDirsDialog().show(childFragmentManager, MusicDirsDialog.TAG) 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.Genre
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.util.androidActivityViewModels
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logEOrThrow import org.oxycblt.auxio.util.logEOrThrow
import org.oxycblt.auxio.util.showToast import org.oxycblt.auxio.util.showToast
@ -41,7 +42,7 @@ import org.oxycblt.auxio.util.showToast
abstract class MenuFragment<T : ViewBinding> : ViewBindingFragment<T>() { abstract class MenuFragment<T : ViewBinding> : ViewBindingFragment<T>() {
private var currentMenu: PopupMenu? = null private var currentMenu: PopupMenu? = null
protected val playbackModel: PlaybackViewModel by activityViewModels() protected val playbackModel: PlaybackViewModel by androidActivityViewModels()
protected val navModel: NavigationViewModel by activityViewModels() 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.BuildConfig
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.databinding.DialogAccentBinding 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.ui.ViewBindingDialogFragment
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.unlikelyToBeNull import org.oxycblt.auxio.util.unlikelyToBeNull
@ -33,8 +34,8 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class AccentDialog : ViewBindingDialogFragment<DialogAccentBinding>(), AccentAdapter.Listener { class AccentDialog : ViewBindingDialogFragment<DialogAccentBinding>(), AccentAdapter.Listener {
private val settingsManager = SettingsManager.getInstance()
private var accentAdapter = AccentAdapter(this) private var accentAdapter = AccentAdapter(this)
private val settings: Settings by settings()
override fun onCreateBinding(inflater: LayoutInflater) = DialogAccentBinding.inflate(inflater) override fun onCreateBinding(inflater: LayoutInflater) = DialogAccentBinding.inflate(inflater)
@ -42,9 +43,9 @@ class AccentDialog : ViewBindingDialogFragment<DialogAccentBinding>(), AccentAda
builder builder
.setTitle(R.string.set_accent) .setTitle(R.string.set_accent)
.setPositiveButton(R.string.lbl_ok) { _, _ -> .setPositiveButton(R.string.lbl_ok) { _, _ ->
if (accentAdapter.selectedAccent != settingsManager.accent) { if (accentAdapter.selectedAccent != settings.accent) {
logD("Applying new accent") logD("Applying new accent")
settingsManager.accent = unlikelyToBeNull(accentAdapter.selectedAccent) settings.accent = unlikelyToBeNull(accentAdapter.selectedAccent)
requireActivity().recreate() requireActivity().recreate()
} }
@ -59,7 +60,7 @@ class AccentDialog : ViewBindingDialogFragment<DialogAccentBinding>(), AccentAda
if (savedInstanceState != null) { if (savedInstanceState != null) {
Accent.from(savedInstanceState.getInt(KEY_PENDING_ACCENT)) Accent.from(savedInstanceState.getInt(KEY_PENDING_ACCENT))
} else { } else {
settingsManager.accent settings.accent
}) })
} }

View file

@ -27,7 +27,9 @@ import android.os.Build
import android.view.View import android.view.View
import android.view.WindowInsets import android.view.WindowInsets
import android.widget.TextView import android.widget.TextView
import androidx.activity.viewModels
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.graphics.drawable.DrawableCompat import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
@ -175,6 +177,9 @@ fun Fragment.launch(
inline fun <reified T : AndroidViewModel> Fragment.androidViewModels() = inline fun <reified T : AndroidViewModel> Fragment.androidViewModels() =
viewModels<T> { ViewModelProvider.AndroidViewModelFactory(requireActivity().application) } 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() = inline fun <reified T : AndroidViewModel> Fragment.androidActivityViewModels() =
activityViewModels<T> { activityViewModels<T> {
ViewModelProvider.AndroidViewModelFactory(requireActivity().application) 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.music.Song
import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.playback.state.RepeatMode 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.getDimenSizeSafe
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
@ -41,15 +41,14 @@ import org.oxycblt.auxio.util.logD
* @author OxygenCobalt * @author OxygenCobalt
*/ */
class WidgetComponent(private val context: Context) : class WidgetComponent(private val context: Context) :
PlaybackStateManager.Callback, SettingsManager.Callback { PlaybackStateManager.Callback, Settings.Callback {
private val playbackManager = PlaybackStateManager.getInstance() private val playbackManager = PlaybackStateManager.getInstance()
private val settingsManager = SettingsManager.getInstance() private val settings = Settings(context, this)
private val widget = WidgetProvider() private val widget = WidgetProvider()
private val provider = BitmapProvider(context) private val provider = BitmapProvider(context)
init { init {
playbackManager.addCallback(this) playbackManager.addCallback(this)
settingsManager.addCallback(this)
if (playbackManager.isInitialized) { if (playbackManager.isInitialized) {
update() update()
@ -129,9 +128,9 @@ class WidgetComponent(private val context: Context) :
*/ */
fun release() { fun release() {
provider.release() provider.release()
settings.release()
widget.reset(context) widget.reset(context)
playbackManager.removeCallback(this) playbackManager.removeCallback(this)
settingsManager.removeCallback(this)
} }
// --- CALLBACKS --- // --- CALLBACKS ---

View file

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