all: rework context-dependent object use
Rework some of the taped together ways context-dependent objects were replied on in-app, such as removing redundant constructs and extremely hacky lifecycle mechanisms.
This commit is contained in:
parent
bf56a50b59
commit
493b0a9f32
30 changed files with 175 additions and 232 deletions
|
@ -50,7 +50,6 @@ audio focus was lost
|
|||
|
||||
#### What's Changed
|
||||
- Ignore MediaStore tags is now Auxio's default and unchangeable behavior. The option has been removed.
|
||||
- Removed the "Play from genre" option in the library/detail playback mode settings+
|
||||
- "Use alternate notification action" is now "Custom notification action"
|
||||
- "Show covers" and "Ignore MediaStore covers" have been unified into "Album covers"
|
||||
|
||||
|
|
|
@ -61,10 +61,8 @@ class MainFragment :
|
|||
private val selectionModel: SelectionViewModel by activityViewModels()
|
||||
private val callback = DynamicBackPressedCallback()
|
||||
private var lastInsets: WindowInsets? = null
|
||||
private var elevationNormal = 0f
|
||||
private var initialNavDestinationChange = true
|
||||
private val elevationNormal: Float by lifecycleObject { binding ->
|
||||
binding.context.getDimen(R.dimen.elevation_normal)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -77,6 +75,8 @@ class MainFragment :
|
|||
override fun onBindingCreated(binding: FragmentMainBinding, savedInstanceState: Bundle?) {
|
||||
super.onBindingCreated(binding, savedInstanceState)
|
||||
|
||||
elevationNormal = binding.context.getDimen(R.dimen.elevation_normal)
|
||||
|
||||
// --- UI SETUP ---
|
||||
val context = requireActivity()
|
||||
// Override the back pressed listener so we can map back navigation to collapsing
|
||||
|
|
|
@ -130,11 +130,11 @@ class DetailViewModel(application: Application) :
|
|||
}
|
||||
|
||||
init {
|
||||
musicStore.addCallback(this)
|
||||
musicStore.addListener(this)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
musicStore.removeCallback(this)
|
||||
musicStore.removeListener(this)
|
||||
}
|
||||
|
||||
override fun onLibraryChanged(library: MusicStore.Library?) {
|
||||
|
|
|
@ -72,17 +72,7 @@ class HomeFragment :
|
|||
private val homeModel: HomeViewModel by androidActivityViewModels()
|
||||
private val musicModel: MusicViewModel by activityViewModels()
|
||||
private val navModel: NavigationViewModel by activityViewModels()
|
||||
|
||||
// lifecycleObject builds this in the creation step, so doing this is okay.
|
||||
private val storagePermissionLauncher: ActivityResultLauncher<String> by lifecycleObject {
|
||||
registerForActivityResult(ActivityResultContracts.RequestPermission()) {
|
||||
musicModel.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
private val sortItem: MenuItem by lifecycleObject { binding ->
|
||||
binding.homeToolbar.menu.findItem(R.id.submenu_sorting)
|
||||
}
|
||||
private var storagePermissionLauncher: ActivityResultLauncher<String>? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -105,6 +95,12 @@ class HomeFragment :
|
|||
override fun onBindingCreated(binding: FragmentHomeBinding, savedInstanceState: Bundle?) {
|
||||
super.onBindingCreated(binding, savedInstanceState)
|
||||
|
||||
// Have to set up the permission launcher before the view is shown
|
||||
storagePermissionLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.RequestPermission()) {
|
||||
musicModel.refresh()
|
||||
}
|
||||
|
||||
// --- UI SETUP ---
|
||||
binding.homeAppbar.addOnOffsetChangedListener(this)
|
||||
binding.homeToolbar.setOnMenuItemClickListener(this)
|
||||
|
@ -171,6 +167,7 @@ class HomeFragment :
|
|||
|
||||
override fun onDestroyBinding(binding: FragmentHomeBinding) {
|
||||
super.onDestroyBinding(binding)
|
||||
storagePermissionLauncher = null
|
||||
binding.homeAppbar.removeOnOffsetChangedListener(this)
|
||||
binding.homeToolbar.setOnMenuItemClickListener(null)
|
||||
}
|
||||
|
@ -285,7 +282,9 @@ class HomeFragment :
|
|||
}
|
||||
}
|
||||
|
||||
val sortMenu = requireNotNull(sortItem.subMenu)
|
||||
val sortMenu =
|
||||
unlikelyToBeNull(
|
||||
requireBinding().homeToolbar.menu.findItem(R.id.submenu_sorting).subMenu)
|
||||
val toHighlight = homeModel.getSortForTab(tabMode)
|
||||
|
||||
for (option in sortMenu) {
|
||||
|
@ -374,7 +373,10 @@ class HomeFragment :
|
|||
visibility = View.VISIBLE
|
||||
text = context.getString(R.string.lbl_grant)
|
||||
setOnClickListener {
|
||||
storagePermissionLauncher.launch(Indexer.PERMISSION_READ_AUDIO)
|
||||
requireNotNull(storagePermissionLauncher) {
|
||||
"Permission launcher was not available"
|
||||
}
|
||||
.launch(Indexer.PERMISSION_READ_AUDIO)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package org.oxycblt.auxio.home
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
@ -39,9 +40,11 @@ import org.oxycblt.auxio.util.logD
|
|||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class HomeViewModel(application: Application) :
|
||||
AndroidViewModel(application), Settings.Listener, MusicStore.Listener {
|
||||
AndroidViewModel(application),
|
||||
MusicStore.Listener,
|
||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private val musicStore = MusicStore.getInstance()
|
||||
private val settings = Settings(application, this)
|
||||
private val settings = Settings(application)
|
||||
|
||||
private val _songsList = MutableStateFlow(listOf<Song>())
|
||||
/** A list of [Song]s, sorted by the preferred [Sort], to be shown in the home view. */
|
||||
|
@ -91,13 +94,14 @@ class HomeViewModel(application: Application) :
|
|||
val isFastScrolling: StateFlow<Boolean> = _isFastScrolling
|
||||
|
||||
init {
|
||||
musicStore.addCallback(this)
|
||||
musicStore.addListener(this)
|
||||
settings.addListener(this)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
musicStore.removeCallback(this)
|
||||
settings.release()
|
||||
musicStore.removeListener(this)
|
||||
settings.removeListener(this)
|
||||
}
|
||||
|
||||
override fun onLibraryChanged(library: MusicStore.Library?) {
|
||||
|
@ -119,7 +123,7 @@ class HomeViewModel(application: Application) :
|
|||
}
|
||||
}
|
||||
|
||||
override fun onSettingChanged(key: String) {
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
when (key) {
|
||||
context.getString(R.string.set_key_lib_tabs) -> {
|
||||
// Tabs changed, update the current tabs and set up a re-create event.
|
||||
|
|
|
@ -28,7 +28,6 @@ import org.oxycblt.auxio.databinding.DialogTabsBinding
|
|||
import org.oxycblt.auxio.music.MusicMode
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.ui.ViewBindingDialogFragment
|
||||
import org.oxycblt.auxio.util.context
|
||||
import org.oxycblt.auxio.util.logD
|
||||
|
||||
/**
|
||||
|
@ -36,12 +35,8 @@ import org.oxycblt.auxio.util.logD
|
|||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAdapter.Listener {
|
||||
private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) }
|
||||
|
||||
private val tabAdapter = TabAdapter(this)
|
||||
private val touchHelper: ItemTouchHelper by lifecycleObject {
|
||||
ItemTouchHelper(TabDragCallback(tabAdapter))
|
||||
}
|
||||
private var touchHelper: ItemTouchHelper? = null
|
||||
|
||||
override fun onCreateBinding(inflater: LayoutInflater) = DialogTabsBinding.inflate(inflater)
|
||||
|
||||
|
@ -50,13 +45,13 @@ class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAd
|
|||
.setTitle(R.string.set_lib_tabs)
|
||||
.setPositiveButton(R.string.lbl_ok) { _, _ ->
|
||||
logD("Committing tab changes")
|
||||
settings.libTabs = tabAdapter.tabs
|
||||
Settings(requireContext()).libTabs = tabAdapter.tabs
|
||||
}
|
||||
.setNegativeButton(R.string.lbl_cancel, null)
|
||||
}
|
||||
|
||||
override fun onBindingCreated(binding: DialogTabsBinding, savedInstanceState: Bundle?) {
|
||||
var tabs = settings.libTabs
|
||||
var tabs = Settings(requireContext()).libTabs
|
||||
// Try to restore a pending tab configuration that was saved prior.
|
||||
if (savedInstanceState != null) {
|
||||
val savedTabs = Tab.fromIntCode(savedInstanceState.getInt(KEY_TABS))
|
||||
|
@ -69,7 +64,8 @@ class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAd
|
|||
tabAdapter.submitTabs(tabs)
|
||||
binding.tabRecycler.apply {
|
||||
adapter = tabAdapter
|
||||
touchHelper.attachToRecyclerView(this)
|
||||
touchHelper =
|
||||
ItemTouchHelper(TabDragCallback(tabAdapter)).also { it.attachToRecyclerView(this) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,7 +101,7 @@ class TabCustomizeDialog : ViewBindingDialogFragment<DialogTabsBinding>(), TabAd
|
|||
}
|
||||
|
||||
override fun onPickUp(viewHolder: RecyclerView.ViewHolder) {
|
||||
touchHelper.startDrag(viewHolder)
|
||||
requireNotNull(touchHelper) { "ItemTouchHelper was not available" }.startDrag(viewHolder)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
|
|
@ -35,7 +35,7 @@ class SelectionViewModel : ViewModel(), MusicStore.Listener {
|
|||
get() = _selected
|
||||
|
||||
init {
|
||||
musicStore.addCallback(this)
|
||||
musicStore.addListener(this)
|
||||
}
|
||||
|
||||
override fun onLibraryChanged(library: MusicStore.Library?) {
|
||||
|
@ -58,7 +58,7 @@ class SelectionViewModel : ViewModel(), MusicStore.Listener {
|
|||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
musicStore.removeCallback(this)
|
||||
musicStore.removeListener(this)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -55,7 +55,7 @@ class MusicStore private constructor() {
|
|||
* @see Listener
|
||||
*/
|
||||
@Synchronized
|
||||
fun addCallback(listener: Listener) {
|
||||
fun addListener(listener: Listener) {
|
||||
listener.onLibraryChanged(library)
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ class MusicStore private constructor() {
|
|||
* @see Listener
|
||||
*/
|
||||
@Synchronized
|
||||
fun removeCallback(listener: Listener) {
|
||||
fun removeListener(listener: Listener) {
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
|
|
|
@ -39,11 +39,11 @@ class MusicViewModel : ViewModel(), Indexer.Listener {
|
|||
get() = _statistics
|
||||
|
||||
init {
|
||||
indexer.registerCallback(this)
|
||||
indexer.registerListener(this)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
indexer.unregisterCallback(this)
|
||||
indexer.unregisterListener(this)
|
||||
}
|
||||
|
||||
override fun onIndexerStateChanged(state: Indexer.State?) {
|
||||
|
|
|
@ -27,7 +27,6 @@ import org.oxycblt.auxio.R
|
|||
import org.oxycblt.auxio.databinding.DialogSeparatorsBinding
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.ui.ViewBindingDialogFragment
|
||||
import org.oxycblt.auxio.util.context
|
||||
|
||||
/**
|
||||
* A [ViewBindingDialogFragment] that allows the user to configure the separator characters used to
|
||||
|
@ -35,8 +34,6 @@ import org.oxycblt.auxio.util.context
|
|||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class SeparatorsDialog : ViewBindingDialogFragment<DialogSeparatorsBinding>() {
|
||||
private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) }
|
||||
|
||||
override fun onCreateBinding(inflater: LayoutInflater) =
|
||||
DialogSeparatorsBinding.inflate(inflater)
|
||||
|
||||
|
@ -45,7 +42,7 @@ class SeparatorsDialog : ViewBindingDialogFragment<DialogSeparatorsBinding>() {
|
|||
.setTitle(R.string.set_separators)
|
||||
.setNegativeButton(R.string.lbl_cancel, null)
|
||||
.setPositiveButton(R.string.lbl_save) { _, _ ->
|
||||
settings.musicSeparators = getCurrentSeparators()
|
||||
Settings(requireContext()).musicSeparators = getCurrentSeparators()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,7 +58,8 @@ class SeparatorsDialog : ViewBindingDialogFragment<DialogSeparatorsBinding>() {
|
|||
// More efficient to do one iteration through the separator list and initialize
|
||||
// the corresponding CheckBox for each character instead of doing an iteration
|
||||
// through the separator list for each CheckBox.
|
||||
(savedInstanceState?.getString(KEY_PENDING_SEPARATORS) ?: settings.musicSeparators)
|
||||
(savedInstanceState?.getString(KEY_PENDING_SEPARATORS)
|
||||
?: Settings(requireContext()).musicSeparators)
|
||||
?.forEach {
|
||||
when (it) {
|
||||
SEPARATOR_COMMA -> binding.separatorComma.isChecked = true
|
||||
|
|
|
@ -47,7 +47,7 @@ class PickerViewModel : ViewModel(), MusicStore.Listener {
|
|||
get() = _genreChoices
|
||||
|
||||
override fun onCleared() {
|
||||
musicStore.removeCallback(this)
|
||||
musicStore.removeListener(this)
|
||||
}
|
||||
|
||||
override fun onLibraryChanged(library: MusicStore.Library?) {
|
||||
|
|
|
@ -22,6 +22,7 @@ import android.os.Bundle
|
|||
import android.os.storage.StorageManager
|
||||
import android.provider.DocumentsContract
|
||||
import android.view.LayoutInflater
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.isVisible
|
||||
|
@ -42,10 +43,8 @@ import org.oxycblt.auxio.util.showToast
|
|||
class MusicDirsDialog :
|
||||
ViewBindingDialogFragment<DialogMusicDirsBinding>(), DirectoryAdapter.Listener {
|
||||
private val dirAdapter = DirectoryAdapter(this)
|
||||
private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) }
|
||||
private val storageManager: StorageManager by lifecycleObject { binding ->
|
||||
binding.context.getSystemServiceCompat(StorageManager::class)
|
||||
}
|
||||
private var openDocumentTreeLauncher: ActivityResultLauncher<Uri?>? = null
|
||||
private var storageManager: StorageManager? = null
|
||||
|
||||
override fun onCreateBinding(inflater: LayoutInflater) =
|
||||
DialogMusicDirsBinding.inflate(inflater)
|
||||
|
@ -57,7 +56,10 @@ class MusicDirsDialog :
|
|||
.setNeutralButton(R.string.lbl_add, null)
|
||||
.setNegativeButton(R.string.lbl_cancel, null)
|
||||
.setPositiveButton(R.string.lbl_save) { _, _ ->
|
||||
val dirs = settings.getMusicDirs(storageManager)
|
||||
val settings = Settings(requireContext())
|
||||
val dirs =
|
||||
settings.getMusicDirs(
|
||||
requireNotNull(storageManager) { "StorageManager was not available" })
|
||||
val newDirs = MusicDirectories(dirAdapter.dirs, isUiModeInclude(requireBinding()))
|
||||
if (dirs != newDirs) {
|
||||
logD("Committing changes")
|
||||
|
@ -67,7 +69,11 @@ class MusicDirsDialog :
|
|||
}
|
||||
|
||||
override fun onBindingCreated(binding: DialogMusicDirsBinding, savedInstanceState: Bundle?) {
|
||||
val launcher =
|
||||
val context = requireContext()
|
||||
val storageManager =
|
||||
context.getSystemServiceCompat(StorageManager::class).also { storageManager = it }
|
||||
|
||||
openDocumentTreeLauncher =
|
||||
registerForActivityResult(
|
||||
ActivityResultContracts.OpenDocumentTree(), ::addDocumentTreeUriToDirs)
|
||||
|
||||
|
@ -79,7 +85,10 @@ class MusicDirsDialog :
|
|||
val dialog = it as AlertDialog
|
||||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)?.setOnClickListener {
|
||||
logD("Opening launcher")
|
||||
launcher.launch(null)
|
||||
requireNotNull(openDocumentTreeLauncher) {
|
||||
"Document tree launcher was not available"
|
||||
}
|
||||
.launch(null)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,7 +97,7 @@ class MusicDirsDialog :
|
|||
itemAnimator = null
|
||||
}
|
||||
|
||||
var dirs = settings.getMusicDirs(storageManager)
|
||||
var dirs = Settings(context).getMusicDirs(storageManager)
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
val pendingDirs = savedInstanceState.getStringArrayList(KEY_PENDING_DIRS)
|
||||
|
@ -127,6 +136,8 @@ class MusicDirsDialog :
|
|||
|
||||
override fun onDestroyBinding(binding: DialogMusicDirsBinding) {
|
||||
super.onDestroyBinding(binding)
|
||||
storageManager = null
|
||||
openDocumentTreeLauncher = null
|
||||
binding.dirsRecycler.adapter = null
|
||||
}
|
||||
|
||||
|
@ -153,7 +164,9 @@ class MusicDirsDialog :
|
|||
DocumentsContract.buildDocumentUriUsingTree(
|
||||
uri, DocumentsContract.getTreeDocumentId(uri))
|
||||
val treeUri = DocumentsContract.getTreeDocumentId(docUri)
|
||||
val dir = Directory.fromDocumentTreeUri(storageManager, treeUri)
|
||||
val dir =
|
||||
Directory.fromDocumentTreeUri(
|
||||
requireNotNull(storageManager) { "StorageManager was not available" }, treeUri)
|
||||
|
||||
if (dir != null) {
|
||||
dirAdapter.add(dir)
|
||||
|
|
|
@ -111,7 +111,7 @@ class Indexer private constructor() {
|
|||
* @param listener The [Listener] to add.
|
||||
*/
|
||||
@Synchronized
|
||||
fun registerCallback(listener: Listener) {
|
||||
fun registerListener(listener: Listener) {
|
||||
if (BuildConfig.DEBUG && this.listener != null) {
|
||||
logW("Listener is already registered")
|
||||
return
|
||||
|
@ -131,7 +131,7 @@ class Indexer private constructor() {
|
|||
* @see Listener
|
||||
*/
|
||||
@Synchronized
|
||||
fun unregisterCallback(listener: Listener) {
|
||||
fun unregisterListener(listener: Listener) {
|
||||
if (BuildConfig.DEBUG && this.listener !== listener) {
|
||||
logW("Given controller did not match current controller")
|
||||
return
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.oxycblt.auxio.music.system
|
|||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.database.ContentObserver
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
|
@ -54,7 +55,8 @@ import org.oxycblt.auxio.util.logD
|
|||
*
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class IndexerService : Service(), Indexer.Controller, Settings.Listener {
|
||||
class IndexerService :
|
||||
Service(), Indexer.Controller, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private val indexer = Indexer.getInstance()
|
||||
private val musicStore = MusicStore.getInstance()
|
||||
private val playbackManager = PlaybackStateManager.getInstance()
|
||||
|
@ -81,7 +83,8 @@ class IndexerService : Service(), Indexer.Controller, Settings.Listener {
|
|||
// Initialize any listener-dependent components last as we wouldn't want a listener race
|
||||
// condition to cause us to load music before we were fully initialize.
|
||||
indexerContentObserver = SystemContentObserver()
|
||||
settings = Settings(this, this)
|
||||
settings = Settings(this)
|
||||
settings.addListener(this)
|
||||
indexer.registerController(this)
|
||||
// An indeterminate indexer and a missing library implies we are extremely early
|
||||
// in app initialization so start loading music.
|
||||
|
@ -105,7 +108,7 @@ class IndexerService : Service(), Indexer.Controller, Settings.Listener {
|
|||
// Then cancel the listener-dependent components to ensure that stray reloading
|
||||
// events will not occur.
|
||||
indexerContentObserver.release()
|
||||
settings.release()
|
||||
settings.removeListener(this)
|
||||
indexer.unregisterController(this)
|
||||
// Then cancel any remaining music loading jobs.
|
||||
serviceJob.cancel()
|
||||
|
@ -230,7 +233,7 @@ class IndexerService : Service(), Indexer.Controller, Settings.Listener {
|
|||
|
||||
// --- SETTING CALLBACKS ---
|
||||
|
||||
override fun onSettingChanged(key: String) {
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
when (key) {
|
||||
// Hook changes in music settings to a new music loading event.
|
||||
getString(R.string.set_key_exclude_non_music),
|
||||
|
|
|
@ -23,6 +23,7 @@ import android.media.audiofx.AudioEffect
|
|||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.view.updatePadding
|
||||
|
@ -53,13 +54,7 @@ class PlaybackPanelFragment :
|
|||
StyledSeekBar.Listener {
|
||||
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
|
||||
private val navModel: NavigationViewModel by activityViewModels()
|
||||
// AudioEffect expects you to use startActivityForResult with the panel intent. There is no
|
||||
// contract analogue for this intent, so the generic contract is used instead.
|
||||
private val equalizerLauncher by lifecycleObject {
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
// Nothing to do
|
||||
}
|
||||
}
|
||||
private var equalizerLauncher: ActivityResultLauncher<Intent>? = null
|
||||
|
||||
override fun onCreateBinding(inflater: LayoutInflater) =
|
||||
FragmentPlaybackPanelBinding.inflate(inflater)
|
||||
|
@ -70,6 +65,13 @@ class PlaybackPanelFragment :
|
|||
) {
|
||||
super.onBindingCreated(binding, savedInstanceState)
|
||||
|
||||
// AudioEffect expects you to use startActivityForResult with the panel intent. There is no
|
||||
// contract analogue for this intent, so the generic contract is used instead.
|
||||
equalizerLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
// --- UI SETUP ---
|
||||
binding.root.setOnApplyWindowInsetsListener { view, insets ->
|
||||
val bars = insets.systemBarInsetsCompat
|
||||
|
@ -116,6 +118,7 @@ class PlaybackPanelFragment :
|
|||
}
|
||||
|
||||
override fun onDestroyBinding(binding: FragmentPlaybackPanelBinding) {
|
||||
equalizerLauncher = null
|
||||
binding.playbackToolbar.setOnMenuItemClickListener(null)
|
||||
// Marquee elements leak if they are not disabled when the views are destroyed.
|
||||
binding.playbackSong.isSelected = false
|
||||
|
@ -137,7 +140,10 @@ class PlaybackPanelFragment :
|
|||
// music playback.
|
||||
.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC)
|
||||
try {
|
||||
equalizerLauncher.launch(equalizerIntent)
|
||||
requireNotNull(equalizerLauncher) {
|
||||
"Equalizer panel launcher was not available"
|
||||
}
|
||||
.launch(equalizerIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
requireContext().showToast(R.string.err_no_app)
|
||||
}
|
||||
|
|
|
@ -93,11 +93,11 @@ class PlaybackViewModel(application: Application) :
|
|||
get() = playbackManager.currentAudioSessionId
|
||||
|
||||
init {
|
||||
playbackManager.addCallback(this)
|
||||
playbackManager.addListener(this)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
playbackManager.removeCallback(this)
|
||||
playbackManager.removeListener(this)
|
||||
}
|
||||
|
||||
override fun onIndexMoved(index: Int) {
|
||||
|
|
|
@ -41,9 +41,7 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueAdapter.
|
|||
private val queueModel: QueueViewModel by activityViewModels()
|
||||
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
|
||||
private val queueAdapter = QueueAdapter(this)
|
||||
private val touchHelper: ItemTouchHelper by lifecycleObject {
|
||||
ItemTouchHelper(QueueDragCallback(queueModel))
|
||||
}
|
||||
private var touchHelper: ItemTouchHelper? = null
|
||||
|
||||
override fun onCreateBinding(inflater: LayoutInflater) = FragmentQueueBinding.inflate(inflater)
|
||||
|
||||
|
@ -53,7 +51,10 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueAdapter.
|
|||
// --- UI SETUP ---
|
||||
binding.queueRecycler.apply {
|
||||
adapter = queueAdapter
|
||||
touchHelper.attachToRecyclerView(this)
|
||||
touchHelper =
|
||||
ItemTouchHelper(QueueDragCallback(queueModel)).also {
|
||||
it.attachToRecyclerView(this)
|
||||
}
|
||||
}
|
||||
|
||||
// Sometimes the scroll can change without the listener being updated, so we also
|
||||
|
@ -84,7 +85,7 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), QueueAdapter.
|
|||
}
|
||||
|
||||
override fun onPickUp(viewHolder: RecyclerView.ViewHolder) {
|
||||
touchHelper.startDrag(viewHolder)
|
||||
requireNotNull(touchHelper) { "ItemTouchHelper was not available" }.startDrag(viewHolder)
|
||||
}
|
||||
|
||||
private fun updateDivider() {
|
||||
|
|
|
@ -47,7 +47,7 @@ class QueueViewModel : ViewModel(), PlaybackStateManager.Listener {
|
|||
var scrollTo: Int? = null
|
||||
|
||||
init {
|
||||
playbackManager.addCallback(this)
|
||||
playbackManager.addListener(this)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -135,6 +135,6 @@ class QueueViewModel : ViewModel(), PlaybackStateManager.Listener {
|
|||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
playbackManager.removeCallback(this)
|
||||
playbackManager.removeListener(this)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,15 +26,12 @@ import org.oxycblt.auxio.R
|
|||
import org.oxycblt.auxio.databinding.DialogPreAmpBinding
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.ui.ViewBindingDialogFragment
|
||||
import org.oxycblt.auxio.util.context
|
||||
|
||||
/**
|
||||
* aa [ViewBindingDialogFragment] that allows user configuration of the current [ReplayGainPreAmp].
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class PreAmpCustomizeDialog : ViewBindingDialogFragment<DialogPreAmpBinding>() {
|
||||
private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) }
|
||||
|
||||
override fun onCreateBinding(inflater: LayoutInflater) = DialogPreAmpBinding.inflate(inflater)
|
||||
|
||||
override fun onConfigDialog(builder: AlertDialog.Builder) {
|
||||
|
@ -42,7 +39,7 @@ class PreAmpCustomizeDialog : ViewBindingDialogFragment<DialogPreAmpBinding>() {
|
|||
.setTitle(R.string.set_pre_amp)
|
||||
.setPositiveButton(R.string.lbl_ok) { _, _ ->
|
||||
val binding = requireBinding()
|
||||
settings.replayGainPreAmp =
|
||||
Settings(requireContext()).replayGainPreAmp =
|
||||
ReplayGainPreAmp(binding.withTagsSlider.value, binding.withoutTagsSlider.value)
|
||||
}
|
||||
.setNegativeButton(R.string.lbl_cancel, null)
|
||||
|
@ -53,7 +50,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 = settings.replayGainPreAmp
|
||||
val preAmp = Settings(requireContext()).replayGainPreAmp
|
||||
binding.withTagsSlider.value = preAmp.with
|
||||
binding.withoutTagsSlider.value = preAmp.without
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ class PlaybackStateManager private constructor() {
|
|||
* @see Listener
|
||||
*/
|
||||
@Synchronized
|
||||
fun addCallback(listener: Listener) {
|
||||
fun addListener(listener: Listener) {
|
||||
if (isInitialized) {
|
||||
listener.onNewPlayback(index, queue, parent)
|
||||
listener.onRepeatChanged(repeatMode)
|
||||
|
@ -117,7 +117,7 @@ class PlaybackStateManager private constructor() {
|
|||
* @see Listener
|
||||
*/
|
||||
@Synchronized
|
||||
fun removeCallback(listener: Listener) {
|
||||
fun removeListener(listener: Listener) {
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
|
@ -629,7 +629,7 @@ class PlaybackStateManager private constructor() {
|
|||
|
||||
/**
|
||||
* The interface for receiving updates from [PlaybackStateManager]. Add the listener to
|
||||
* [PlaybackStateManager] using [addCallback], remove them on destruction with [removeCallback].
|
||||
* [PlaybackStateManager] using [addListener], remove them on destruction with [removeListener].
|
||||
*/
|
||||
interface Listener {
|
||||
/**
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.oxycblt.auxio.playback.system
|
|||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
|
@ -47,7 +48,9 @@ import org.oxycblt.auxio.util.logD
|
|||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class MediaSessionComponent(private val context: Context, private val listener: Listener) :
|
||||
MediaSessionCompat.Callback(), PlaybackStateManager.Listener, Settings.Listener {
|
||||
MediaSessionCompat.Callback(),
|
||||
PlaybackStateManager.Listener,
|
||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private val mediaSession =
|
||||
MediaSessionCompat(context, context.packageName).apply {
|
||||
isActive = true
|
||||
|
@ -55,13 +58,13 @@ class MediaSessionComponent(private val context: Context, private val listener:
|
|||
}
|
||||
|
||||
private val playbackManager = PlaybackStateManager.getInstance()
|
||||
private val settings = Settings(context, this)
|
||||
private val settings = Settings(context)
|
||||
|
||||
private val notification = NotificationComponent(context, mediaSession.sessionToken)
|
||||
private val provider = BitmapProvider(context)
|
||||
|
||||
init {
|
||||
playbackManager.addCallback(this)
|
||||
playbackManager.addListener(this)
|
||||
mediaSession.setCallback(this)
|
||||
}
|
||||
|
||||
|
@ -79,15 +82,15 @@ class MediaSessionComponent(private val context: Context, private val listener:
|
|||
*/
|
||||
fun release() {
|
||||
provider.release()
|
||||
settings.release()
|
||||
playbackManager.removeCallback(this)
|
||||
settings.removeListener(this)
|
||||
playbackManager.removeListener(this)
|
||||
mediaSession.apply {
|
||||
isActive = false
|
||||
release()
|
||||
}
|
||||
}
|
||||
|
||||
// --- PLAYBACKSTATEMANAGER CALLBACKS ---
|
||||
// --- PLAYBACKSTATEMANAGER OVERRIDES ---
|
||||
|
||||
override fun onIndexMoved(index: Int) {
|
||||
updateMediaMetadata(playbackManager.song, playbackManager.parent)
|
||||
|
@ -139,9 +142,9 @@ class MediaSessionComponent(private val context: Context, private val listener:
|
|||
invalidateSecondaryAction()
|
||||
}
|
||||
|
||||
// --- SETTINGSMANAGER CALLBACKS ---
|
||||
// --- SETTINGS OVERRIDES ---
|
||||
|
||||
override fun onSettingChanged(key: String) {
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
when (key) {
|
||||
context.getString(R.string.set_key_cover_mode) ->
|
||||
updateMediaMetadata(playbackManager.song, playbackManager.parent)
|
||||
|
@ -149,7 +152,7 @@ class MediaSessionComponent(private val context: Context, private val listener:
|
|||
}
|
||||
}
|
||||
|
||||
// --- MEDIASESSION CALLBACKS ---
|
||||
// --- MEDIASESSION OVERRIDES ---
|
||||
|
||||
override fun onPlayFromMediaId(mediaId: String?, extras: Bundle?) {
|
||||
super.onPlayFromMediaId(mediaId, extras)
|
||||
|
|
|
@ -22,6 +22,7 @@ import android.content.BroadcastReceiver
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.SharedPreferences
|
||||
import android.media.AudioManager
|
||||
import android.media.audiofx.AudioEffect
|
||||
import android.os.IBinder
|
||||
|
@ -80,8 +81,8 @@ class PlaybackService :
|
|||
Player.Listener,
|
||||
InternalPlayer,
|
||||
MediaSessionComponent.Listener,
|
||||
Settings.Listener,
|
||||
MusicStore.Listener {
|
||||
MusicStore.Listener,
|
||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
// Player components
|
||||
private lateinit var player: ExoPlayer
|
||||
private lateinit var replayGainProcessor: ReplayGainAudioProcessor
|
||||
|
@ -144,12 +145,13 @@ class PlaybackService :
|
|||
.build()
|
||||
.also { it.addListener(this) }
|
||||
// Initialize the core service components
|
||||
settings = Settings(this, this)
|
||||
settings = Settings(this)
|
||||
settings.addListener(this)
|
||||
foregroundManager = ForegroundManager(this)
|
||||
// Initialize any listener-dependent components last as we wouldn't want a listener race
|
||||
// condition to cause us to load music before we were fully initialize.
|
||||
playbackManager.registerInternalPlayer(this)
|
||||
musicStore.addCallback(this)
|
||||
musicStore.addListener(this)
|
||||
widgetComponent = WidgetComponent(this)
|
||||
mediaSessionComponent = MediaSessionComponent(this, this)
|
||||
registerReceiver(
|
||||
|
@ -185,12 +187,12 @@ class PlaybackService :
|
|||
super.onDestroy()
|
||||
|
||||
foregroundManager.release()
|
||||
settings.release()
|
||||
settings.removeListener(this)
|
||||
|
||||
// Pause just in case this destruction was unexpected.
|
||||
playbackManager.setPlaying(false)
|
||||
playbackManager.unregisterInternalPlayer(this)
|
||||
musicStore.removeCallback(this)
|
||||
musicStore.removeListener(this)
|
||||
|
||||
unregisterReceiver(systemReceiver)
|
||||
serviceJob.cancel()
|
||||
|
@ -329,12 +331,13 @@ class PlaybackService :
|
|||
}
|
||||
}
|
||||
|
||||
// --- SETTINGSMANAGER OVERRIDES ---
|
||||
// --- SETTINGS OVERRIDES ---
|
||||
|
||||
override fun onSettingChanged(key: String) {
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
if (key == getString(R.string.set_key_replay_gain) ||
|
||||
key == getString(R.string.set_key_pre_amp_with) ||
|
||||
key == getString(R.string.set_key_pre_amp_without)) {
|
||||
// ReplayGain changed, we need to set it up again.
|
||||
onTracksChanged(player.currentTracks)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,10 +53,8 @@ import org.oxycblt.auxio.util.*
|
|||
class SearchFragment : ListFragment<FragmentSearchBinding>() {
|
||||
private val searchModel: SearchViewModel by androidViewModels()
|
||||
private val searchAdapter = SearchAdapter(this)
|
||||
private var imm: InputMethodManager? = null
|
||||
private var launchedKeyboard = false
|
||||
private val imm: InputMethodManager by lifecycleObject { binding ->
|
||||
binding.context.getSystemServiceCompat(InputMethodManager::class)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -74,13 +72,15 @@ class SearchFragment : ListFragment<FragmentSearchBinding>() {
|
|||
override fun onBindingCreated(binding: FragmentSearchBinding, savedInstanceState: Bundle?) {
|
||||
super.onBindingCreated(binding, savedInstanceState)
|
||||
|
||||
imm = binding.context.getSystemServiceCompat(InputMethodManager::class)
|
||||
|
||||
binding.searchToolbar.apply {
|
||||
// Initialize the current filtering mode.
|
||||
menu.findItem(searchModel.getFilterOptionId()).isChecked = true
|
||||
|
||||
setNavigationOnClickListener {
|
||||
// Keyboard is no longer needed.
|
||||
imm.hide()
|
||||
hideKeyboard()
|
||||
findNavController().navigateUp()
|
||||
}
|
||||
|
||||
|
@ -95,7 +95,7 @@ class SearchFragment : ListFragment<FragmentSearchBinding>() {
|
|||
|
||||
if (!launchedKeyboard) {
|
||||
// Auto-open the keyboard when this view is shown
|
||||
imm.show(this)
|
||||
showKeyboard(this)
|
||||
launchedKeyboard = true
|
||||
}
|
||||
}
|
||||
|
@ -184,7 +184,7 @@ class SearchFragment : ListFragment<FragmentSearchBinding>() {
|
|||
else -> return
|
||||
}
|
||||
// Keyboard is no longer needed.
|
||||
imm.hide()
|
||||
hideKeyboard()
|
||||
findNavController().navigate(action)
|
||||
}
|
||||
|
||||
|
@ -193,7 +193,7 @@ class SearchFragment : ListFragment<FragmentSearchBinding>() {
|
|||
if (requireBinding().searchSelectionToolbar.updateSelectionAmount(selected.size) &&
|
||||
selected.isNotEmpty()) {
|
||||
// Make selection of obscured items easier by hiding the keyboard.
|
||||
imm.hide()
|
||||
hideKeyboard()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -201,15 +201,19 @@ class SearchFragment : ListFragment<FragmentSearchBinding>() {
|
|||
* Safely focus the keyboard on a particular [View].
|
||||
* @param view The [View] to focus the keyboard on.
|
||||
*/
|
||||
private fun InputMethodManager.show(view: View) {
|
||||
private fun showKeyboard(view: View) {
|
||||
view.apply {
|
||||
requestFocus()
|
||||
postDelayed(200) { showSoftInput(view, InputMethodManager.SHOW_IMPLICIT) }
|
||||
postDelayed(200) {
|
||||
requireNotNull(imm) { "InputMethodManager was not available" }
|
||||
.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Safely hide the keyboard from this view. */
|
||||
private fun InputMethodManager.hide() {
|
||||
hideSoftInputFromWindow(requireView().windowToken, InputMethodManager.HIDE_NOT_ALWAYS)
|
||||
private fun hideKeyboard() {
|
||||
requireNotNull(imm) { "InputMethodManager was not available" }
|
||||
.hideSoftInputFromWindow(requireView().windowToken, InputMethodManager.HIDE_NOT_ALWAYS)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,12 +55,12 @@ class SearchViewModel(application: Application) :
|
|||
get() = _searchResults
|
||||
|
||||
init {
|
||||
musicStore.addCallback(this)
|
||||
musicStore.addListener(this)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
musicStore.removeCallback(this)
|
||||
musicStore.removeListener(this)
|
||||
}
|
||||
|
||||
override fun onLibraryChanged(library: MusicStore.Library?) {
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.oxycblt.auxio.settings
|
|||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
import android.os.Build
|
||||
import android.os.storage.StorageManager
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
|
@ -40,20 +41,14 @@ import org.oxycblt.auxio.util.logD
|
|||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
||||
/**
|
||||
* A [SharedPreferences] wrapper providing type-safe interfaces to all of the app's settings. Object
|
||||
* mutability
|
||||
* A [SharedPreferences] wrapper providing type-safe interfaces to all of the app's settings. Member
|
||||
* mutability is dependent on how they are used in app. Immutable members are often only modified by
|
||||
* the preferences view, while mutable members are modified elsewhere.
|
||||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class Settings(private val context: Context, private val listener: Listener? = null) :
|
||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
class Settings(private val context: Context) {
|
||||
private val inner = PreferenceManager.getDefaultSharedPreferences(context.applicationContext)
|
||||
|
||||
init {
|
||||
if (listener != null) {
|
||||
inner.registerOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate any settings from an old version into their modern counterparts. This can cause data
|
||||
* loss depending on the feasibility of a migration.
|
||||
|
@ -154,25 +149,19 @@ class Settings(private val context: Context, private val listener: Listener? = n
|
|||
}
|
||||
|
||||
/**
|
||||
* Release this instance and any callbacks held by it. This is not needed if no [Listener] was
|
||||
* originally attached.
|
||||
* Add a [SharedPreferences.OnSharedPreferenceChangeListener] to monitor for settings updates.
|
||||
* @param listener The [SharedPreferences.OnSharedPreferenceChangeListener] to add.
|
||||
*/
|
||||
fun release() {
|
||||
inner.unregisterOnSharedPreferenceChangeListener(this)
|
||||
fun addListener(listener: OnSharedPreferenceChangeListener) {
|
||||
inner.registerOnSharedPreferenceChangeListener(listener)
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
unlikelyToBeNull(listener).onSettingChanged(key)
|
||||
}
|
||||
|
||||
/** Simplified listener for settings changes. */
|
||||
interface Listener {
|
||||
// TODO: Refactor this lifecycle
|
||||
/**
|
||||
* Called when a setting has changed.
|
||||
* @param key The key of the setting that changed.
|
||||
* Unregister a [SharedPreferences.OnSharedPreferenceChangeListener], preventing any further
|
||||
* settings updates from being sent to ti.t
|
||||
*/
|
||||
fun onSettingChanged(key: String)
|
||||
fun removeListener(listener: OnSharedPreferenceChangeListener) {
|
||||
inner.unregisterOnSharedPreferenceChangeListener(listener)
|
||||
}
|
||||
|
||||
// --- VALUES ---
|
||||
|
|
|
@ -23,11 +23,8 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
||||
|
@ -37,7 +34,6 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
|
|||
*/
|
||||
abstract class ViewBindingDialogFragment<VB : ViewBinding> : DialogFragment() {
|
||||
private var _binding: VB? = null
|
||||
private var lifecycleObjects = mutableListOf<LifecycleObject<VB, *>>()
|
||||
|
||||
/**
|
||||
* Configure the [AlertDialog.Builder] during [onCreateDialog].
|
||||
|
@ -85,25 +81,6 @@ abstract class ViewBindingDialogFragment<VB : ViewBinding> : DialogFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to automatically create and destroy an object derived from the [ViewBinding].
|
||||
* @param create Block to create the object from the [ViewBinding].
|
||||
*/
|
||||
fun <T> lifecycleObject(create: (VB) -> T): ReadOnlyProperty<Fragment, T> {
|
||||
lifecycleObjects.add(LifecycleObject(null, create))
|
||||
|
||||
return object : ReadOnlyProperty<Fragment, T> {
|
||||
private val objIdx = lifecycleObjects.lastIndex
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun getValue(thisRef: Fragment, property: KProperty<*>) =
|
||||
requireNotNull(lifecycleObjects[objIdx].data) {
|
||||
"Cannot access lifecycle object when view does not exist"
|
||||
}
|
||||
as T
|
||||
}
|
||||
}
|
||||
|
||||
final override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
@ -119,9 +96,6 @@ abstract class ViewBindingDialogFragment<VB : ViewBinding> : DialogFragment() {
|
|||
|
||||
final override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val binding = unlikelyToBeNull(_binding)
|
||||
// Populate lifecycle-dependent objects
|
||||
lifecycleObjects.forEach { it.populate(binding) }
|
||||
// Configure binding
|
||||
onBindingCreated(requireBinding(), savedInstanceState)
|
||||
// Apply the newly-configured view to the dialog.
|
||||
|
@ -132,21 +106,8 @@ abstract class ViewBindingDialogFragment<VB : ViewBinding> : DialogFragment() {
|
|||
final override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
onDestroyBinding(unlikelyToBeNull(_binding))
|
||||
// Clear the lifecycle-dependent objects
|
||||
lifecycleObjects.forEach { it.clear() }
|
||||
// Clear binding
|
||||
_binding = null
|
||||
logD("Fragment destroyed")
|
||||
}
|
||||
|
||||
/** Internal implementation of [lifecycleObject]. */
|
||||
private data class LifecycleObject<VB, T>(var data: T?, val create: (VB) -> T) {
|
||||
fun populate(binding: VB) {
|
||||
data = create(binding)
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
data = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,8 +23,6 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
||||
|
@ -34,7 +32,6 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
|
|||
*/
|
||||
abstract class ViewBindingFragment<VB : ViewBinding> : Fragment() {
|
||||
private var _binding: VB? = null
|
||||
private var lifecycleObjects = mutableListOf<LifecycleObject<VB, *>>()
|
||||
|
||||
/**
|
||||
* Inflate the [ViewBinding] during [onCreateView].
|
||||
|
@ -75,26 +72,6 @@ abstract class ViewBindingFragment<VB : ViewBinding> : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to automatically create and destroy an object derived from the [ViewBinding].
|
||||
* @param create Block to create the object from the [ViewBinding].
|
||||
*/
|
||||
fun <T> lifecycleObject(create: (VB) -> T): ReadOnlyProperty<Fragment, T> {
|
||||
// TODO: Phase this out.
|
||||
lifecycleObjects.add(LifecycleObject(null, create))
|
||||
|
||||
return object : ReadOnlyProperty<Fragment, T> {
|
||||
private val objIdx = lifecycleObjects.lastIndex
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun getValue(thisRef: Fragment, property: KProperty<*>) =
|
||||
requireNotNull(lifecycleObjects[objIdx].data) {
|
||||
"Cannot access lifecycle object when view does not exist"
|
||||
}
|
||||
as T
|
||||
}
|
||||
}
|
||||
|
||||
final override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
@ -103,9 +80,6 @@ abstract class ViewBindingFragment<VB : ViewBinding> : Fragment() {
|
|||
|
||||
final override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val binding = unlikelyToBeNull(_binding)
|
||||
// Populate lifecycle-dependent objects
|
||||
lifecycleObjects.forEach { it.populate(binding) }
|
||||
// Configure binding
|
||||
onBindingCreated(requireBinding(), savedInstanceState)
|
||||
logD("Fragment created")
|
||||
|
@ -114,21 +88,8 @@ abstract class ViewBindingFragment<VB : ViewBinding> : Fragment() {
|
|||
final override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
onDestroyBinding(unlikelyToBeNull(_binding))
|
||||
// Clear the lifecycle-dependent objects
|
||||
lifecycleObjects.forEach { it.clear() }
|
||||
// Clear binding
|
||||
_binding = null
|
||||
logD("Fragment destroyed")
|
||||
}
|
||||
|
||||
/** Internal implementation of [lifecycleObject]. */
|
||||
private data class LifecycleObject<VB, T>(var data: T?, val create: (VB) -> T) {
|
||||
fun populate(binding: VB) {
|
||||
data = create(binding)
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
data = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@ import org.oxycblt.auxio.list.ClickableListListener
|
|||
import org.oxycblt.auxio.list.Item
|
||||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.ui.ViewBindingDialogFragment
|
||||
import org.oxycblt.auxio.util.context
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
||||
|
@ -38,7 +37,6 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
|
|||
class AccentCustomizeDialog :
|
||||
ViewBindingDialogFragment<DialogAccentBinding>(), ClickableListListener {
|
||||
private var accentAdapter = AccentAdapter(this)
|
||||
private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) }
|
||||
|
||||
override fun onCreateBinding(inflater: LayoutInflater) = DialogAccentBinding.inflate(inflater)
|
||||
|
||||
|
@ -46,6 +44,7 @@ class AccentCustomizeDialog :
|
|||
builder
|
||||
.setTitle(R.string.set_accent)
|
||||
.setPositiveButton(R.string.lbl_ok) { _, _ ->
|
||||
val settings = Settings(requireContext())
|
||||
if (accentAdapter.selectedAccent == settings.accent) {
|
||||
// Nothing to do.
|
||||
return@setPositiveButton
|
||||
|
@ -66,7 +65,7 @@ class AccentCustomizeDialog :
|
|||
if (savedInstanceState != null) {
|
||||
Accent.from(savedInstanceState.getInt(KEY_PENDING_ACCENT))
|
||||
} else {
|
||||
settings.accent
|
||||
Settings(requireContext()).accent
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package org.oxycblt.auxio.widgets
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Build
|
||||
import coil.request.ImageRequest
|
||||
|
@ -41,14 +42,15 @@ import org.oxycblt.auxio.util.logD
|
|||
* @author Alexander Capehart (OxygenCobalt)
|
||||
*/
|
||||
class WidgetComponent(private val context: Context) :
|
||||
PlaybackStateManager.Listener, Settings.Listener {
|
||||
PlaybackStateManager.Listener, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private val playbackManager = PlaybackStateManager.getInstance()
|
||||
private val settings = Settings(context, this)
|
||||
private val settings = Settings(context)
|
||||
private val widgetProvider = WidgetProvider()
|
||||
private val provider = BitmapProvider(context)
|
||||
|
||||
init {
|
||||
playbackManager.addCallback(this)
|
||||
playbackManager.addListener(this)
|
||||
settings.addListener(this)
|
||||
}
|
||||
|
||||
/** Update [WidgetProvider] with the current playback state. */
|
||||
|
@ -104,9 +106,9 @@ class WidgetComponent(private val context: Context) :
|
|||
/** Release this instance, preventing any further events from updating the widget instances. */
|
||||
fun release() {
|
||||
provider.release()
|
||||
settings.release()
|
||||
settings.removeListener(this)
|
||||
widgetProvider.reset(context)
|
||||
playbackManager.removeCallback(this)
|
||||
playbackManager.removeListener(this)
|
||||
}
|
||||
|
||||
// --- CALLBACKS ---
|
||||
|
@ -118,7 +120,8 @@ class WidgetComponent(private val context: Context) :
|
|||
override fun onStateChanged(state: InternalPlayer.State) = update()
|
||||
override fun onShuffledChanged(isShuffled: Boolean) = update()
|
||||
override fun onRepeatChanged(repeatMode: RepeatMode) = update()
|
||||
override fun onSettingChanged(key: String) {
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
if (key == context.getString(R.string.set_key_cover_mode) ||
|
||||
key == context.getString(R.string.set_key_round_mode)) {
|
||||
update()
|
||||
|
|
|
@ -75,11 +75,12 @@ if ndk_path is None or not os.path.isfile(os.path.join(ndk_path, "ndk-build")):
|
|||
candidates.append(entry.path)
|
||||
|
||||
if len(candidates) > 0:
|
||||
print(WARN + "warn:" + NC + " NDK_PATH was not set or invalid. multiple " +
|
||||
print(WARN + "warn:" + NC + " ANDROID_NDK_HOME was not set or invalid. multiple " +
|
||||
"candidates were found however:")
|
||||
for i, candidate in enumerate(candidates):
|
||||
print("[" + str(i) + "] " + candidate)
|
||||
|
||||
print(WARN + "info:" + NC + " NDK r21e is recommended for this script. Other " +
|
||||
"NDKs may result in unexpected behavior.")
|
||||
try:
|
||||
ndk_path = candidates[int(input("enter the ndk to use [default 0]: "))]
|
||||
except:
|
||||
|
|
Loading…
Reference in a new issue