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:
parent
ba48cdad29
commit
532a30325a
39 changed files with 742 additions and 749 deletions
|
@ -10,6 +10,10 @@
|
|||
- Fixed seam that would appear on some album covers
|
||||
- Fixed visual issue with the queue opening animation
|
||||
|
||||
#### Dev/Meta
|
||||
- Migrated preferences from shared object to utility
|
||||
- Removed 2.0.0 compat code
|
||||
|
||||
## v2.4.0
|
||||
|
||||
#### What's New
|
||||
|
|
|
@ -30,15 +30,11 @@ import org.oxycblt.auxio.image.ArtistImageFetcher
|
|||
import org.oxycblt.auxio.image.CrossfadeTransitionFactory
|
||||
import org.oxycblt.auxio.image.GenreImageFetcher
|
||||
import org.oxycblt.auxio.image.MusicKeyer
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.util.logD
|
||||
|
||||
class AuxioApp : Application(), ImageLoaderFactory {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
logD(BuildConfig.APPLICATION_ID + ".MainActivity")
|
||||
|
||||
// Adding static shortcuts in a dynamic manner is better than declaring them
|
||||
// manually, as it will properly handle the difference between debug and release
|
||||
// Auxio instances.
|
||||
|
@ -54,10 +50,6 @@ class AuxioApp : Application(), ImageLoaderFactory {
|
|||
action = INTENT_KEY_SHORTCUT_SHUFFLE
|
||||
})
|
||||
.build()))
|
||||
|
||||
// Init SettingsManager here so that there aren't any race conditions
|
||||
// [e.g PlaybackService gets SettingsManager before activity can init SettingsManager]
|
||||
SettingsManager.init(applicationContext)
|
||||
}
|
||||
|
||||
override fun newImageLoader(): ImageLoader {
|
||||
|
|
|
@ -18,11 +18,9 @@
|
|||
package org.oxycblt.auxio
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.view.WindowCompat
|
||||
|
@ -31,7 +29,9 @@ import org.oxycblt.auxio.databinding.ActivityMainBinding
|
|||
import org.oxycblt.auxio.music.IndexerService
|
||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.playback.system.PlaybackService
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.settings.settings
|
||||
import org.oxycblt.auxio.util.androidViewModels
|
||||
import org.oxycblt.auxio.util.getSystemBarInsetsCompat
|
||||
import org.oxycblt.auxio.util.isNight
|
||||
import org.oxycblt.auxio.util.logD
|
||||
|
@ -51,7 +51,7 @@ import org.oxycblt.auxio.util.logD
|
|||
* @author OxygenCobalt
|
||||
*/
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private val playbackModel: PlaybackViewModel by viewModels()
|
||||
private val playbackModel: PlaybackViewModel by androidViewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -72,17 +72,13 @@ class MainActivity : AppCompatActivity() {
|
|||
startService(Intent(this, PlaybackService::class.java))
|
||||
|
||||
// If we have a valid intent, use that. Otherwise, restore the playback state.
|
||||
val action = intentToDelayedAction(intent) ?: PlaybackViewModel.DelayedAction.RestoreState
|
||||
|
||||
playbackModel.startDelayedAction(this, action)
|
||||
playbackModel.startDelayedAction(
|
||||
intentToDelayedAction(intent) ?: PlaybackViewModel.DelayedAction.RestoreState)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
|
||||
intentToDelayedAction(intent)?.let { action ->
|
||||
playbackModel.startDelayedAction(this, action)
|
||||
}
|
||||
intentToDelayedAction(intent)?.let(playbackModel::startDelayedAction)
|
||||
}
|
||||
|
||||
private fun intentToDelayedAction(intent: Intent?): PlaybackViewModel.DelayedAction? {
|
||||
|
@ -99,45 +95,25 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a [Uri] from an intent as long as:
|
||||
* - The intent is ACTION_VIEW
|
||||
* - The intent has not already been used.
|
||||
*/
|
||||
private fun retrieveViewUri(intent: Intent?): Uri? {
|
||||
if (intent != null) {
|
||||
val action = intent.action
|
||||
val isConsumed = intent.getBooleanExtra(KEY_INTENT_USED, false)
|
||||
|
||||
if (action == Intent.ACTION_VIEW && !isConsumed) {
|
||||
// Mark the intent as used so this does not fire again
|
||||
intent.putExtra(KEY_INTENT_USED, true)
|
||||
return intent.data
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun setupTheme() {
|
||||
val settingsManager = SettingsManager.getInstance()
|
||||
val settings = Settings(this)
|
||||
|
||||
// Disable theme customization above Android 12, as it's far enough in as a version to
|
||||
// the point where most phones should have an automatic option for light/dark theming.
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||
AppCompatDelegate.setDefaultNightMode(settingsManager.theme)
|
||||
AppCompatDelegate.setDefaultNightMode(settings.theme)
|
||||
} else {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||
}
|
||||
|
||||
// The black theme has a completely separate set of styles since style attributes cannot
|
||||
// be modified at runtime.
|
||||
if (isNight && settingsManager.useBlackTheme) {
|
||||
logD("Applying black theme [accent ${settingsManager.accent}]")
|
||||
setTheme(settingsManager.accent.blackTheme)
|
||||
if (isNight && settings.useBlackTheme) {
|
||||
logD("Applying black theme [accent ${settings.accent}]")
|
||||
setTheme(settings.accent.blackTheme)
|
||||
} else {
|
||||
logD("Applying normal theme [accent ${settingsManager.accent}]")
|
||||
setTheme(settingsManager.accent.theme)
|
||||
logD("Applying normal theme [accent ${settings.accent}]")
|
||||
setTheme(settings.accent.theme)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.oxycblt.auxio.playback.PlaybackViewModel
|
|||
import org.oxycblt.auxio.ui.MainNavigationAction
|
||||
import org.oxycblt.auxio.ui.NavigationViewModel
|
||||
import org.oxycblt.auxio.ui.ViewBindingFragment
|
||||
import org.oxycblt.auxio.util.androidActivityViewModels
|
||||
import org.oxycblt.auxio.util.launch
|
||||
|
||||
/**
|
||||
|
@ -40,7 +41,7 @@ import org.oxycblt.auxio.util.launch
|
|||
* @author OxygenCobalt
|
||||
*/
|
||||
class MainFragment : ViewBindingFragment<FragmentMainBinding>() {
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
|
||||
private val navModel: NavigationViewModel by activityViewModels()
|
||||
private var callback: DynamicBackPressedCallback? = null
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ import org.oxycblt.auxio.music.Genre
|
|||
import org.oxycblt.auxio.music.MimeType
|
||||
import org.oxycblt.auxio.music.MusicStore
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.ui.Header
|
||||
import org.oxycblt.auxio.ui.Item
|
||||
import org.oxycblt.auxio.ui.Sort
|
||||
|
@ -62,7 +62,7 @@ class DetailViewModel(application: Application) :
|
|||
)
|
||||
|
||||
private val musicStore = MusicStore.getInstance()
|
||||
private val settingsManager = SettingsManager.getInstance()
|
||||
private val settings = Settings(application)
|
||||
|
||||
private val _currentSong = MutableStateFlow<DetailSong?>(null)
|
||||
val currentSong: StateFlow<DetailSong?>
|
||||
|
@ -77,9 +77,9 @@ class DetailViewModel(application: Application) :
|
|||
get() = _albumData
|
||||
|
||||
var albumSort: Sort
|
||||
get() = settingsManager.detailAlbumSort
|
||||
get() = settings.detailAlbumSort
|
||||
set(value) {
|
||||
settingsManager.detailAlbumSort = value
|
||||
settings.detailAlbumSort = value
|
||||
currentAlbum.value?.let(::refreshAlbumData)
|
||||
}
|
||||
|
||||
|
@ -91,9 +91,9 @@ class DetailViewModel(application: Application) :
|
|||
val artistData: StateFlow<List<Item>> = _artistData
|
||||
|
||||
var artistSort: Sort
|
||||
get() = settingsManager.detailArtistSort
|
||||
get() = settings.detailArtistSort
|
||||
set(value) {
|
||||
settingsManager.detailArtistSort = value
|
||||
settings.detailArtistSort = value
|
||||
currentArtist.value?.let(::refreshArtistData)
|
||||
}
|
||||
|
||||
|
@ -105,9 +105,9 @@ class DetailViewModel(application: Application) :
|
|||
val genreData: StateFlow<List<Item>> = _genreData
|
||||
|
||||
var genreSort: Sort
|
||||
get() = settingsManager.detailGenreSort
|
||||
get() = settings.detailGenreSort
|
||||
set(value) {
|
||||
settingsManager.detailGenreSort = value
|
||||
settings.detailGenreSort = value
|
||||
currentGenre.value?.let(::refreshGenreData)
|
||||
}
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ import org.oxycblt.auxio.ui.DisplayMode
|
|||
import org.oxycblt.auxio.ui.MainNavigationAction
|
||||
import org.oxycblt.auxio.ui.NavigationViewModel
|
||||
import org.oxycblt.auxio.ui.ViewBindingFragment
|
||||
import org.oxycblt.auxio.util.androidActivityViewModels
|
||||
import org.oxycblt.auxio.util.getColorStateListSafe
|
||||
import org.oxycblt.auxio.util.getSystemBarInsetsCompat
|
||||
import org.oxycblt.auxio.util.launch
|
||||
|
@ -71,9 +72,9 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
|
|||
* @author OxygenCobalt
|
||||
*/
|
||||
class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuItemClickListener {
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
|
||||
private val navModel: NavigationViewModel by activityViewModels()
|
||||
private val homeModel: HomeViewModel by activityViewModels()
|
||||
private val homeModel: HomeViewModel by androidActivityViewModels()
|
||||
private val indexerModel: IndexerViewModel by activityViewModels()
|
||||
|
||||
private var storagePermissionLauncher: ActivityResultLauncher<String>? = null
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
|
||||
package org.oxycblt.auxio.home
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.oxycblt.auxio.home.tabs.Tab
|
||||
|
@ -26,18 +27,20 @@ import org.oxycblt.auxio.music.Artist
|
|||
import org.oxycblt.auxio.music.Genre
|
||||
import org.oxycblt.auxio.music.MusicStore
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.ui.DisplayMode
|
||||
import org.oxycblt.auxio.ui.Sort
|
||||
import org.oxycblt.auxio.util.application
|
||||
import org.oxycblt.auxio.util.logD
|
||||
|
||||
/**
|
||||
* The ViewModel for managing [HomeFragment]'s data, sorting modes, and tab state.
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.Callback {
|
||||
class HomeViewModel(application: Application) :
|
||||
AndroidViewModel(application), Settings.Callback, MusicStore.Callback {
|
||||
private val musicStore = MusicStore.getInstance()
|
||||
private val settingsManager = SettingsManager.getInstance()
|
||||
private val settings = Settings(application, this)
|
||||
|
||||
private val _songs = MutableStateFlow(listOf<Song>())
|
||||
val songs: StateFlow<List<Song>>
|
||||
|
@ -60,7 +63,7 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.Callback
|
|||
|
||||
/** Internal getter for getting the visible library tabs */
|
||||
private val visibleTabs: List<DisplayMode>
|
||||
get() = settingsManager.libTabs.filterIsInstance<Tab.Visible>().map { it.mode }
|
||||
get() = settings.libTabs.filterIsInstance<Tab.Visible>().map { it.mode }
|
||||
|
||||
private val _currentTab = MutableStateFlow(tabs[0])
|
||||
val currentTab: StateFlow<DisplayMode> = _currentTab
|
||||
|
@ -77,7 +80,6 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.Callback
|
|||
|
||||
init {
|
||||
musicStore.addCallback(this)
|
||||
settingsManager.addCallback(this)
|
||||
}
|
||||
|
||||
/** Update the current tab based off of the new ViewPager position. */
|
||||
|
@ -93,10 +95,10 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.Callback
|
|||
/** Get the specific sort for the given [DisplayMode]. */
|
||||
fun getSortForDisplay(displayMode: DisplayMode): Sort {
|
||||
return when (displayMode) {
|
||||
DisplayMode.SHOW_SONGS -> settingsManager.libSongSort
|
||||
DisplayMode.SHOW_ALBUMS -> settingsManager.libAlbumSort
|
||||
DisplayMode.SHOW_ARTISTS -> settingsManager.libArtistSort
|
||||
DisplayMode.SHOW_GENRES -> settingsManager.libGenreSort
|
||||
DisplayMode.SHOW_SONGS -> settings.libSongSort
|
||||
DisplayMode.SHOW_ALBUMS -> settings.libAlbumSort
|
||||
DisplayMode.SHOW_ARTISTS -> settings.libArtistSort
|
||||
DisplayMode.SHOW_GENRES -> settings.libGenreSort
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,19 +107,19 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.Callback
|
|||
logD("Updating ${_currentTab.value} sort to $sort")
|
||||
when (_currentTab.value) {
|
||||
DisplayMode.SHOW_SONGS -> {
|
||||
settingsManager.libSongSort = sort
|
||||
settings.libSongSort = sort
|
||||
_songs.value = sort.songs(_songs.value)
|
||||
}
|
||||
DisplayMode.SHOW_ALBUMS -> {
|
||||
settingsManager.libAlbumSort = sort
|
||||
settings.libAlbumSort = sort
|
||||
_albums.value = sort.albums(_albums.value)
|
||||
}
|
||||
DisplayMode.SHOW_ARTISTS -> {
|
||||
settingsManager.libArtistSort = sort
|
||||
settings.libArtistSort = sort
|
||||
_artists.value = sort.artists(_artists.value)
|
||||
}
|
||||
DisplayMode.SHOW_GENRES -> {
|
||||
settingsManager.libGenreSort = sort
|
||||
settings.libGenreSort = sort
|
||||
_genres.value = sort.genres(_genres.value)
|
||||
}
|
||||
}
|
||||
|
@ -136,10 +138,10 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.Callback
|
|||
|
||||
override fun onLibraryChanged(library: MusicStore.Library?) {
|
||||
if (library != null) {
|
||||
_songs.value = settingsManager.libSongSort.songs(library.songs)
|
||||
_albums.value = settingsManager.libAlbumSort.albums(library.albums)
|
||||
_artists.value = settingsManager.libArtistSort.artists(library.artists)
|
||||
_genres.value = settingsManager.libGenreSort.genres(library.genres)
|
||||
_songs.value = settings.libSongSort.songs(library.songs)
|
||||
_albums.value = settings.libAlbumSort.albums(library.albums)
|
||||
_artists.value = settings.libArtistSort.artists(library.artists)
|
||||
_genres.value = settings.libGenreSort.genres(library.genres)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,6 +153,6 @@ class HomeViewModel : ViewModel(), SettingsManager.Callback, MusicStore.Callback
|
|||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
musicStore.removeCallback(this)
|
||||
settingsManager.removeCallback(this)
|
||||
settings.release()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,13 +20,13 @@ package org.oxycblt.auxio.home.list
|
|||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
|
||||
import org.oxycblt.auxio.home.HomeViewModel
|
||||
import org.oxycblt.auxio.home.fastscroll.FastScrollRecyclerView
|
||||
import org.oxycblt.auxio.ui.Item
|
||||
import org.oxycblt.auxio.ui.MenuFragment
|
||||
import org.oxycblt.auxio.ui.MenuItemListener
|
||||
import org.oxycblt.auxio.util.androidActivityViewModels
|
||||
import org.oxycblt.auxio.util.applySpans
|
||||
|
||||
/**
|
||||
|
@ -38,7 +38,7 @@ abstract class HomeListFragment<T : Item> :
|
|||
MenuItemListener,
|
||||
FastScrollRecyclerView.PopupProvider,
|
||||
FastScrollRecyclerView.OnFastScrollListener {
|
||||
protected val homeModel: HomeViewModel by activityViewModels()
|
||||
protected val homeModel: HomeViewModel by androidActivityViewModels()
|
||||
|
||||
override fun onCreateBinding(inflater: LayoutInflater) =
|
||||
FragmentHomeListBinding.inflate(inflater)
|
||||
|
|
|
@ -22,6 +22,8 @@ import android.view.View
|
|||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.databinding.FragmentHomeListBinding
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.settings.settings
|
||||
import org.oxycblt.auxio.ui.DisplayMode
|
||||
import org.oxycblt.auxio.ui.Item
|
||||
import org.oxycblt.auxio.ui.MenuItemListener
|
||||
|
@ -39,6 +41,7 @@ import org.oxycblt.auxio.util.logEOrThrow
|
|||
*/
|
||||
class SongListFragment : HomeListFragment<Song>() {
|
||||
private val homeAdapter = SongsAdapter(this)
|
||||
private val settings: Settings by settings()
|
||||
|
||||
override fun onBindingCreated(binding: FragmentHomeListBinding, savedInstanceState: Bundle?) {
|
||||
super.onBindingCreated(binding, savedInstanceState)
|
||||
|
@ -80,7 +83,7 @@ class SongListFragment : HomeListFragment<Song>() {
|
|||
|
||||
override fun onItemClick(item: Item) {
|
||||
check(item is Song)
|
||||
playbackModel.play(item)
|
||||
playbackModel.play(item, settings.songPlaybackMode)
|
||||
}
|
||||
|
||||
override fun onOpenMenu(item: Item, anchor: View) {
|
||||
|
|
|
@ -25,7 +25,8 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import org.oxycblt.auxio.BuildConfig
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.databinding.DialogTabsBinding
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.settings.settings
|
||||
import org.oxycblt.auxio.ui.DisplayMode
|
||||
import org.oxycblt.auxio.ui.ViewBindingDialogFragment
|
||||
import org.oxycblt.auxio.util.logD
|
||||
|
@ -36,8 +37,8 @@ import org.oxycblt.auxio.util.requireAttached
|
|||
* @author OxygenCobalt
|
||||
*/
|
||||
class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAdapter.Listener {
|
||||
private val settingsManager = SettingsManager.getInstance()
|
||||
private val tabAdapter = TabAdapter(this)
|
||||
private val settings: Settings by settings()
|
||||
private var touchHelper: ItemTouchHelper? = null
|
||||
private var callback: TabDragCallback? = null
|
||||
|
||||
|
@ -48,7 +49,7 @@ class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAd
|
|||
.setTitle(R.string.set_lib_tabs)
|
||||
.setPositiveButton(R.string.lbl_ok) { _, _ ->
|
||||
logD("Committing tab changes")
|
||||
settingsManager.libTabs = tabAdapter.data.tabs
|
||||
settings.libTabs = tabAdapter.data.tabs
|
||||
}
|
||||
.setNegativeButton(R.string.lbl_cancel, null)
|
||||
}
|
||||
|
@ -59,7 +60,7 @@ class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAd
|
|||
logD("Found saved tab state")
|
||||
tabAdapter.data.submitTabs(savedTabs)
|
||||
} else {
|
||||
tabAdapter.data.submitTabs(settingsManager.libTabs)
|
||||
tabAdapter.data.submitTabs(settings.libTabs)
|
||||
}
|
||||
|
||||
binding.tabRecycler.apply {
|
||||
|
|
|
@ -45,7 +45,7 @@ import kotlinx.coroutines.withContext
|
|||
import okio.buffer
|
||||
import okio.source
|
||||
import org.oxycblt.auxio.music.Album
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.logW
|
||||
|
||||
|
@ -56,19 +56,19 @@ import org.oxycblt.auxio.util.logW
|
|||
* TODO: Artist images
|
||||
*/
|
||||
abstract class BaseFetcher : Fetcher {
|
||||
private val settingsManager = SettingsManager.getInstance()
|
||||
|
||||
/**
|
||||
* Fetch the artwork of an [album]. This call respects user configuration and has proper
|
||||
* redundancy in the case that metadata fails to load.
|
||||
*/
|
||||
protected suspend fun fetchArt(context: Context, album: Album): InputStream? {
|
||||
if (!settingsManager.showCovers) {
|
||||
val settings = Settings(context)
|
||||
|
||||
if (!settings.showCovers) {
|
||||
return null
|
||||
}
|
||||
|
||||
return try {
|
||||
if (settingsManager.useQualityCovers) {
|
||||
if (settings.useQualityCovers) {
|
||||
fetchQualityCovers(context, album)
|
||||
} else {
|
||||
fetchMediaStoreCovers(context, album)
|
||||
|
|
|
@ -34,7 +34,7 @@ import org.oxycblt.auxio.music.Artist
|
|||
import org.oxycblt.auxio.music.Genre
|
||||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.util.getColorStateListSafe
|
||||
import org.oxycblt.auxio.util.getDrawableSafe
|
||||
|
||||
|
@ -51,11 +51,13 @@ class StyledImageView
|
|||
@JvmOverloads
|
||||
constructor(context: Context, attrs: AttributeSet? = null, @AttrRes defStyleAttr: Int = 0) :
|
||||
AppCompatImageView(context, attrs, defStyleAttr) {
|
||||
private val settings = Settings(context)
|
||||
|
||||
var cornerRadius = 0f
|
||||
set(value) {
|
||||
field = value
|
||||
(background as? MaterialShapeDrawable)?.let { bg ->
|
||||
if (!isInEditMode && SettingsManager.getInstance().roundCovers) {
|
||||
if (settings.roundCovers) {
|
||||
bg.setCornerSize(value)
|
||||
} else {
|
||||
bg.setCornerSize(0f)
|
||||
|
|
|
@ -34,9 +34,11 @@ class MusicStore private constructor() {
|
|||
|
||||
var library: Library? = null
|
||||
set(value) {
|
||||
field = value
|
||||
for (callback in callbacks) {
|
||||
callback.onLibraryChanged(library)
|
||||
synchronized(this) {
|
||||
field = value
|
||||
for (callback in callbacks) {
|
||||
callback.onLibraryChanged(library)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ import org.oxycblt.auxio.music.no
|
|||
import org.oxycblt.auxio.music.queryCursor
|
||||
import org.oxycblt.auxio.music.storageVolumesCompat
|
||||
import org.oxycblt.auxio.music.useQuery
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.util.contentResolverSafe
|
||||
import org.oxycblt.auxio.util.getSystemServiceSafe
|
||||
import org.oxycblt.auxio.util.logD
|
||||
|
@ -125,10 +125,10 @@ abstract class MediaStoreBackend : Indexer.Backend {
|
|||
protected val volumes = mutableListOf<StorageVolume>()
|
||||
|
||||
override fun query(context: Context): Cursor {
|
||||
val settingsManager = SettingsManager.getInstance()
|
||||
val settings = Settings(context)
|
||||
val storageManager = context.getSystemServiceSafe(StorageManager::class)
|
||||
volumes.addAll(storageManager.storageVolumesCompat)
|
||||
val dirs = settingsManager.getMusicDirs(context, storageManager)
|
||||
val dirs = settings.getMusicDirs(storageManager)
|
||||
|
||||
val args = mutableListOf<String>()
|
||||
var selector = BASE_SELECTOR
|
||||
|
@ -144,8 +144,7 @@ abstract class MediaStoreBackend : Indexer.Backend {
|
|||
" AND NOT ("
|
||||
}
|
||||
|
||||
// Since selector arguments are contained within a single parentheses, we need to
|
||||
// do a bunch of stuff.
|
||||
// Each impl adds the directories that they want selected.
|
||||
for (i in dirs.dirs.indices) {
|
||||
if (addDirToSelectorArgs(dirs.dirs[i], args)) {
|
||||
selector +=
|
||||
|
@ -510,8 +509,9 @@ open class VolumeAwareMediaStoreBackend : MediaStoreBackend() {
|
|||
val relativePath = cursor.getStringOrNull(relativePathIndex)
|
||||
|
||||
// We now have access to the volume name, so we try to leverage it instead.
|
||||
// I have no idea how well this works in practice, so we still leverage
|
||||
// the API 21 path grokking in the case that these fields are not sane.
|
||||
// I have no idea how well this works in practice, but I assume that the fields
|
||||
// probably exist.
|
||||
// TODO: Remove redundant null checks for fields you are pretty sure are not null.
|
||||
if (volumeName != null && relativePath != null) {
|
||||
// Iterating through the volume list is cheaper than creating a map,
|
||||
// interestingly enough.
|
||||
|
|
|
@ -25,14 +25,15 @@ import android.view.LayoutInflater
|
|||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import org.oxycblt.auxio.BuildConfig
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.databinding.DialogMusicDirsBinding
|
||||
import org.oxycblt.auxio.music.Directory
|
||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.settings.settings
|
||||
import org.oxycblt.auxio.ui.ViewBindingDialogFragment
|
||||
import org.oxycblt.auxio.util.androidActivityViewModels
|
||||
import org.oxycblt.auxio.util.getSystemServiceSafe
|
||||
import org.oxycblt.auxio.util.hardRestart
|
||||
import org.oxycblt.auxio.util.logD
|
||||
|
@ -44,11 +45,10 @@ import org.oxycblt.auxio.util.showToast
|
|||
*/
|
||||
class MusicDirsDialog :
|
||||
ViewBindingDialogFragment<DialogMusicDirsBinding>(), MusicDirAdapter.Listener {
|
||||
private val settingsManager = SettingsManager.getInstance()
|
||||
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
|
||||
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val dirAdapter = MusicDirAdapter(this)
|
||||
|
||||
private val settings: Settings by settings()
|
||||
private var storageManager: StorageManager? = null
|
||||
|
||||
override fun onCreateBinding(inflater: LayoutInflater) =
|
||||
|
@ -80,7 +80,7 @@ class MusicDirsDialog :
|
|||
}
|
||||
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.setOnClickListener {
|
||||
val dirs = settingsManager.getMusicDirs(requireContext(), requireStorageManager())
|
||||
val dirs = settings.getMusicDirs(requireStorageManager())
|
||||
|
||||
if (dirs.dirs != dirAdapter.data.currentList ||
|
||||
dirs.shouldInclude != isInclude(requireBinding())) {
|
||||
|
@ -99,7 +99,7 @@ class MusicDirsDialog :
|
|||
}
|
||||
|
||||
val storageManager = requireStorageManager()
|
||||
var dirs = settingsManager.getMusicDirs(requireContext(), storageManager)
|
||||
var dirs = settings.getMusicDirs(storageManager)
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
val pendingDirs = savedInstanceState.getStringArrayList(KEY_PENDING_DIRS)
|
||||
|
@ -187,8 +187,7 @@ class MusicDirsDialog :
|
|||
binding.folderModeGroup.checkedButtonId == R.id.dirs_mode_include
|
||||
|
||||
private fun saveAndRestart() {
|
||||
settingsManager.setMusicDirs(
|
||||
MusicDirs(dirAdapter.data.currentList, isInclude(requireBinding())))
|
||||
settings.setMusicDirs(MusicDirs(dirAdapter.data.currentList, isInclude(requireBinding())))
|
||||
|
||||
playbackModel.savePlaybackState(requireContext()) { requireContext().hardRestart() }
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.oxycblt.auxio.music.Song
|
|||
import org.oxycblt.auxio.ui.MainNavigationAction
|
||||
import org.oxycblt.auxio.ui.NavigationViewModel
|
||||
import org.oxycblt.auxio.ui.ViewBindingFragment
|
||||
import org.oxycblt.auxio.util.androidActivityViewModels
|
||||
import org.oxycblt.auxio.util.getColorStateListSafe
|
||||
import org.oxycblt.auxio.util.getSystemBarInsetsCompat
|
||||
import org.oxycblt.auxio.util.getSystemGestureInsetsCompat
|
||||
|
@ -40,7 +41,7 @@ import org.oxycblt.auxio.util.textSafe
|
|||
* @author OxygenCobalt
|
||||
*/
|
||||
class PlaybackBarFragment : ViewBindingFragment<FragmentPlaybackBarBinding>() {
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
|
||||
private val navModel: NavigationViewModel by activityViewModels()
|
||||
|
||||
override fun onCreateBinding(inflater: LayoutInflater) =
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.oxycblt.auxio.playback.state.RepeatMode
|
|||
import org.oxycblt.auxio.ui.MainNavigationAction
|
||||
import org.oxycblt.auxio.ui.NavigationViewModel
|
||||
import org.oxycblt.auxio.ui.ViewBindingFragment
|
||||
import org.oxycblt.auxio.util.androidActivityViewModels
|
||||
import org.oxycblt.auxio.util.getDrawableSafe
|
||||
import org.oxycblt.auxio.util.getSystemBarInsetsCompat
|
||||
import org.oxycblt.auxio.util.getSystemGestureInsetsCompat
|
||||
|
@ -53,7 +54,7 @@ class PlaybackPanelFragment :
|
|||
ViewBindingFragment<FragmentPlaybackPanelBinding>(),
|
||||
StyledSeekBar.Callback,
|
||||
Toolbar.OnMenuItemClickListener {
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
|
||||
private val navModel: NavigationViewModel by activityViewModels()
|
||||
|
||||
private var queueItem: MenuItem? = null
|
||||
|
|
|
@ -17,9 +17,10 @@
|
|||
|
||||
package org.oxycblt.auxio.playback
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
@ -31,9 +32,12 @@ import org.oxycblt.auxio.music.MusicParent
|
|||
import org.oxycblt.auxio.music.MusicStore
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.playback.state.PlaybackMode
|
||||
import org.oxycblt.auxio.playback.state.PlaybackStateDatabase
|
||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||
import org.oxycblt.auxio.playback.state.RepeatMode
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.settings.settings
|
||||
import org.oxycblt.auxio.util.application
|
||||
import org.oxycblt.auxio.util.logE
|
||||
|
||||
/**
|
||||
|
@ -44,12 +48,13 @@ import org.oxycblt.auxio.util.logE
|
|||
*
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore.Callback {
|
||||
class PlaybackViewModel(application: Application) :
|
||||
AndroidViewModel(application), PlaybackStateManager.Callback, MusicStore.Callback {
|
||||
private val musicStore = MusicStore.getInstance()
|
||||
private val settingsManager = SettingsManager.getInstance()
|
||||
private val settings = Settings(application)
|
||||
private val playbackManager = PlaybackStateManager.getInstance()
|
||||
|
||||
private var pendingDelayedAction: DelayedActionImpl? = null
|
||||
private var pendingDelayedAction: DelayedAction? = null
|
||||
|
||||
private val _song = MutableStateFlow<Song?>(null)
|
||||
/** The current song. */
|
||||
|
@ -85,12 +90,10 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore
|
|||
|
||||
// --- PLAYING FUNCTIONS ---
|
||||
|
||||
/**
|
||||
* Play a [song] with the [mode] specified. [mode] will default to the preferred song playback
|
||||
* mode of the user if not specified.
|
||||
*/
|
||||
fun play(song: Song, mode: PlaybackMode = settingsManager.songPlaybackMode) {
|
||||
playbackManager.play(song, mode)
|
||||
/** Play a [song] with the [mode] specified, */
|
||||
fun play(song: Song, mode: PlaybackMode) {
|
||||
playbackManager.play(
|
||||
song, settings.keepShuffle && playbackManager.isPlaying, mode, settings)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -103,7 +106,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore
|
|||
return
|
||||
}
|
||||
|
||||
playbackManager.play(album, shuffled)
|
||||
playbackManager.play(album, shuffled, settings)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -116,7 +119,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore
|
|||
return
|
||||
}
|
||||
|
||||
playbackManager.play(artist, shuffled)
|
||||
playbackManager.play(artist, shuffled, settings)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -129,12 +132,12 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore
|
|||
return
|
||||
}
|
||||
|
||||
playbackManager.play(genre, shuffled)
|
||||
playbackManager.play(genre, shuffled, settings)
|
||||
}
|
||||
|
||||
/** Shuffle all songs */
|
||||
fun shuffleAll() {
|
||||
playbackManager.shuffleAll()
|
||||
playbackManager.shuffleAll(settings)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -149,26 +152,29 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore
|
|||
* We would normally want to put this kind of functionality into PlaybackService, but it's
|
||||
* lifecycle makes that more or less impossible.
|
||||
*/
|
||||
fun startDelayedAction(context: Context, action: DelayedAction) {
|
||||
fun startDelayedAction(action: DelayedAction) {
|
||||
val library = musicStore.library
|
||||
val actionImpl = DelayedActionImpl(context.applicationContext, action)
|
||||
if (library != null) {
|
||||
performActionImpl(actionImpl, library)
|
||||
performActionImpl(action, library)
|
||||
} else {
|
||||
pendingDelayedAction = actionImpl
|
||||
pendingDelayedAction = action
|
||||
}
|
||||
}
|
||||
|
||||
private fun performActionImpl(action: DelayedActionImpl, library: MusicStore.Library) {
|
||||
when (action.inner) {
|
||||
private fun performActionImpl(action: DelayedAction, library: MusicStore.Library) {
|
||||
when (action) {
|
||||
is DelayedAction.RestoreState -> {
|
||||
if (!playbackManager.isInitialized) {
|
||||
viewModelScope.launch { playbackManager.restoreState(action.context) }
|
||||
viewModelScope.launch {
|
||||
playbackManager.restoreState(PlaybackStateDatabase.getInstance(application))
|
||||
}
|
||||
}
|
||||
}
|
||||
is DelayedAction.ShuffleAll -> shuffleAll()
|
||||
is DelayedAction.Open -> {
|
||||
library.findSongForUri(action.context, action.inner.uri)?.let(::play)
|
||||
library.findSongForUri(application, action.uri)?.let { song ->
|
||||
play(song, settings.songPlaybackMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -219,17 +225,17 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore
|
|||
|
||||
/** Add an [Album] to the top of the queue. */
|
||||
fun playNext(album: Album) {
|
||||
playbackManager.playNext(settingsManager.detailAlbumSort.songs(album.songs))
|
||||
playbackManager.playNext(settings.detailAlbumSort.songs(album.songs))
|
||||
}
|
||||
|
||||
/** Add an [Artist] to the top of the queue. */
|
||||
fun playNext(artist: Artist) {
|
||||
playbackManager.playNext(settingsManager.detailArtistSort.songs(artist.songs))
|
||||
playbackManager.playNext(settings.detailArtistSort.songs(artist.songs))
|
||||
}
|
||||
|
||||
/** Add a [Genre] to the top of the queue. */
|
||||
fun playNext(genre: Genre) {
|
||||
playbackManager.playNext(settingsManager.detailGenreSort.songs(genre.songs))
|
||||
playbackManager.playNext(settings.detailGenreSort.songs(genre.songs))
|
||||
}
|
||||
|
||||
/** Add a [Song] to the end of the queue. */
|
||||
|
@ -239,17 +245,17 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore
|
|||
|
||||
/** Add an [Album] to the end of the queue. */
|
||||
fun addToQueue(album: Album) {
|
||||
playbackManager.addToQueue(settingsManager.detailAlbumSort.songs(album.songs))
|
||||
playbackManager.addToQueue(settings.detailAlbumSort.songs(album.songs))
|
||||
}
|
||||
|
||||
/** Add an [Artist] to the end of the queue. */
|
||||
fun addToQueue(artist: Artist) {
|
||||
playbackManager.addToQueue(settingsManager.detailArtistSort.songs(artist.songs))
|
||||
playbackManager.addToQueue(settings.detailArtistSort.songs(artist.songs))
|
||||
}
|
||||
|
||||
/** Add a [Genre] to the end of the queue. */
|
||||
fun addToQueue(genre: Genre) {
|
||||
playbackManager.addToQueue(settingsManager.detailGenreSort.songs(genre.songs))
|
||||
playbackManager.addToQueue(settings.detailGenreSort.songs(genre.songs))
|
||||
}
|
||||
|
||||
// --- STATUS FUNCTIONS ---
|
||||
|
@ -261,7 +267,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore
|
|||
|
||||
/** Flip the shuffle status, e.g from on to off. Will keep song by default. */
|
||||
fun invertShuffled() {
|
||||
playbackManager.reshuffle(!playbackManager.isShuffled)
|
||||
playbackManager.reshuffle(!playbackManager.isShuffled, settings)
|
||||
}
|
||||
|
||||
/** Increment the repeat mode, e.g from [RepeatMode.NONE] to [RepeatMode.ALL] */
|
||||
|
@ -277,7 +283,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore
|
|||
*/
|
||||
fun savePlaybackState(context: Context, onDone: () -> Unit) {
|
||||
viewModelScope.launch {
|
||||
playbackManager.saveState(context)
|
||||
playbackManager.saveState(PlaybackStateDatabase.getInstance(context))
|
||||
onDone()
|
||||
}
|
||||
}
|
||||
|
@ -289,8 +295,6 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback, MusicStore
|
|||
data class Open(val uri: Uri) : DelayedAction()
|
||||
}
|
||||
|
||||
private data class DelayedActionImpl(val context: Context, val inner: DelayedAction)
|
||||
|
||||
// --- OVERRIDES ---
|
||||
|
||||
override fun onCleared() {
|
||||
|
|
|
@ -20,7 +20,6 @@ package org.oxycblt.auxio.playback.queue
|
|||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
@ -28,6 +27,7 @@ import org.oxycblt.auxio.databinding.FragmentQueueBinding
|
|||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.ui.ViewBindingFragment
|
||||
import org.oxycblt.auxio.util.androidActivityViewModels
|
||||
import org.oxycblt.auxio.util.launch
|
||||
import org.oxycblt.auxio.util.requireAttached
|
||||
|
||||
|
@ -36,7 +36,7 @@ import org.oxycblt.auxio.util.requireAttached
|
|||
* @author OxygenCobalt
|
||||
*/
|
||||
class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueItemListener {
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
|
||||
private var queueAdapter = QueueAdapter(this)
|
||||
private var touchHelper: ItemTouchHelper? = null
|
||||
private var callback: QueueDragCallback? = null
|
||||
|
|
|
@ -25,7 +25,8 @@ import kotlin.math.abs
|
|||
import org.oxycblt.auxio.BuildConfig
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.databinding.DialogPreAmpBinding
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.settings.settings
|
||||
import org.oxycblt.auxio.ui.ViewBindingDialogFragment
|
||||
import org.oxycblt.auxio.util.textSafe
|
||||
|
||||
|
@ -34,7 +35,7 @@ import org.oxycblt.auxio.util.textSafe
|
|||
* @author OxygenCobalt
|
||||
*/
|
||||
class PreAmpCustomizeDialog : ViewBindingDialogFragment<DialogPreAmpBinding>() {
|
||||
private val settingsManager = SettingsManager.getInstance()
|
||||
private val settings: Settings by settings()
|
||||
|
||||
override fun onCreateBinding(inflater: LayoutInflater) = DialogPreAmpBinding.inflate(inflater)
|
||||
|
||||
|
@ -43,7 +44,7 @@ class PreAmpCustomizeDialog : ViewBindingDialogFragment<DialogPreAmpBinding>() {
|
|||
.setTitle(R.string.set_pre_amp)
|
||||
.setPositiveButton(R.string.lbl_ok) { _, _ ->
|
||||
val binding = requireBinding()
|
||||
settingsManager.replayGainPreAmp =
|
||||
settings.replayGainPreAmp =
|
||||
ReplayGainPreAmp(binding.withTagsSlider.value, binding.withoutTagsSlider.value)
|
||||
}
|
||||
.setNegativeButton(R.string.lbl_cancel, null)
|
||||
|
@ -54,7 +55,7 @@ class PreAmpCustomizeDialog : ViewBindingDialogFragment<DialogPreAmpBinding>() {
|
|||
// First initialization, we need to supply the sliders with the values from
|
||||
// settings. After this, the sliders save their own state, so we do not need to
|
||||
// do any restore behavior.
|
||||
val preAmp = settingsManager.replayGainPreAmp
|
||||
val preAmp = settings.replayGainPreAmp
|
||||
binding.withTagsSlider.value = preAmp.with
|
||||
binding.withoutTagsSlider.value = preAmp.without
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.oxycblt.auxio.playback.replaygain
|
||||
|
||||
import android.content.Context
|
||||
import com.google.android.exoplayer2.C
|
||||
import com.google.android.exoplayer2.audio.AudioProcessor
|
||||
import com.google.android.exoplayer2.audio.BaseAudioProcessor
|
||||
|
@ -27,7 +28,7 @@ import java.nio.ByteBuffer
|
|||
import kotlin.math.pow
|
||||
import org.oxycblt.auxio.music.Album
|
||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.util.clamp
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
@ -42,12 +43,12 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
|
|||
*
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class ReplayGainAudioProcessor : BaseAudioProcessor() {
|
||||
class ReplayGainAudioProcessor(context: Context) : BaseAudioProcessor() {
|
||||
private data class Gain(val track: Float, val album: Float)
|
||||
private data class GainTag(val key: String, val value: Float)
|
||||
|
||||
private val playbackManager = PlaybackStateManager.getInstance()
|
||||
private val settingsManager = SettingsManager.getInstance()
|
||||
private val settings = Settings(context)
|
||||
|
||||
private var volume = 1f
|
||||
set(value) {
|
||||
|
@ -63,20 +64,20 @@ class ReplayGainAudioProcessor : BaseAudioProcessor() {
|
|||
* based off Vanilla Music's implementation, but has diverged to a significant extent.
|
||||
*/
|
||||
fun applyReplayGain(metadata: Metadata?) {
|
||||
if (settingsManager.replayGainMode == ReplayGainMode.OFF) {
|
||||
if (settings.replayGainMode == ReplayGainMode.OFF) {
|
||||
logD("ReplayGain not enabled")
|
||||
volume = 1f
|
||||
return
|
||||
}
|
||||
|
||||
val gain = metadata?.let(::parseReplayGain)
|
||||
val preAmp = settingsManager.replayGainPreAmp
|
||||
val preAmp = settings.replayGainPreAmp
|
||||
|
||||
val adjust =
|
||||
if (gain != null) {
|
||||
// ReplayGain is configurable, so determine what to do based off of the mode.
|
||||
val useAlbumGain =
|
||||
when (settingsManager.replayGainMode) {
|
||||
when (settings.replayGainMode) {
|
||||
ReplayGainMode.OFF -> throw IllegalStateException()
|
||||
|
||||
// User wants track gain to be preferred. Default to album gain only if
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
package org.oxycblt.auxio.playback.state
|
||||
|
||||
import android.content.Context
|
||||
import kotlin.math.max
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
@ -28,7 +27,7 @@ import org.oxycblt.auxio.music.Genre
|
|||
import org.oxycblt.auxio.music.MusicParent
|
||||
import org.oxycblt.auxio.music.MusicStore
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.logW
|
||||
|
||||
|
@ -51,7 +50,6 @@ import org.oxycblt.auxio.util.logW
|
|||
*/
|
||||
class PlaybackStateManager private constructor() {
|
||||
private val musicStore = MusicStore.getInstance()
|
||||
private val settingsManager = SettingsManager.getInstance()
|
||||
|
||||
/** The currently playing song. Null if there isn't one */
|
||||
val song
|
||||
|
@ -150,7 +148,7 @@ class PlaybackStateManager private constructor() {
|
|||
* Play a [song].
|
||||
* @param playbackMode The [PlaybackMode] to construct the queue off of.
|
||||
*/
|
||||
fun play(song: Song, playbackMode: PlaybackMode) {
|
||||
fun play(song: Song, shuffle: Boolean, playbackMode: PlaybackMode, settings: Settings) {
|
||||
val library = musicStore.library ?: return
|
||||
|
||||
synchronized(this) {
|
||||
|
@ -162,7 +160,7 @@ class PlaybackStateManager private constructor() {
|
|||
PlaybackMode.IN_GENRE -> song.genre
|
||||
}
|
||||
|
||||
applyNewQueue(library, settingsManager.keepShuffle && isShuffled, song)
|
||||
applyNewQueue(library, settings, shuffle, song)
|
||||
seekTo(0)
|
||||
notifyNewPlayback()
|
||||
notifyShuffledChanged()
|
||||
|
@ -175,12 +173,12 @@ class PlaybackStateManager private constructor() {
|
|||
* Play a [parent], such as an artist or album.
|
||||
* @param shuffled Whether the queue is shuffled or not
|
||||
*/
|
||||
fun play(parent: MusicParent, shuffled: Boolean) {
|
||||
fun play(parent: MusicParent, shuffled: Boolean, settings: Settings) {
|
||||
val library = musicStore.library ?: return
|
||||
|
||||
synchronized(this) {
|
||||
this.parent = parent
|
||||
applyNewQueue(library, shuffled, null)
|
||||
applyNewQueue(library, settings, shuffled, null)
|
||||
seekTo(0)
|
||||
notifyNewPlayback()
|
||||
notifyShuffledChanged()
|
||||
|
@ -190,11 +188,11 @@ class PlaybackStateManager private constructor() {
|
|||
}
|
||||
|
||||
/** Shuffle all songs. */
|
||||
fun shuffleAll() {
|
||||
fun shuffleAll(settings: Settings) {
|
||||
val library = musicStore.library ?: return
|
||||
synchronized(this) {
|
||||
parent = null
|
||||
applyNewQueue(library, true, null)
|
||||
applyNewQueue(library, settings, true, null)
|
||||
seekTo(0)
|
||||
notifyNewPlayback()
|
||||
notifyShuffledChanged()
|
||||
|
@ -291,17 +289,22 @@ class PlaybackStateManager private constructor() {
|
|||
}
|
||||
|
||||
/** Set whether this instance is [shuffled]. Updates the queue accordingly. */
|
||||
fun reshuffle(shuffled: Boolean) {
|
||||
fun reshuffle(shuffled: Boolean, settings: Settings) {
|
||||
val library = musicStore.library ?: return
|
||||
synchronized(this) {
|
||||
val song = song ?: return
|
||||
applyNewQueue(library, shuffled, song)
|
||||
applyNewQueue(library, settings, shuffled, song)
|
||||
notifyQueueChanged()
|
||||
notifyShuffledChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyNewQueue(library: MusicStore.Library, shuffled: Boolean, keep: Song?) {
|
||||
private fun applyNewQueue(
|
||||
library: MusicStore.Library,
|
||||
settings: Settings,
|
||||
shuffled: Boolean,
|
||||
keep: Song?
|
||||
) {
|
||||
val newQueue = (parent?.songs ?: library.songs).toMutableList()
|
||||
val newIndex: Int
|
||||
|
||||
|
@ -317,10 +320,10 @@ class PlaybackStateManager private constructor() {
|
|||
val sort =
|
||||
parent.let { parent ->
|
||||
when (parent) {
|
||||
null -> settingsManager.libSongSort
|
||||
is Album -> settingsManager.detailAlbumSort
|
||||
is Artist -> settingsManager.detailArtistSort
|
||||
is Genre -> settingsManager.detailGenreSort
|
||||
null -> settings.libSongSort
|
||||
is Album -> settings.detailAlbumSort
|
||||
is Artist -> settings.detailArtistSort
|
||||
is Genre -> settings.detailGenreSort
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -372,26 +375,12 @@ class PlaybackStateManager private constructor() {
|
|||
/** Rewind to the beginning of a song. */
|
||||
fun rewind() = seekTo(0)
|
||||
|
||||
/** Repeat the current song (in line with the user configuration). */
|
||||
fun repeat() {
|
||||
rewind()
|
||||
if (settingsManager.pauseOnRepeat) {
|
||||
synchronized(this) {
|
||||
isPlaying = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- PERSISTENCE FUNCTIONS ---
|
||||
|
||||
/**
|
||||
* Restore the state from the database
|
||||
* @param context [Context] required.
|
||||
*/
|
||||
suspend fun restoreState(context: Context) {
|
||||
/** Restore the state from the [database] */
|
||||
suspend fun restoreState(database: PlaybackStateDatabase) {
|
||||
val library = musicStore.library ?: return
|
||||
val start: Long
|
||||
val database = PlaybackStateDatabase.getInstance(context)
|
||||
val state: PlaybackStateDatabase.SavedState?
|
||||
|
||||
logD("Getting state from DB")
|
||||
|
@ -421,17 +410,13 @@ class PlaybackStateManager private constructor() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current state to the database.
|
||||
* @param context [Context] required
|
||||
*/
|
||||
suspend fun saveState(context: Context) {
|
||||
/** Save the current state to the [database]. */
|
||||
suspend fun saveState(database: PlaybackStateDatabase) {
|
||||
logD("Saving state to DB")
|
||||
|
||||
// Pack the entire state and save it to the database.
|
||||
withContext(Dispatchers.IO) {
|
||||
val start = System.currentTimeMillis()
|
||||
val database = PlaybackStateDatabase.getInstance(context)
|
||||
|
||||
database.write(
|
||||
synchronized(this) {
|
||||
|
|
|
@ -31,7 +31,7 @@ import org.oxycblt.auxio.music.MusicParent
|
|||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||
import org.oxycblt.auxio.playback.state.RepeatMode
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.util.logD
|
||||
|
||||
/**
|
||||
|
@ -46,9 +46,9 @@ class MediaSessionComponent(private val context: Context, private val player: Pl
|
|||
Player.Listener,
|
||||
MediaSessionCompat.Callback(),
|
||||
PlaybackStateManager.Callback,
|
||||
SettingsManager.Callback {
|
||||
Settings.Callback {
|
||||
private val playbackManager = PlaybackStateManager.getInstance()
|
||||
private val settingsManager = SettingsManager.getInstance()
|
||||
private val settings = Settings(context, this)
|
||||
private val mediaSession =
|
||||
MediaSessionCompat(context, context.packageName).apply { isActive = true }
|
||||
private val provider = BitmapProvider(context)
|
||||
|
@ -59,7 +59,6 @@ class MediaSessionComponent(private val context: Context, private val player: Pl
|
|||
init {
|
||||
player.addListener(this)
|
||||
playbackManager.addCallback(this)
|
||||
settingsManager.addCallback(this)
|
||||
mediaSession.setCallback(this)
|
||||
}
|
||||
|
||||
|
@ -69,9 +68,9 @@ class MediaSessionComponent(private val context: Context, private val player: Pl
|
|||
|
||||
fun release() {
|
||||
provider.release()
|
||||
settings.release()
|
||||
player.removeListener(this)
|
||||
playbackManager.removeCallback(this)
|
||||
settingsManager.removeCallback(this)
|
||||
|
||||
mediaSession.apply {
|
||||
isActive = false
|
||||
|
@ -218,7 +217,8 @@ class MediaSessionComponent(private val context: Context, private val player: Pl
|
|||
override fun onSetShuffleMode(shuffleMode: Int) {
|
||||
playbackManager.reshuffle(
|
||||
shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_ALL ||
|
||||
shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_GROUP)
|
||||
shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_GROUP,
|
||||
settings)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
|
|
|
@ -48,9 +48,10 @@ import org.oxycblt.auxio.BuildConfig
|
|||
import org.oxycblt.auxio.IntegerTable
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.playback.replaygain.ReplayGainAudioProcessor
|
||||
import org.oxycblt.auxio.playback.state.PlaybackStateDatabase
|
||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||
import org.oxycblt.auxio.playback.state.RepeatMode
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.widgets.WidgetComponent
|
||||
import org.oxycblt.auxio.widgets.WidgetProvider
|
||||
|
@ -74,10 +75,10 @@ class PlaybackService :
|
|||
Player.Listener,
|
||||
NotificationComponent.Callback,
|
||||
PlaybackStateManager.Controller,
|
||||
SettingsManager.Callback {
|
||||
Settings.Callback {
|
||||
// Player components
|
||||
private lateinit var player: ExoPlayer
|
||||
private val replayGainProcessor = ReplayGainAudioProcessor()
|
||||
private lateinit var replayGainProcessor: ReplayGainAudioProcessor
|
||||
|
||||
// System backend components
|
||||
private lateinit var notificationComponent: NotificationComponent
|
||||
|
@ -87,7 +88,7 @@ class PlaybackService :
|
|||
|
||||
// Managers
|
||||
private val playbackManager = PlaybackStateManager.getInstance()
|
||||
private val settingsManager = SettingsManager.getInstance()
|
||||
private lateinit var settings: Settings
|
||||
|
||||
// State
|
||||
private var isForeground = false
|
||||
|
@ -105,6 +106,8 @@ class PlaybackService :
|
|||
|
||||
// --- PLAYER SETUP ---
|
||||
|
||||
replayGainProcessor = ReplayGainAudioProcessor(this)
|
||||
|
||||
player = newPlayer()
|
||||
player.addListener(this)
|
||||
|
||||
|
@ -138,8 +141,9 @@ class PlaybackService :
|
|||
|
||||
// --- PLAYBACKSTATEMANAGER SETUP ---
|
||||
|
||||
settings = Settings(this, this)
|
||||
|
||||
playbackManager.registerController(this)
|
||||
settingsManager.addCallback(this)
|
||||
|
||||
logD("Service created")
|
||||
}
|
||||
|
@ -166,7 +170,7 @@ class PlaybackService :
|
|||
playbackManager.isPlaying = false
|
||||
|
||||
playbackManager.unregisterController(this)
|
||||
settingsManager.removeCallback(this)
|
||||
settings.release()
|
||||
unregisterReceiver(systemReceiver)
|
||||
serviceJob.cancel()
|
||||
|
||||
|
@ -196,7 +200,10 @@ class PlaybackService :
|
|||
when (state) {
|
||||
Player.STATE_ENDED -> {
|
||||
if (playbackManager.repeatMode == RepeatMode.TRACK) {
|
||||
playbackManager.repeat()
|
||||
playbackManager.rewind()
|
||||
if (settings.pauseOnRepeat) {
|
||||
playbackManager.isPlaying = false
|
||||
}
|
||||
} else {
|
||||
playbackManager.next()
|
||||
}
|
||||
|
@ -261,7 +268,7 @@ class PlaybackService :
|
|||
}
|
||||
|
||||
override fun shouldPrevRewind() =
|
||||
settingsManager.rewindWithPrev && player.currentPosition > REWIND_THRESHOLD
|
||||
settings.rewindWithPrev && player.currentPosition > REWIND_THRESHOLD
|
||||
|
||||
override fun onPlayingChanged(isPlaying: Boolean) {
|
||||
player.playWhenReady = isPlaying
|
||||
|
@ -269,13 +276,13 @@ class PlaybackService :
|
|||
}
|
||||
|
||||
override fun onRepeatChanged(repeatMode: RepeatMode) {
|
||||
if (!settingsManager.useAltNotifAction) {
|
||||
if (!settings.useAltNotifAction) {
|
||||
notificationComponent.updateRepeatMode(repeatMode)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onShuffledChanged(isShuffled: Boolean) {
|
||||
if (settingsManager.useAltNotifAction) {
|
||||
if (settings.useAltNotifAction) {
|
||||
notificationComponent.updateShuffled(isShuffled)
|
||||
}
|
||||
}
|
||||
|
@ -293,7 +300,7 @@ class PlaybackService :
|
|||
}
|
||||
|
||||
override fun onNotifSettingsChanged() {
|
||||
if (settingsManager.useAltNotifAction) {
|
||||
if (settings.useAltNotifAction) {
|
||||
onShuffledChanged(playbackManager.isShuffled)
|
||||
} else {
|
||||
onRepeatChanged(playbackManager.repeatMode)
|
||||
|
@ -353,7 +360,9 @@ class PlaybackService :
|
|||
private fun stopAndSave() {
|
||||
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
|
||||
isForeground = false
|
||||
saveScope.launch { playbackManager.saveState(this@PlaybackService) }
|
||||
saveScope.launch {
|
||||
playbackManager.saveState(PlaybackStateDatabase.getInstance(this@PlaybackService))
|
||||
}
|
||||
}
|
||||
|
||||
/** A [BroadcastReceiver] for receiving general playback events from the system. */
|
||||
|
@ -386,7 +395,8 @@ class PlaybackService :
|
|||
ACTION_PLAY_PAUSE -> playbackManager.isPlaying = !playbackManager.isPlaying
|
||||
ACTION_INC_REPEAT_MODE ->
|
||||
playbackManager.repeatMode = playbackManager.repeatMode.increment()
|
||||
ACTION_INVERT_SHUFFLE -> playbackManager.reshuffle(!playbackManager.isShuffled)
|
||||
ACTION_INVERT_SHUFFLE ->
|
||||
playbackManager.reshuffle(!playbackManager.isShuffled, settings)
|
||||
ACTION_SKIP_PREV -> playbackManager.prev()
|
||||
ACTION_SKIP_NEXT -> playbackManager.next()
|
||||
ACTION_EXIT -> {
|
||||
|
@ -408,7 +418,7 @@ class PlaybackService :
|
|||
*/
|
||||
private fun maybeResumeFromPlug() {
|
||||
if (playbackManager.song != null &&
|
||||
settingsManager.headsetAutoplay &&
|
||||
settings.headsetAutoplay &&
|
||||
initialHeadsetPlugEventHandled) {
|
||||
logD("Device connected, resuming")
|
||||
playbackManager.isPlaying = true
|
||||
|
|
|
@ -36,6 +36,8 @@ import org.oxycblt.auxio.music.Genre
|
|||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.MusicParent
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.settings.settings
|
||||
import org.oxycblt.auxio.ui.Header
|
||||
import org.oxycblt.auxio.ui.Item
|
||||
import org.oxycblt.auxio.ui.MenuFragment
|
||||
|
@ -57,6 +59,7 @@ class SearchFragment :
|
|||
private val searchModel: SearchViewModel by androidViewModels()
|
||||
|
||||
private val searchAdapter = SearchAdapter(this)
|
||||
private val settings: Settings by settings()
|
||||
private var imm: InputMethodManager? = null
|
||||
private var launchedKeyboard = false
|
||||
|
||||
|
@ -124,7 +127,7 @@ class SearchFragment :
|
|||
|
||||
override fun onItemClick(item: Item) {
|
||||
when (item) {
|
||||
is Song -> playbackModel.play(item)
|
||||
is Song -> playbackModel.play(item, settings.songPlaybackMode)
|
||||
is MusicParent -> navModel.exploreNavigateTo(item)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ import kotlinx.coroutines.launch
|
|||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.music.Music
|
||||
import org.oxycblt.auxio.music.MusicStore
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.ui.DisplayMode
|
||||
import org.oxycblt.auxio.ui.Header
|
||||
import org.oxycblt.auxio.ui.Item
|
||||
|
@ -45,23 +45,18 @@ import org.oxycblt.auxio.util.logD
|
|||
class SearchViewModel(application: Application) :
|
||||
AndroidViewModel(application), MusicStore.Callback {
|
||||
private val musicStore = MusicStore.getInstance()
|
||||
private val settingsManager = SettingsManager.getInstance()
|
||||
private val settings = Settings(application)
|
||||
|
||||
private val _searchResults = MutableStateFlow(listOf<Item>())
|
||||
/** Current search results from the last [search] call. */
|
||||
val searchResults: StateFlow<List<Item>>
|
||||
get() = _searchResults
|
||||
|
||||
private var _filterMode: DisplayMode? = null
|
||||
val filterMode: DisplayMode?
|
||||
get() = _filterMode
|
||||
get() = settings.searchFilterMode
|
||||
|
||||
private var lastQuery: String? = null
|
||||
|
||||
init {
|
||||
_filterMode = settingsManager.searchFilterMode
|
||||
}
|
||||
|
||||
/**
|
||||
* Use [query] to perform a search of the music library. Will push results to [searchResults].
|
||||
*/
|
||||
|
@ -84,28 +79,28 @@ class SearchViewModel(application: Application) :
|
|||
|
||||
// Note: a filter mode of null means to not filter at all.
|
||||
|
||||
if (_filterMode == null || _filterMode == DisplayMode.SHOW_ARTISTS) {
|
||||
if (filterMode == null || filterMode == DisplayMode.SHOW_ARTISTS) {
|
||||
library.artists.filterByOrNull(query)?.let { artists ->
|
||||
results.add(Header(-1, R.string.lbl_artists))
|
||||
results.addAll(sort.artists(artists))
|
||||
}
|
||||
}
|
||||
|
||||
if (_filterMode == null || _filterMode == DisplayMode.SHOW_ALBUMS) {
|
||||
if (filterMode == null || filterMode == DisplayMode.SHOW_ALBUMS) {
|
||||
library.albums.filterByOrNull(query)?.let { albums ->
|
||||
results.add(Header(-2, R.string.lbl_albums))
|
||||
results.addAll(sort.albums(albums))
|
||||
}
|
||||
}
|
||||
|
||||
if (_filterMode == null || _filterMode == DisplayMode.SHOW_GENRES) {
|
||||
if (filterMode == null || filterMode == DisplayMode.SHOW_GENRES) {
|
||||
library.genres.filterByOrNull(query)?.let { genres ->
|
||||
results.add(Header(-3, R.string.lbl_genres))
|
||||
results.addAll(sort.genres(genres))
|
||||
}
|
||||
}
|
||||
|
||||
if (_filterMode == null || _filterMode == DisplayMode.SHOW_SONGS) {
|
||||
if (filterMode == null || filterMode == DisplayMode.SHOW_SONGS) {
|
||||
library.songs.filterByOrNull(query)?.let { songs ->
|
||||
results.add(Header(-4, R.string.lbl_songs))
|
||||
results.addAll(sort.songs(songs))
|
||||
|
@ -120,7 +115,7 @@ class SearchViewModel(application: Application) :
|
|||
* Update the current filter mode with a menu [id]. New value will be pushed to [filterMode].
|
||||
*/
|
||||
fun updateFilterModeWithId(@IdRes id: Int) {
|
||||
_filterMode =
|
||||
val newFilterMode =
|
||||
when (id) {
|
||||
R.id.option_filter_songs -> DisplayMode.SHOW_SONGS
|
||||
R.id.option_filter_albums -> DisplayMode.SHOW_ALBUMS
|
||||
|
@ -129,9 +124,9 @@ class SearchViewModel(application: Application) :
|
|||
else -> null
|
||||
}
|
||||
|
||||
logD("Updating filter mode to $_filterMode")
|
||||
logD("Updating filter mode to $newFilterMode")
|
||||
|
||||
settingsManager.searchFilterMode = _filterMode
|
||||
settings.searchFilterMode = filterMode
|
||||
|
||||
search(lastQuery)
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import org.oxycblt.auxio.BuildConfig
|
||||
|
@ -37,6 +36,7 @@ import org.oxycblt.auxio.music.Artist
|
|||
import org.oxycblt.auxio.music.Genre
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.ui.ViewBindingFragment
|
||||
import org.oxycblt.auxio.util.androidActivityViewModels
|
||||
import org.oxycblt.auxio.util.formatDuration
|
||||
import org.oxycblt.auxio.util.getSystemBarInsetsCompat
|
||||
import org.oxycblt.auxio.util.launch
|
||||
|
@ -49,7 +49,7 @@ import org.oxycblt.auxio.util.textSafe
|
|||
* @author OxygenCobalt
|
||||
*/
|
||||
class AboutFragment : ViewBindingFragment<FragmentAboutBinding>() {
|
||||
private val homeModel: HomeViewModel by activityViewModels()
|
||||
private val homeModel: HomeViewModel by androidActivityViewModels()
|
||||
|
||||
override fun onCreateBinding(inflater: LayoutInflater) = FragmentAboutBinding.inflate(inflater)
|
||||
|
||||
|
|
369
app/src/main/java/org/oxycblt/auxio/settings/Settings.kt
Normal file
369
app/src/main/java/org/oxycblt/auxio/settings/Settings.kt
Normal 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() {}
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ import android.os.storage.StorageManager
|
|||
import android.util.Log
|
||||
import androidx.core.content.edit
|
||||
import java.io.File
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.music.Directory
|
||||
import org.oxycblt.auxio.music.directoryCompat
|
||||
import org.oxycblt.auxio.music.isInternalCompat
|
||||
|
@ -34,35 +35,11 @@ import org.oxycblt.auxio.ui.accent.Accent
|
|||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.queryAll
|
||||
|
||||
// A couple of utils for migrating from old settings values to the new formats
|
||||
// A couple of utils for migrating from old settings values to the new formats.
|
||||
// Usually, these will last for 6 months before being removed.
|
||||
|
||||
fun handleAccentCompat(prefs: SharedPreferences): Accent {
|
||||
if (prefs.contains(OldKeys.KEY_ACCENT2)) {
|
||||
Log.d("Auxio.SettingsCompat", "Migrating ${OldKeys.KEY_ACCENT2}")
|
||||
|
||||
var accent = prefs.getInt(OldKeys.KEY_ACCENT2, 5)
|
||||
|
||||
// Blue grey was merged with Light Blue in 2.0.0
|
||||
if (accent >= 17) {
|
||||
accent = 6
|
||||
}
|
||||
|
||||
// Deep Orange was merged with red in 2.0.0
|
||||
if (accent == 14) {
|
||||
accent = 0
|
||||
}
|
||||
|
||||
// Correct accents beyond deep orange (Brown/Grey)
|
||||
if (accent > 14) {
|
||||
accent--
|
||||
}
|
||||
|
||||
prefs.edit {
|
||||
putInt(OldKeys.KEY_ACCENT3, accent)
|
||||
remove(OldKeys.KEY_ACCENT2)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
fun handleAccentCompat(context: Context, prefs: SharedPreferences): Accent {
|
||||
val currentKey = context.getString(R.string.set_key_accent)
|
||||
|
||||
if (prefs.contains(OldKeys.KEY_ACCENT3)) {
|
||||
Log.d("Auxio.SettingsCompat", "Migrating ${OldKeys.KEY_ACCENT3}")
|
||||
|
@ -76,13 +53,13 @@ fun handleAccentCompat(prefs: SharedPreferences): Accent {
|
|||
}
|
||||
|
||||
prefs.edit {
|
||||
putInt(SettingsManager.KEY_ACCENT, accent)
|
||||
putInt(currentKey, accent)
|
||||
remove(OldKeys.KEY_ACCENT3)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
return Accent.from(prefs.getInt(SettingsManager.KEY_ACCENT, Accent.DEFAULT))
|
||||
return Accent.from(prefs.getInt(currentKey, Accent.DEFAULT))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -154,6 +131,5 @@ class LegacyExcludedDatabase(context: Context) :
|
|||
|
||||
/** Cache of the old keys used in Auxio. */
|
||||
private object OldKeys {
|
||||
const val KEY_ACCENT2 = "KEY_ACCENT2"
|
||||
const val KEY_ACCENT3 = "auxio_accent"
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ import android.view.View
|
|||
import androidx.annotation.DrawableRes
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceCategory
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
|
@ -38,6 +37,7 @@ import org.oxycblt.auxio.playback.replaygain.ReplayGainMode
|
|||
import org.oxycblt.auxio.settings.ui.IntListPreference
|
||||
import org.oxycblt.auxio.settings.ui.IntListPreferenceDialog
|
||||
import org.oxycblt.auxio.ui.accent.AccentDialog
|
||||
import org.oxycblt.auxio.util.androidActivityViewModels
|
||||
import org.oxycblt.auxio.util.getSystemBarInsetsCompat
|
||||
import org.oxycblt.auxio.util.hardRestart
|
||||
import org.oxycblt.auxio.util.isNight
|
||||
|
@ -54,8 +54,8 @@ import org.oxycblt.auxio.util.showToast
|
|||
*/
|
||||
@Suppress("UNUSED")
|
||||
class SettingsListFragment : PreferenceFragmentCompat() {
|
||||
private val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
val settingsManager = SettingsManager.getInstance()
|
||||
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
|
||||
private val settings: Settings by settings()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
@ -112,7 +112,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
|||
|
||||
preference.apply {
|
||||
when (key) {
|
||||
SettingsManager.KEY_THEME -> {
|
||||
getString(R.string.set_key_theme) -> {
|
||||
setIcon(AppCompatDelegate.getDefaultNightMode().toThemeIcon())
|
||||
|
||||
onPreferenceChangeListener =
|
||||
|
@ -122,16 +122,17 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
|||
true
|
||||
}
|
||||
}
|
||||
SettingsManager.KEY_ACCENT -> {
|
||||
getString(R.string.set_key_accent) -> {
|
||||
onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
AccentDialog().show(childFragmentManager, AccentDialog.TAG)
|
||||
true
|
||||
}
|
||||
|
||||
summary = context.getString(settingsManager.accent.name)
|
||||
// TODO: Replace with preference impl
|
||||
summary = context.getString(settings.accent.name)
|
||||
}
|
||||
SettingsManager.KEY_BLACK_THEME -> {
|
||||
getString(R.string.set_key_black_theme) -> {
|
||||
onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
if (requireContext().isNight) {
|
||||
|
@ -141,23 +142,23 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
|||
true
|
||||
}
|
||||
}
|
||||
SettingsManager.KEY_LIB_TABS -> {
|
||||
getString(R.string.set_key_lib_tabs) -> {
|
||||
onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
TabCustomizeDialog().show(childFragmentManager, TabCustomizeDialog.TAG)
|
||||
true
|
||||
}
|
||||
}
|
||||
SettingsManager.KEY_SHOW_COVERS,
|
||||
SettingsManager.KEY_QUALITY_COVERS -> {
|
||||
getString(R.string.set_key_show_covers),
|
||||
getString(R.string.set_key_quality_covers) -> {
|
||||
onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _, _ ->
|
||||
Coil.imageLoader(requireContext()).apply { this.memoryCache?.clear() }
|
||||
true
|
||||
}
|
||||
}
|
||||
SettingsManager.KEY_REPLAY_GAIN -> {
|
||||
notifyDependencyChange(settingsManager.replayGainMode == ReplayGainMode.OFF)
|
||||
getString(R.string.set_key_replay_gain) -> {
|
||||
notifyDependencyChange(settings.replayGainMode == ReplayGainMode.OFF)
|
||||
onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _, value ->
|
||||
notifyDependencyChange(
|
||||
|
@ -165,7 +166,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
|||
true
|
||||
}
|
||||
}
|
||||
SettingsManager.KEY_PRE_AMP -> {
|
||||
getString(R.string.set_key_pre_amp) -> {
|
||||
onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
PreAmpCustomizeDialog()
|
||||
|
@ -173,9 +174,10 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
|||
true
|
||||
}
|
||||
}
|
||||
SettingsManager.KEY_SAVE_STATE -> {
|
||||
getString(R.string.set_key_save_state) -> {
|
||||
onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
// FIXME: Callback can still occur on non-attached fragment
|
||||
playbackModel.savePlaybackState(requireContext()) {
|
||||
requireContext().showToast(R.string.lbl_state_saved)
|
||||
}
|
||||
|
@ -183,7 +185,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
|||
true
|
||||
}
|
||||
}
|
||||
SettingsManager.KEY_REINDEX -> {
|
||||
getString(R.string.set_key_reindex) -> {
|
||||
onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
playbackModel.savePlaybackState(requireContext()) {
|
||||
|
@ -193,7 +195,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
|||
true
|
||||
}
|
||||
}
|
||||
SettingsManager.KEY_MUSIC_DIRS -> {
|
||||
getString(R.string.set_key_music_dirs) -> {
|
||||
onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
MusicDirsDialog().show(childFragmentManager, MusicDirsDialog.TAG)
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,6 +29,7 @@ import org.oxycblt.auxio.music.Artist
|
|||
import org.oxycblt.auxio.music.Genre
|
||||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||
import org.oxycblt.auxio.util.androidActivityViewModels
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.logEOrThrow
|
||||
import org.oxycblt.auxio.util.showToast
|
||||
|
@ -41,7 +42,7 @@ import org.oxycblt.auxio.util.showToast
|
|||
abstract class MenuFragment<T : ViewBinding> : ViewBindingFragment<T>() {
|
||||
private var currentMenu: PopupMenu? = null
|
||||
|
||||
protected val playbackModel: PlaybackViewModel by activityViewModels()
|
||||
protected val playbackModel: PlaybackViewModel by androidActivityViewModels()
|
||||
protected val navModel: NavigationViewModel by activityViewModels()
|
||||
|
||||
/**
|
||||
|
|
|
@ -23,7 +23,8 @@ import androidx.appcompat.app.AlertDialog
|
|||
import org.oxycblt.auxio.BuildConfig
|
||||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.databinding.DialogAccentBinding
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.settings.settings
|
||||
import org.oxycblt.auxio.ui.ViewBindingDialogFragment
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
@ -33,8 +34,8 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
|
|||
* @author OxygenCobalt
|
||||
*/
|
||||
class AccentDialog : ViewBindingDialogFragment<DialogAccentBinding>(), AccentAdapter.Listener {
|
||||
private val settingsManager = SettingsManager.getInstance()
|
||||
private var accentAdapter = AccentAdapter(this)
|
||||
private val settings: Settings by settings()
|
||||
|
||||
override fun onCreateBinding(inflater: LayoutInflater) = DialogAccentBinding.inflate(inflater)
|
||||
|
||||
|
@ -42,9 +43,9 @@ class AccentDialog : ViewBindingDialogFragment<DialogAccentBinding>(), AccentAda
|
|||
builder
|
||||
.setTitle(R.string.set_accent)
|
||||
.setPositiveButton(R.string.lbl_ok) { _, _ ->
|
||||
if (accentAdapter.selectedAccent != settingsManager.accent) {
|
||||
if (accentAdapter.selectedAccent != settings.accent) {
|
||||
logD("Applying new accent")
|
||||
settingsManager.accent = unlikelyToBeNull(accentAdapter.selectedAccent)
|
||||
settings.accent = unlikelyToBeNull(accentAdapter.selectedAccent)
|
||||
requireActivity().recreate()
|
||||
}
|
||||
|
||||
|
@ -59,7 +60,7 @@ class AccentDialog : ViewBindingDialogFragment<DialogAccentBinding>(), AccentAda
|
|||
if (savedInstanceState != null) {
|
||||
Accent.from(savedInstanceState.getInt(KEY_PENDING_ACCENT))
|
||||
} else {
|
||||
settingsManager.accent
|
||||
settings.accent
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,9 @@ import android.os.Build
|
|||
import android.view.View
|
||||
import android.view.WindowInsets
|
||||
import android.widget.TextView
|
||||
import androidx.activity.viewModels
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
|
@ -175,6 +177,9 @@ fun Fragment.launch(
|
|||
inline fun <reified T : AndroidViewModel> Fragment.androidViewModels() =
|
||||
viewModels<T> { ViewModelProvider.AndroidViewModelFactory(requireActivity().application) }
|
||||
|
||||
inline fun <reified T : AndroidViewModel> AppCompatActivity.androidViewModels() =
|
||||
viewModels<T> { ViewModelProvider.AndroidViewModelFactory(application) }
|
||||
|
||||
inline fun <reified T : AndroidViewModel> Fragment.androidActivityViewModels() =
|
||||
activityViewModels<T> {
|
||||
ViewModelProvider.AndroidViewModelFactory(requireActivity().application)
|
||||
|
|
|
@ -29,7 +29,7 @@ import org.oxycblt.auxio.music.MusicParent
|
|||
import org.oxycblt.auxio.music.Song
|
||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||
import org.oxycblt.auxio.playback.state.RepeatMode
|
||||
import org.oxycblt.auxio.settings.SettingsManager
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.util.getDimenSizeSafe
|
||||
import org.oxycblt.auxio.util.logD
|
||||
|
||||
|
@ -41,15 +41,14 @@ import org.oxycblt.auxio.util.logD
|
|||
* @author OxygenCobalt
|
||||
*/
|
||||
class WidgetComponent(private val context: Context) :
|
||||
PlaybackStateManager.Callback, SettingsManager.Callback {
|
||||
PlaybackStateManager.Callback, Settings.Callback {
|
||||
private val playbackManager = PlaybackStateManager.getInstance()
|
||||
private val settingsManager = SettingsManager.getInstance()
|
||||
private val settings = Settings(context, this)
|
||||
private val widget = WidgetProvider()
|
||||
private val provider = BitmapProvider(context)
|
||||
|
||||
init {
|
||||
playbackManager.addCallback(this)
|
||||
settingsManager.addCallback(this)
|
||||
|
||||
if (playbackManager.isInitialized) {
|
||||
update()
|
||||
|
@ -129,9 +128,9 @@ class WidgetComponent(private val context: Context) :
|
|||
*/
|
||||
fun release() {
|
||||
provider.release()
|
||||
settings.release()
|
||||
widget.reset(context)
|
||||
playbackManager.removeCallback(this)
|
||||
settingsManager.removeCallback(this)
|
||||
}
|
||||
|
||||
// --- CALLBACKS ---
|
||||
|
|
|
@ -2,4 +2,5 @@
|
|||
<resources>
|
||||
<bool name="enable_theme_settings">true</bool>
|
||||
<integer name="recycler_spans">1</integer>
|
||||
<integer name="detail_app_bar_title_anim_duration">150</integer>
|
||||
</resources>
|
||||
|
|
|
@ -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>
|
94
app/src/main/res/values/settings.xml
Normal file
94
app/src/main/res/values/settings.xml
Normal 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>
|
|
@ -11,19 +11,19 @@
|
|||
app:icon="@drawable/ic_light"
|
||||
app:iconSpaceReserved="false"
|
||||
app:isPreferenceVisible="@bool/enable_theme_settings"
|
||||
app:key="KEY_THEME2"
|
||||
app:key="@string/set_key_theme"
|
||||
app:title="@string/set_theme" />
|
||||
|
||||
<Preference
|
||||
app:icon="@drawable/ic_accent"
|
||||
app:key="auxio_accent2"
|
||||
app:key="@string/set_key_accent"
|
||||
app:title="@string/set_accent" />
|
||||
|
||||
<org.oxycblt.auxio.settings.ui.M3SwitchPreference
|
||||
app:allowDividerBelow="false"
|
||||
app:defaultValue="false"
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="KEY_BLACK_THEME"
|
||||
app:key="@string/set_key_black_theme"
|
||||
app:summary="@string/set_black_mode_desc"
|
||||
app:title="@string/set_black_mode" />
|
||||
|
||||
|
@ -35,29 +35,29 @@
|
|||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="auxio_lib_tabs"
|
||||
app:key="@string/set_key_lib_tabs"
|
||||
app:summary="@string/set_lib_tabs_desc"
|
||||
app:title="@string/set_lib_tabs" />
|
||||
|
||||
<org.oxycblt.auxio.settings.ui.M3SwitchPreference
|
||||
app:defaultValue="true"
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="KEY_SHOW_COVERS"
|
||||
app:key="@string/set_key_show_covers"
|
||||
app:summary="@string/set_show_covers_desc"
|
||||
app:title="@string/set_show_covers" />
|
||||
|
||||
<org.oxycblt.auxio.settings.ui.M3SwitchPreference
|
||||
app:defaultValue="false"
|
||||
app:dependency="KEY_SHOW_COVERS"
|
||||
app:dependency="@string/set_key_show_covers"
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="KEY_QUALITY_COVERS"
|
||||
app:key="@string/set_key_quality_covers"
|
||||
app:summary="@string/set_quality_covers_desc"
|
||||
app:title="@string/set_quality_covers" />
|
||||
|
||||
<org.oxycblt.auxio.settings.ui.M3SwitchPreference
|
||||
app:defaultValue="false"
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="auxio_round_covers"
|
||||
app:key="@string/set_key_round_covers"
|
||||
app:summary="@string/set_round_covers_desc"
|
||||
app:title="@string/set_round_covers" />
|
||||
|
||||
|
@ -65,7 +65,7 @@
|
|||
app:allowDividerBelow="false"
|
||||
app:defaultValue="false"
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="KEY_ALT_NOTIF_ACTION"
|
||||
app:key="@string/set_key_alt_notif_action"
|
||||
app:summaryOff="@string/set_alt_repeat"
|
||||
app:summaryOn="@string/set_alt_shuffle"
|
||||
app:title="@string/set_alt_action" />
|
||||
|
@ -79,7 +79,7 @@
|
|||
<org.oxycblt.auxio.settings.ui.M3SwitchPreference
|
||||
app:defaultValue="false"
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="auxio_headset_autoplay"
|
||||
app:key="@string/set_key_headset_autoplay"
|
||||
app:summary="@string/set_headset_autoplay_desc"
|
||||
app:title="@string/set_headset_autoplay" />
|
||||
|
||||
|
@ -89,14 +89,14 @@
|
|||
app:entries="@array/entries_replay_gain"
|
||||
app:entryValues="@array/values_replay_gain"
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="auxio_replay_gain"
|
||||
app:key="@string/set_key_replay_gain"
|
||||
app:title="@string/set_replay_gain" />
|
||||
|
||||
<Preference
|
||||
app:allowDividerBelow="false"
|
||||
app:dependency="auxio_replay_gain"
|
||||
app:dependency="@string/set_key_replay_gain"
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="auxio_pre_amp"
|
||||
app:key="@string/set_key_pre_amp"
|
||||
app:summary="@string/set_pre_amp_desc"
|
||||
app:title="@string/set_pre_amp" />
|
||||
|
||||
|
@ -111,14 +111,14 @@
|
|||
app:entries="@array/entries_song_playback_mode"
|
||||
app:entryValues="@array/values_song_playback_mode"
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="KEY_SONG_PLAY_MODE2"
|
||||
app:key="@string/set_key_song_play_mode"
|
||||
app:title="@string/set_song_mode"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<org.oxycblt.auxio.settings.ui.M3SwitchPreference
|
||||
app:defaultValue="true"
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="KEY_KEEP_SHUFFLE"
|
||||
app:key="@string/set_key_keep_shuffle"
|
||||
app:summary="@string/set_keep_shuffle_desc"
|
||||
app:title="@string/set_keep_shuffle" />
|
||||
|
||||
|
@ -126,7 +126,7 @@
|
|||
app:allowDividerBelow="false"
|
||||
app:defaultValue="true"
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="KEY_PREV_REWIND"
|
||||
app:key="@string/set_key_rewind_prev"
|
||||
app:summary="@string/set_rewind_prev_desc"
|
||||
app:title="@string/set_rewind_prev" />
|
||||
|
||||
|
@ -134,7 +134,7 @@
|
|||
app:allowDividerBelow="false"
|
||||
app:defaultValue="false"
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="KEY_LOOP_PAUSE"
|
||||
app:key="@string/set_key_repeat_pause"
|
||||
app:summary="@string/set_repeat_pause_desc"
|
||||
app:title="@string/set_repeat_pause" />
|
||||
|
||||
|
@ -146,19 +146,19 @@
|
|||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="auxio_save_state"
|
||||
app:key="@string/set_key_save_state"
|
||||
app:summary="@string/set_save_desc"
|
||||
app:title="@string/set_save" />
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="auxio_reindex"
|
||||
app:key="@string/set_key_reindex"
|
||||
app:summary="@string/set_reindex_desc"
|
||||
app:title="@string/set_reindex" />
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="auxio_music_dirs"
|
||||
app:key="@string/set_key_music_dirs"
|
||||
app:summary="@string/set_dirs_desc"
|
||||
app:title="@string/set_dirs" />
|
||||
|
||||
|
|
Loading…
Reference in a new issue