From 493b0a9f32feb68eb5f69622a73cc7ba668cd0f7 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Sat, 31 Dec 2022 11:11:09 -0700 Subject: [PATCH] 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. --- CHANGELOG.md | 1 - .../java/org/oxycblt/auxio/MainFragment.kt | 6 +-- .../oxycblt/auxio/detail/DetailViewModel.kt | 4 +- .../org/oxycblt/auxio/home/HomeFragment.kt | 28 +++++++------ .../org/oxycblt/auxio/home/HomeViewModel.kt | 16 +++++--- .../auxio/home/tabs/TabCustomizeDialog.kt | 16 +++----- .../list/selection/SelectionViewModel.kt | 4 +- .../org/oxycblt/auxio/music/MusicStore.kt | 4 +- .../org/oxycblt/auxio/music/MusicViewModel.kt | 4 +- .../auxio/music/extractor/SeparatorsDialog.kt | 8 ++-- .../auxio/music/picker/PickerViewModel.kt | 2 +- .../auxio/music/storage/MusicDirsDialog.kt | 31 ++++++++++---- .../org/oxycblt/auxio/music/system/Indexer.kt | 4 +- .../auxio/music/system/IndexerService.kt | 11 +++-- .../auxio/playback/PlaybackPanelFragment.kt | 22 ++++++---- .../auxio/playback/PlaybackViewModel.kt | 4 +- .../auxio/playback/queue/QueueFragment.kt | 11 ++--- .../auxio/playback/queue/QueueViewModel.kt | 4 +- .../replaygain/PreAmpCustomizeDialog.kt | 7 +--- .../playback/state/PlaybackStateManager.kt | 6 +-- .../playback/system/MediaSessionComponent.kt | 21 ++++++---- .../auxio/playback/system/PlaybackService.kt | 19 +++++---- .../oxycblt/auxio/search/SearchFragment.kt | 26 +++++++----- .../oxycblt/auxio/search/SearchViewModel.kt | 4 +- .../org/oxycblt/auxio/settings/Settings.kt | 41 +++++++------------ .../auxio/ui/ViewBindingDialogFragment.kt | 39 ------------------ .../oxycblt/auxio/ui/ViewBindingFragment.kt | 39 ------------------ .../auxio/ui/accent/AccentCustomizeDialog.kt | 5 +-- .../oxycblt/auxio/widgets/WidgetComponent.kt | 15 ++++--- prebuild.py | 5 ++- 30 files changed, 175 insertions(+), 232 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61e4cbaea..e979d1af3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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" diff --git a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt index b692ae477..833b85a16 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainFragment.kt @@ -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) @@ -76,6 +74,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() diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt index f29a4999e..4e6e60005 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt @@ -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?) { diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt index 64a5b2696..d83ee3618 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -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 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? = 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) } } } diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt index 157736366..f9e88e3ef 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeViewModel.kt @@ -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()) /** 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 = _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. diff --git a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt index c9da12001..063086b15 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/tabs/TabCustomizeDialog.kt @@ -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(), 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(), 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(), 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(), TabAd } override fun onPickUp(viewHolder: RecyclerView.ViewHolder) { - touchHelper.startDrag(viewHolder) + requireNotNull(touchHelper) { "ItemTouchHelper was not available" }.startDrag(viewHolder) } private companion object { diff --git a/app/src/main/java/org/oxycblt/auxio/list/selection/SelectionViewModel.kt b/app/src/main/java/org/oxycblt/auxio/list/selection/SelectionViewModel.kt index efc654077..754e8ca08 100644 --- a/app/src/main/java/org/oxycblt/auxio/list/selection/SelectionViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/list/selection/SelectionViewModel.kt @@ -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) } /** diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt index df3fad818..713163cbc 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt @@ -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) } diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt index 117495e7d..bb6205dc5 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicViewModel.kt @@ -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?) { diff --git a/app/src/main/java/org/oxycblt/auxio/music/extractor/SeparatorsDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/extractor/SeparatorsDialog.kt index c302f8646..96259f7c5 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/extractor/SeparatorsDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/extractor/SeparatorsDialog.kt @@ -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() { - 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() { .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() { // 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 diff --git a/app/src/main/java/org/oxycblt/auxio/music/picker/PickerViewModel.kt b/app/src/main/java/org/oxycblt/auxio/music/picker/PickerViewModel.kt index 825ed4061..0050a8bae 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/picker/PickerViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/picker/PickerViewModel.kt @@ -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?) { diff --git a/app/src/main/java/org/oxycblt/auxio/music/storage/MusicDirsDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/storage/MusicDirsDialog.kt index a0308a91e..6d4244d07 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/storage/MusicDirsDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/storage/MusicDirsDialog.kt @@ -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(), 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? = 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) diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/Indexer.kt b/app/src/main/java/org/oxycblt/auxio/music/system/Indexer.kt index 2e13d05d4..ee987d57e 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/system/Indexer.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/system/Indexer.kt @@ -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 diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt index 35f3e7f50..f3ef164bd 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt @@ -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), diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt index fd9ebc425..3a629f8b7 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackPanelFragment.kt @@ -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? = 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) } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt index ab12b5cd8..2f5ea9f14 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -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) { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt index 07e502f57..e12fa0f0f 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueFragment.kt @@ -41,9 +41,7 @@ class QueueFragment : ViewBindingFragment(), 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(), 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(), QueueAdapter. } override fun onPickUp(viewHolder: RecyclerView.ViewHolder) { - touchHelper.startDrag(viewHolder) + requireNotNull(touchHelper) { "ItemTouchHelper was not available" }.startDrag(viewHolder) } private fun updateDivider() { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueViewModel.kt index f35019fde..fb30d6c5c 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/queue/QueueViewModel.kt @@ -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) } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/replaygain/PreAmpCustomizeDialog.kt b/app/src/main/java/org/oxycblt/auxio/playback/replaygain/PreAmpCustomizeDialog.kt index 3c08e6b47..b1666684a 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/replaygain/PreAmpCustomizeDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/replaygain/PreAmpCustomizeDialog.kt @@ -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() { - 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() { .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() { // 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 } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt index 18bbeea0f..c5415c23b 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt @@ -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 { /** diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt index c34bdb628..863d71b6b 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt @@ -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) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt index 82f60bb51..d62053211 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt @@ -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) } } diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt index 0a7154855..f9055e96d 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchFragment.kt @@ -53,10 +53,8 @@ import org.oxycblt.auxio.util.* class SearchFragment : ListFragment() { 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() { 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() { 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() { else -> return } // Keyboard is no longer needed. - imm.hide() + hideKeyboard() findNavController().navigate(action) } @@ -193,7 +193,7 @@ class SearchFragment : ListFragment() { 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() { * 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) } } diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt index 0be8d3926..72ea04fae 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchViewModel.kt @@ -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?) { diff --git a/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt b/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt index 64bebac04..aabb07823 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt @@ -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. - */ - fun onSettingChanged(key: String) + /** + * Unregister a [SharedPreferences.OnSharedPreferenceChangeListener], preventing any further + * settings updates from being sent to ti.t + */ + fun removeListener(listener: OnSharedPreferenceChangeListener) { + inner.unregisterOnSharedPreferenceChangeListener(listener) } // --- VALUES --- diff --git a/app/src/main/java/org/oxycblt/auxio/ui/ViewBindingDialogFragment.kt b/app/src/main/java/org/oxycblt/auxio/ui/ViewBindingDialogFragment.kt index 177ae8735..fd362131d 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/ViewBindingDialogFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/ViewBindingDialogFragment.kt @@ -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 : DialogFragment() { private var _binding: VB? = null - private var lifecycleObjects = mutableListOf>() /** * Configure the [AlertDialog.Builder] during [onCreateDialog]. @@ -85,25 +81,6 @@ abstract class ViewBindingDialogFragment : DialogFragment() { } } - /** - * Delegate to automatically create and destroy an object derived from the [ViewBinding]. - * @param create Block to create the object from the [ViewBinding]. - */ - fun lifecycleObject(create: (VB) -> T): ReadOnlyProperty { - lifecycleObjects.add(LifecycleObject(null, create)) - - return object : ReadOnlyProperty { - 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 : 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 : 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(var data: T?, val create: (VB) -> T) { - fun populate(binding: VB) { - data = create(binding) - } - - fun clear() { - data = null - } - } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/ViewBindingFragment.kt b/app/src/main/java/org/oxycblt/auxio/ui/ViewBindingFragment.kt index f0a005582..b5ece20e2 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/ViewBindingFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/ViewBindingFragment.kt @@ -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 : Fragment() { private var _binding: VB? = null - private var lifecycleObjects = mutableListOf>() /** * Inflate the [ViewBinding] during [onCreateView]. @@ -75,26 +72,6 @@ abstract class ViewBindingFragment : Fragment() { } } - /** - * Delegate to automatically create and destroy an object derived from the [ViewBinding]. - * @param create Block to create the object from the [ViewBinding]. - */ - fun lifecycleObject(create: (VB) -> T): ReadOnlyProperty { - // TODO: Phase this out. - lifecycleObjects.add(LifecycleObject(null, create)) - - return object : ReadOnlyProperty { - 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 : 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 : 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(var data: T?, val create: (VB) -> T) { - fun populate(binding: VB) { - data = create(binding) - } - - fun clear() { - data = null - } - } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentCustomizeDialog.kt b/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentCustomizeDialog.kt index c14e38985..1f3048725 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentCustomizeDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/accent/AccentCustomizeDialog.kt @@ -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(), 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 }) } diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt index b6e9f437c..638299bb9 100644 --- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt +++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetComponent.kt @@ -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() diff --git a/prebuild.py b/prebuild.py index 59ea9552b..f8353d1a7 100755 --- a/prebuild.py +++ b/prebuild.py @@ -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: